После выката обходчик должен начать новый круг с главной. Он и начинал — но через минуту очередь «откатывалась» к старому остатку, а главная оставалась холодной. Виновником оказалась особенность WordPress, о которой мало кто помнит: опция, прочитанная в запросе, кэшируется до конца этого запроса.
Это двадцать четвёртая часть цикла «Кэш страниц без MISS».
Как устроен прогон
- воркер читает очередь адресов из опции;
- обходит их до 50 секунд;
- записывает обратно то, что не успел.
Если за эти 50 секунд сброс после выката поставил новый круг — полный перечень, главная первой, — воркер в конце прогона перезаписывает его своим старым остатком.
Симптом в журнале: счётчик «осталось» убывает со старого числа (857 → 851), а не с полного перечня (966).
Почему не помогло перечитать
Первая мысль — перед записью перечитать опцию и долить новое. Не работает: без постоянного кэша объектов (Redis, Memcached) WordPress держит прочитанную опцию в памяти до конца запроса. Повторный get_option возвращает тот же снимок, что и в начале прогона.
Лечение
function warm_queue_read(): array {
wp_cache_delete('page_warm_queue', 'options'); // выбросить снимок
return (array) get_option('page_warm_queue', []);
}
// в начале прогона
$cycle = get_option('page_warm_cycle_id');
$queue = warm_queue_read();
// ... обход ...
// в конце: если за время прогона начался новый круг — не затирать его
wp_cache_delete('page_warm_cycle_id', 'options');
if (get_option('page_warm_cycle_id') !== $cycle) {
$fresh = warm_queue_read();
$queue = array_values(array_unique(array_merge($fresh, $left)));
} else {
$queue = $left;
}
update_option('page_warm_queue', $queue, false);
wp_cache_deleteперед каждым чтением — получить значение из базы, а не снимок;- номер круга — сброс пишет новый номер (например,
microtime), воркер сравнивает его со своим; - слияние вместо перезаписи — новый круг идёт первым, остаток старого — следом.
Как это проверить
Гонку трудно поймать вживую. Мы подменяли очередь в базе прямо во время прогона — на первом HTTP-запросе обходчика через фильтр pre_http_request — и смотрели, что окажется в опции после прогона.
После выката главная холодная, хотя прогрев идёт?
Пришлите адрес — проверю, не затирает ли обходчик новую очередь.
// загружаю комментарии…