Под каждой статьёй этого блога — число просмотров и кнопки «нравится» и «не нравится». Страница при этом лежит в кэше неделю и отдаётся всем одинаковой. Цифры в неё не запечены: они приходят отдельным запросом.

Это пятая часть цикла «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 на просмотр статьи и один на страницу списка.

Тот же приём — пустое место в разметке плюс запрос после загрузки — дальше в цикле повторится для комментариев.

Счётчики на сайте мешают кэшу или врут?

Пришлите адрес — переделаю так, чтобы страницы оставались в кэше, а цифры были честными.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.