Под каждой статьёй этого блога — число просмотров и кнопки «нравится» и «не нравится». Страница при этом лежит в кэше неделю и отдаётся всем одинаковой. Цифры в неё не запечены: они приходят отдельным запросом.
Это пятая часть цикла «WordPress на кэшируемом сайте».
Почему не в HTML
Если печатать счётчик в разметку, у вас два плохих варианта. Либо цифра застывает на неделю (кэш живёт неделю), либо каждый просмотр и лайк сбрасывает кэш статьи — и тогда кэша фактически нет у самых читаемых страниц. Засчитывать просмотр кукой при рендере ещё хуже: ответ с Set-Cookie nginx не кэширует вообще (седьмая часть цикла про кэш страниц).
Как устроено
- в разметке — место под цифры с точками вместо чисел;
- после полной загрузки страницы и в свободную минуту браузера скрипт отправляет один запрос: «засчитай просмотр и дай счётчики»;
- ответ приходит из PHP мимо кэша (
admin-ajax.phpв кэш не попадает) и заполняет цифры; - в списке статей — один запрос на все карточки, до 30 штук, без засчитывания просмотров.
function afterLoad() {
return new Promise((resolve) => {
const go = () => ('requestIdleCallback' in window
? requestIdleCallback(() => resolve(), { timeout: 2000 })
: setTimeout(resolve, 200));
document.readyState === 'complete' ? go() : addEventListener('load', go, { once: true });
});
}
await afterLoad();
const res = await wpAjax('post_stats', { id: box.dataset.postId, view: 1 });
fillStats(box, res.stats);
Запрос уходит после загрузки, поэтому в окно первой отрисовки он не попадает и на скорость страницы не влияет.
Кого считать
- один просмотр с адреса раз в 12 часов — обновление страницы не накручивает счётчик;
- явных ботов не считаем по User-Agent; к тому же без JavaScript запрос просто не придёт;
- редакторов не считаем — иначе каждый просмотр своей статьи после правки добавлял бы цифру.
Сам адрес посетителя не хранится: в ключ отметки идёт хэш адреса с солью сайта.
Атомарное увеличение
Обычный путь «прочитать мету, прибавить единицу, записать» при двух одновременных просмотрах теряет один из них: оба прочитали 10, оба записали 11. Поэтому увеличение — одним запросом к базе:
$wpdb->query($wpdb->prepare(
"UPDATE {$wpdb->postmeta}
SET meta_value = GREATEST(0, CAST(meta_value AS SIGNED) + %d)
WHERE post_id = %d AND meta_key = %s",
$delta, $postId, $meta
));
wp_cache_delete($postId, 'post_meta'); // иначе объектный кэш вернёт старое число
Последняя строка обязательна, если у сайта есть объектный кэш (у нас Redis): прямой запрос мимо update_post_meta кэш меты не сбрасывает, и счётчик «застывает» до его вытеснения.
Лайк без куки
Голос хранится на сервере, по тому же хэшу адреса, на год. Клиенту не верим: прежний голос сервер берёт у себя, поэтому повторные клики не накручивают, а передумать можно — лайк, дизлайк, снять голос.
Честное ограничение: отметка голоса лежит во временном хранилище WordPress, а оно у нас в Redis с вытеснением старых ключей при нехватке памяти. Если отметку вытеснит, человек сможет проголосовать ещё раз. Для счётчика симпатий в блоге это приемлемо; для голосования с последствиями нужна таблица в базе.
Что получили
- страница статьи — из кэша, как и раньше;
- изменение счётчиков кэш не сбрасывает вовсе;
- один небольшой запрос к PHP на просмотр статьи и один на страницу списка.
Тот же приём — пустое место в разметке плюс запрос после загрузки — дальше в цикле повторится для комментариев.
Счётчики на сайте мешают кэшу или врут?
Пришлите адрес — переделаю так, чтобы страницы оставались в кэше, а цифры были честными.
// загружаю комментарии…