STALE можно, MISS нельзя: цель кэширования и три слоя кэша
Мерить нужно не HIT, а MISS: устаревшая копия мгновенно — норма, сборка страницы при посетителе — отказ. Три слоя кэша и десять компонентов гарантии. Каталог: 97 % MISS на товарных → 0.
Мерить нужно не HIT, а MISS: устаревшая копия мгновенно — норма, сборка страницы при посетителе — отказ. Три слоя кэша и десять компонентов гарантии. Каталог: 97 % MISS на товарных → 0.
valid — свежесть, inactive — удаление за простой, max_size — потолок. inactive=2h за тихую ночь делал 79 из 89 страниц холодными, хотя блоки были тёплыми на 100 %. Что за что отвечает и конфигурация, к которой мы пришли.
Кэш на 30 минут с фоновым обновлением: 1,3–1,7 ядра круглосуточно при нуле посетителей и окно обновления до 1,5 с для следующего посетителя. После перехода на неделю — CPU 30–49 % → 0–4 %. И контрпример, где экономии не было.
Изменились данные — страница сбрасывается и греется; не менялись — греть незачем. valid 7d, inactive 7d, max_size 1g, сброс при выкате обязателен. Цена: пропущенный хук стоит не полчаса, а неделю.
Языковая кука в ключе — и кэш за ночь вырос с 28 до 293 МБ при том же трафике. Страницы × устройства × языки × метки: как посчитать объём, почему при кэше на неделю потолок поднимают в той же правке и как посмотреть кэш, не трогая сайт.
HEAD и GET — разные ячейки (250 мусорных от проверки curl -I), кука сортировки со значением default давала посетителю личный холодный кэш. Склейка через map, квирк nginx со словом default и как найти забытые в ключе куки.
nginx не кэширует ответ с Set-Cookie, а обходчик приходит без кук и тоже получает куку. Счётчик просмотров и метка источника держали в кэше 25 страниц из 89 — после переноса на ajax 89 из 89. Как найти за минуту.
Кука сравнения в карте обхода выключает кэш на всём сайте: главная 1,4 с вместо 0,07 с у того, кто сравнивает товары. Что можно в обход, что — в ключ, что — на клиент, и ловушка с запечённым счётчиком.
Серверная проверка куки под кэшем даёт вариант первого посетителя всем: плашка согласия у всех навсегда или ни у кого, маска телефона чужой страны. Лечение: рисовать всегда и убирать скриптом, язык — в ключ.
HTML одинаков для всех, живое дорисовывается отдельно. Остатки — пустой слот, скрипт и микрокэш 30 секунд, обмен не сбрасывает страницы. Кнопки редактора — класс на body и отдельный скрипт.
Адрес с ?ysclid всегда MISS: модуль атрибуции ставит куку, а ответ с Set-Cookie nginx не кэширует. 2246 самых дорогих запросов в сутки получают самый медленный ответ. Почему в nginx не лечится и как решать — метка на клиенте.
X-Cache-Status говорит, что сейчас, но не что было вчера. log_format с $upstream_cache_status, временем ответа и частями ключа: BYPASS залогиненного, холодный вариант ключа или сеть — видно по одной строке.
Удалённую копию нельзя отдать как устаревшую — следующий посетитель ждёт рендер. Греть после ответа через fastcgi_finish_request и во всех вариантах ключа: на одном адресе 8 ячеек, а прогревалась одна.
Ключ не вычислять — читать строку KEY в файлах кэша и сравнивать путь без параметров. Главную точечно не чистили без всякой причины, а немедленный сброс из админки не вызывался. Бонус: 449 вариантов главной от бота соцсети.
Пустые сохранения обмена, дрожь порядка (~5000 сбросов в сутки), счётчики просмотров (2,2 ГБ сирот за ночь). Подпись значимых полей, обратная ловушка — правки контентщика перестали появляться — и телеметрия причин сбросов.
Код чистки пропускал сброс в расчёте на внешнюю функцию, а её не перенесли: страницы товаров не сбрасывались никогда. Итог — карточки без картинок на телефоне. Как проверили починку и почему тихий пропуск хуже ошибки.
Мета приходит после save_post, пропуски ради анти-шторма с умершим обоснованием, производные меты без подписи значений и настройки в шапке каждой страницы — поддомен неделю показывал пустой контакт. Что чинили и какой вопрос задавать.
Человек ждёт «сохранил и вижу», обмен 1С шлёт сотни сохранений волной. Сброс сразу в конце запроса для админки и пачкой через минуту для обмена; как различить источник одной функцией и проверить без правки контента.
Ячейка блока обновляется по версии кода, версии данных или времени записи — правка PHP-шаблона не меняет ничего. Сброс делает ячейку устаревшей, и первое открытие показывает старое: это защита от MISS. Рецепт для разработки.
Тема сравнивает время манифеста сборки с сохранённым и при расхождении сбрасывает кэш — ловит и живой запрос, и крон. Почему нужен сигнал после сброса, как мы ложно решили, что выкат не сбрасывает, и сколько наполняется кэш (22 % за 8 минут).
Манифест сборки приехал на 27 секунд раньше исправленного PHP — детектор выката запустил сброс старым кодом. Правку в пути сброса нельзя проверить тем же выкатом: проверять следующим или вызовом сигнала руками (0,03–0,07 с).
nginx удаляет подкаталоги кэша, пока мы их обходим: исключение, return в catch — и сигнал прогрева не отправлен. 634 из 640 страниц MISS через 6 минут после выката. CATCH_GET_CHILD, catch без return и как воспроизвести.
Выкат одновременно замечают живой запрос и крон — два сброса рвут каталог друг у друга. add_option проверяет и пишет в два шага и не спасает. INSERT IGNORE, протухание замка через 5 минут, try/finally и сигнал только от владельца.
Воркер в конце прогона записывал старый остаток поверх нового круга после выката — главная оставалась холодной. Повторный get_option возвращает снимок начала запроса. wp_cache_delete, номер круга и слияние вместо перезаписи.
Выкат первым заметил бот на www. и dev. — код прогрева искал свой сервер по чужому имени, очередь не встала, в кэше 12 файлов на 6 часов. Тихий вариант: прогрев греет ячейки чужого хоста. Лечение — адрес прямо из базы.
Выкат удалил старые бандлы с хэшем в имени, а страница в кэше ссылалась на них: 4 из 10 файлов отдавали 404, формы не работали. Почему сброс не всегда спасает, как защититься и проверка одной командой.
Свежесть даёт инвалидация, а обход нужен для другого: после массового сброса первым посетителем должны быть мы, хвост редких страниц надо защищать от вытеснения, а из разметки обходчик собирает реестр блоков.
Ручной список потерял архив каталога на двух сайтах. Три источника: перечисление из WordPress, карта сайта и самоочистка по коду ответа — почему пятисотку запоминать нельзя. Бонус: отчёт о 33 адресах с 404 в карте сайта.
Обходчик не ходил дальше 6-й страницы, а их было 13: страницы 7–13 отдавали MISS по 216–707 мс. Глубину брать из данных, а не из константы: после правки 28 страниц пагинации вместо 12.
Обходчик идёт не через публичный домен, а прямо в nginx сайта. Без X-Forwarded-Proto в кэш ложатся http-ссылки, без точного имени контейнера греется кэш соседа. Бюджет, обрыв, замок и потолок очереди.
За 15 секунд через nginx прошло 2 запроса, а пул вырос с 14 до 46 процессов: устаревшая страница отдаётся мгновенно, рендер идёт фоном, и за обходчиком тянется хвост. Пауза только после быстрого ответа, тёплый запас пула — и почему поднимать min_spare хуже.
Один код прогрева: 0 запросов за 6 часов на одном сайте и 5109 на другом. Ноль — это «всё тёплое», а тысячи — часовой добор под кэш на 30 минут. На рабочем сайте 75 % запросов к PHP — обходчик. Что сняли и что оставили.
У точечной чистки была своя очередь с разовым событием и без сторожа. Событие не выполнилось — к полудню из 212 товарных страниц в кэше около 40, ответ 2,4–3,7 с. Решение: класть адреса в голову очереди основного обходчика.
Ключ кэша шире, чем греет обходчик: первый заход на поддомен 2,48 с, выбравший другую страну шёл мимо кэша на каждой странице. Правило сверки ключа с обходом и что мы сознательно не греем.
Прогон вышел по замку и не поставил следующее событие — очередь из 42 задач стояла, пока кто-нибудь не добавит новую. Одна функция планирования, сторож на тике крона и доказательство: событие снесли руками, через 130 с очередь пуста сама.
Разовая чистка через 180 с после выката не сработала: такое же событие уже стояло — 32 981 мёртвый ключ провисел 20 минут. Приём: вешать разовую работу на идущий воркер и сравнивать версию кода, а не время. 4568 ключей за 0,03 с.
Сосед на общем сервере занимал 331 % CPU, порог «нагрузка ≥ 3 — пропустить» выключил прогрев на часы, молча. Ступени: треть бюджета, один рендер за прогон, пропуск только при аварии. И три гипотезы, которые замер опроверг.
Окно в 20 минут закрылось при 3118 холодных ячейках. Ограничитель прогона — время, режим держится, пока хвост больше 300: 4229 → 0 за 33 минуты при нагрузке 0,04–0,22. Потолок 3 часа и что происходит на маленьком сайте.
Прогон 40 с, следующий — через 60 с от конца: воркер проскакивал тик крона и работал 40 секунд из 120. Каждый прогон отрабатывал полный бюджет, поэтому отчёт был зелёным. Запуск через 5 с — темп ×2,1. Мерить интервал, а не рендеры.
Пустая тестовая копия с прогревом — около 1,5 ядра круглосуточно. Два ключа в wp-config: только прогрев или кэш целиком через X-Accel-Expires. Ворота в точках входа и грабли: сброс делает PHP, а HIT до него не доходит.
Ячейки поимённо только на чтение — 33 001 из 33 001; все адреса двумя User-Agent — 1190 запросов за 4 с, 1178 HIT и 12 законных BYPASS; пагинация по пресетам; обычный браузер. И норма для маленьких сайтов.
$ tail -f /blog
Пишу разборы по скорости сайтов, кэшу и разработке — на живых проектах и с цифрами. Подпишитесь, и ссылка на новую статью придёт сразу после выхода. Без рекламы и спама, отписаться — в один клик.
или на почту