Мы уже разобрались, как устроена система внутри (Монолит против микросервисов), как её части договариваются между собой (Проектирование API) и как не терять тяжёлые задачи в очереди (Очереди сообщений). Пришла пора самой благодарной темы: как сделать так, чтобы сайт отвечал быстрее, почти не меняя код. Ответ почти всегда один — кэш.
Кэш (от английского cache, читается «кэш») — это быстрая память, где мы храним уже вычисленный или уже полученный результат, чтобы не считать его заново. Пользователь открыл страницу, вы сходили в базу, собрали данные и отдали их. Через минуту этот же пользователь (или другой) просит то же самое. Зачем опять идти в базу, если можно вспомнить готовый ответ? Именно это и делает кэш: превращает дорогую операцию в дешёвую.
Хорошо настроенный кэш способен убрать с плеч сервера 70–90 % нагрузки. Плохо настроенный — добавить багов: пользователи увидят чужие цены, устаревшие страницы и «прилипшие» корзины. Разница между этими двумя исходами — понимание нескольких простых принципов. Разберём их по порядку: как ускорить, как записывать, как вычищать старое, чем Redis отличается от Memcached, что делать, чтобы кэш не «протёк», и как это всё применить к WordPress. Без магии и жаргона — на конкретных примерах.
Часть I. Кэш как ускоритель: из чего вообще складывается выигрыш
1. Одна замена: дорогое действие становимся дешёвым
Любой ответ сайта собирается из «дорогих» кусков: SQL-запрос к базе (MySQL, PostgreSQL), вызов внешнего API, сложный расчёт на CPU, чтение большого файла с диска. Всё это медленно, потому что данные далеко — на диске, в другом процессе, в другой стране через сеть.
Кэш работает по простому правилу: сделал один раз — отдал много раз. Вы один раз сходили в базу, собрали карточку товара, положили результат в память под ключом product:42. Все последующие обращения к этому товару читают готовый результат из памяти и не трогают базу вообще.
Чтобы это работало, у кэша есть три обязательных свойства:
- Скорость чтения и записи. Он должен быть заметно быстрее того, что заменяет. Иначе смысла нет.
- Ограниченный объём. Память не резиновая: рано или поздно старые записи приходится выкидывать, чтобы впустить новые. Отсюда — алгоритмы очистки из Части III.
- Понятные сроки годности. Данные в кэше могут устареть. Нужен способ вовремя их сбросить — отсюда invalidation из Части V.
2. Hit rate, miss rate, latency: три числа, которые надо видеть
Чтобы понять, помогает ли вам кэш, достаточно трёх показателей.
Latency (задержка) — сколько времени проходит от запроса до ответа. Измеряется в миллисекундах (мс) и наносекундах. Ориентиры по порядку величин, чтобы чувствовать масштаб:
| Что читаем | Примерная задержка |
|---|---|
| Регистр/CPU-кэш | доли наносекунды |
| Оперативная память машины | ~100 нс |
| Redis по локальной сети | ~1–2 мс |
| Запись в свою таблицу MySQL | ~5–20 мс |
| Сложный SELECT по большому индексу | ~20–200 мс |
| HTTP-запрос к внешнему API | ~50–300 мс |
| Запрос на другой континент | 150–250 мс и больше |
Hit rate (доля попаданий) — какая часть обращений нашла нужные данные в кэше. Miss rate (доля промахов) — обратная величина: сколько раз кэш не помог и пришлось идти «в источник». Формула простая:
hit_rate = hits / (hits + misses)
Если из 1000 обращений к карточке товара кэш вернул результат 950 раз, hit rate = 95 %, miss rate = 5 %. Остальные 50 обращений — это запросы, которые всё-таки дошли до базы.
Почему это важно. Разница между кэшем с hit rate 50 % и кэшем с 95 % — огромная. В первом случае база получает почти половину вашего трафика, во втором — двадцатую часть. Одна и та же инфраструктура, разная конфигурация — разная боль.
3. Когда кэш помогает, а когда нет
Кэш полезен там, где одни и те же данные просят часто, а меняются они редко. Типичные кандидаты:
- главное меню и виджеты сайта;
- карточка товара, страница статьи, описание категории;
- результаты сложного, но стабильного запроса (топ продаж за неделю);
- готовые HTML-страницы (страничный кэш);
- токены и настройки сессий.
Бесполезен или опасен там, где данные меняются на каждом чихе или должны быть точны в реальном времени: остаток на счету, счётчик «кто сейчас онлайн», баланс кошелька. Для таких значений кэш либо ставят на очень короткий TTL (секунды), либо не ставят вообще. Правило: если вы не можете объяснить, когда значение во кэше перестаёт быть правдой, кэшировать его нельзя.
Часть II. Стратегии записи: как данные попадают в хранилище
Кэш и база данных — это два хранилища, где лежат копии одних и тех же данных. Главный вопрос при записи: что обновлять, в каком порядке и что делать, если одно из двух упало. Разберём три базовые стратегии, их названия расшифруем по-русски.
4. Write-through: пишем сразу в оба
Write-through (сквозная запись) — при изменении данных запись идёт одновременно в кэш и в базу, и только когда оба ответили «готово», мы считаем операцию завершённой.
<?php
// Write-through: обновляем источник, затем прогреваем кэш тем же значением.
function save_product_through(int $id, array $data): void
{
// 1) Пишем в базу (источник истины).
db()->prepare('UPDATE products SET price = ?, updated_at = NOW() WHERE id = ?')
->execute([$data['price'], $id]);
// 2) Сразу кладём новое значение в кэш, чтобы следующий читатель не промахнулся.
cache()->set('product:' . $id, $data, 3600); // TTL — время жизни, в секундах
}Плюсы: база и кэш всегда согласованы, следующий читатель почти наверняка получит свежие данные. Минус: каждая запись стоит дороже (надо дождаться и диска базы, и кэша), поэтому write-through выбирают там, где чтение важнее записи.
5. Write-back: сначала в кэш, в базу — позже
Write-back, он же write-behind (отложенная запись) — изменение пишется только в быстрый кэш, помечается как «грязная» (незаписанная) и сливается в базу позже: батчем, по таймеру или при освобождении памяти.
Плюсы: запись почти мгновенная и очень частые правки одного и того же значения не дублируются — в базу уходит усреднённый результат. Это резко снижает нагрузку на диск.
Минусы и они же риски: пока значение лежит только в кэше, оно незащищено. Упал сервер с кэшем до слияния — данные потеряны. Поэтому write-back применяют там, где временная потеря допустима (счётчики просмотров, аналитика, игровые прогрессы), и обязательно с надёжным механизмом сброса на диск.
6. Read-through: не пишем, а подтягиваем
Read-through (сквозное чтение) — это не про запись, а про чтение. Когда данные просят, а в кэше их нет (miss), обёртка сама идёт в источник, берёт значение, кладёт его в кэш и отдаёт вызывающему коду. Клиент ничего не знает про базу — для него всё делает кэш.
Именно read-through чаще всего стоит за формулировкой «у нас включён объект-кэш»: приложение просит значение, а под капотом идёт «в кэш → при промахе в базу → записать в кэш → вернуть». Комбинация «read-through для чтения + write-through или инвалидация для записи» — самый частый и безопасный рецепт.
<?php
// Read-through: достать из кэша, при промахе сходить в базу и записать в кэш.
function get_product(int $id): array
{
$key = 'product:' . $id;
if (($data = cache()->get($key)) !== null) {
return $data; // hit — попали, база не тронута
}
// miss — идём в источник
$row = db()->query('SELECT * FROM products WHERE id = ?', [$id])->fetchAll();
$data = $row[0] ?? [];
cache()->set($key, $data, 3600); // прогреваем кэш на будущее
return $data;
}Коротко о выборе стратегии:
| Ситуация | Стратегия | Почему |
|---|---|---|
| Читаем много, пишем редко | Read-through + инвалидация при записи | База спит, значения свежие |
| Нужна абсолютная согласованность | Write-through | Никакого расхождения между кэшем и базой |
| Пишем очень часто, потери допустимы | Write-back | Резко меньше запросов на запись |
| Счётчики, аналитика, «кто смотрел» | Write-back | Мгновенный ответ, слияние батчем |
Часть III. Алгоритмы очистки: что выкидывать, когда место кончилось
Кэш живёт в памяти ограниченного размера. Когда она заполняется, новые данные можно впустить только выкинув старые. Какое значение выкинуть — это и решает алгоритм вытеснения (eviction policy). Аббревиатуры расшифруем сразу.
7. Три «классики» по одному критерию
- LRU (Least Recently Used — «наименее недавно использованный») — выкидываем то, к которому дольше всего не обращались. Логика: если в значение не заходили давно, оно вряд ли понадобится сейчас. Самый частый выбор по умолчанию.
- LFU (Least Frequently Used — «наименее часто используемый») — выкидываем то, к которому реже всего обращались за всё время. Логика: к популярному наверняка вернутся, к непопулярному — нет. Хорошо работает, когда популярность стабильна, но «застревает» на бывших хитах, которые вышли из моды.
- FIFO (First In, First Out — «первым пришёл, первым ушёл») — выкидываем самое старое по времени записи, независимо от того, как часто в него заходили. Самый простой, но часто стреляет мимо: свежие-но-неполезные данные могут вытеснить старое-но-горячее.
На практике LRU выигрывает почти всегда: он учитывает и частоту, и давность. Поэтому и в Redis, и в Memcached, и в браузерных кэшах дефолт чаще всего LRU или его вариация.
8. TTL: срок годности вместо вытеснения
TTL (Time To Live — «время жизни») — это не про вытеснение «по памяти», а про самоустаревание по времени. Вы кладёте значение и говорите: «через N секунд оно протухнет». TTL отвечает на вопрос не «когда место кончилось», а «когда данные перестают быть правдой».
<?php
cache()->set('settings:main', $settings, 900); // проживёт 15 минут
cache()->set('home:hero', $html, 86400); // сутки
cache()->set('captcha:' . $sessionId, $code, 120); // две минутыВажный нюанс: короткий TTL — это почти всегда разброс. Если тысячи записей истекут в одну секунду, все они одновременно ринутся в базу. Об этом подробнее в Части V про cache stampede. Значит, TTL всегда ставят с небольшой случайной добавкой.
Redis и Memcached по-разному применяют TTL и вытеснение:
- Memcached при переполнении вытесняет по принципу «старое/давно не тронутое» и строго хранит TTL: протухшее просто исчезает.
- Redis умеет выбирать политику вытеснения:
allkeys-lru,volatile-lru(только с TTL),allkeys-lfu,allkeys-random,noeviction(не выкидывать ничего, при переполнении падать с ошибкой). Разные политики — под разные задачи.
# Посмотреть текущую политику вытеснения и лимит памяти в Redis
redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy
# Менять на лету (осторожно, влияет на весь инстанс)
redis-cli CONFIG SET maxmemory 512mb
redis-cli CONFIG SET maxmemory-policy allkeys-lru
Часть IV. Redis против Memcached: чем отличаются и когда что выбирать
Оба — распределённые кэши по модели «ключ-значение» поверх памяти. Оба быстрые. Оба дают один эффект: разгрузить базу. Разница — в том, что ещё они умеют, и это решает выбор.
9. Сравнение «на пальцах»
| Характеристика | Redis | Memcached |
|---|---|---|
| Модель данных | ключ → строка, плюс списки, хэши, множества, сортированные множества | только ключ → строка (до 1 МБ) |
| Хранение на диске | да, выгрузка (RDB/AOF) — переживает перезапуск | нет, только оперативная память, при рестарте всё теряется |
| Репликация и кластер | есть из коробки (мастер-реплика, Cluster) | нет, распределяет сам клиент |
| «Атомарные» операции и блокировки | есть (SETNX, INCR, транзакции, Lua-скрипты) | ограничены (счётчики, cas) |
| Подписка на события (pub/sub) | есть | нет |
| Многоядерность | один поток на инстанс (нужно несколько процессов) | многопоточный, хорошо масштабится по ядрам |
| Протокол | RESP (более богатый) | текстовый ASCII/бинарный, очень простой |
10. Когда что брать
Берите Redis, если:
- нужны структуры, а не только сериализованные «блобы» (сортированные множества для рейтинга, списки для очереди, хэши для профиля);
- нужна выгрузка на диск, чтобы кэш пережил перезапуск и не «стартовал пустым»;
- хотите делать блокировки, счётчики, простую pub/sub;
- нужен один инструмент под кэш, очередь, сессии и «тонкое» состояние одновременно.
Берите Memcached, если:
- данные — всегда сериализованный «мешок» (объект, JSON, сжатый HTML) под строковым ключом;
- нужна максимальная простота и предсказуемое многопоточное масштабирование на ядра;
- потеря содержимого при рестарте не страшна (всё равно это read-through кэш);
- вы уже в мире, где Memcached есть на хостинге «из коробки» (классический общий хостинг).
Практический вывод для WordPress и PHP-проектов на VPS: сегодня почти всегда Redis — он универсальнее и покрывает сразу кэш, сессии и очереди. Memcached остаётся разумным выбором на дешёвых хостингах и в проектах, где нужен именно чистый «blob-кэш» без логики.
11. Обёртка-функция над Redis на PHP
Чтобы не вызывать Redis напрямую в каждом файле, делают тонкую обёртку (wrapper, «обёртка»): она унифицирует названия, зашивает TTL, ловит падения и не даёт приложению упасть, если кэш недоступен. Ниже — пример такой функции.
<?php
// redis_cache.php — простая обёртка над Redis для «достать или посчитать».
//
// Если Redis жив — читаем из кэша. При промахе вычисляем значение через
// $callback, записываем его в Redis с TTL и возвращаем. Если Redis упал —
// молча работаем без кэша (graceful degradation), а не роняем весь сайт.
function cached(string $key, int $ttl, callable $callback)
{
static $redis = null;
static $broken = false;
// Redis лежит — просто исполняем «дорогую» логику каждый раз.
if ($broken || $redis === null) {
try {
if ($redis === null) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 1.0); // 1 сек на коннект
$redis->setOption(Redis::OPT_READ_TIMEOUT, 1.0);
}
} catch (Throwable $e) {
$broken = true; // не долбим упавший Redis каждым запросом
return $callback();
}
}
try {
$raw = $redis->get($key);
if ($raw !== false) { // hit — значение в кэше есть
return unserialize($raw);
}
$value = $callback(); // miss — считаем через источник
// Небольшой случайный сдвиг TTL, чтобы ключи не протухали толпой.
$jitter = random_int(0, (int) max(1, $ttl * 0.1));
$redis->setex($key, $ttl + $jitter, serialize($value));
return $value;
} catch (Throwable $e) {
// Любая ошибка сети/сериализации не должна валить страницу.
return $callback();
}
}
// Пример использования: дорогое «собрать топ продаж за сутки».
$top = cached(
'sales:top:24h',
900,
fn() => db()->query(
'SELECT sku, SUM(qty) q FROM sale_items
WHERE created_at > NOW() - INTERVAL 1 DAY
GROUP BY sku ORDER BY q DESC LIMIT 20'
)->fetchAll()
);Что здесь принципиально: обёртка не роняет сайт, если Redis недоступен (падает в «работу без кэша»), ставит TTL с разбросом и прячет unserialize/serialize внутри. Эти три вещи отличают «кустарный» кэш от боевого.
12. Команды redis-cli для диагностики
redis-cli — встроенная консоль Redis. Через неё смотрят, жив ли кэш, что лежит в памяти и почему hit rate просел.
# Проверить, отвечает ли сервер и какой он версии
redis-cli PING # ожидаем PONG
redis-cli INFO server # версия, время работы, режим
# Метрики кэша: попадания и промахи
redis-cli INFO stats | egrep 'keyspace_hits|keyspace_misses'
# keyspace_hits — сколько раз GET нашёл значение (попадания)
# keyspace_misses — сколько раз GET не нашёл (промахи)
# Посмотреть конкретный ключ, его тип и остаток жизни
redis-cli TYPE product:42 # string / hash / list / set...
redis-cli GET product:42
redis-cli TTL product:42 # секунд до протухания (-1 = без TTL)
redis-cli OBJECT ENCODING product:42
# Быстро и безопасно (не блокируя сервер) осмотреть часть ключей:
redis-cli --scan --pattern 'product:*' | head
redis-cli DBSIZE # сколько всего ключей
# Удалить один ключ (например, после изменения товара — инвалидация)
redis-cli DEL product:42
# Сбросить конкретный шаблон ключей (в бою аккуратнее — см. Часть V)
redis-cli --scan --pattern 'cat:7:*' | xargs -r redis-cli DEL
# Сколько памяти занято и как распределено
redis-cli INFO memory | egrep 'used_memory_human|maxmemory_human'
Пара подсказок: не зовите KEYS * на бою (блокирует сервер) — всегда --scan. INFO stats и --scan дают вам те самые hit rate и состояние памяти из Части I.
Часть V. Invalidation и cache stampede: как не сломать то, что ускорили
Ускорить просто. Трудно — не обмануть пользователя устаревшим значением и не положить базу в момент, когда кэш «кончился». Это два самых страшных сценария в кэшировании.
13. Invalidation: протухание кэша при изменении данных
Invalidation (инвалидация, «обесценивание») — процесс, при котором вы сообщаете кэшу, что значение устарело, и он перестаёт его отдавать. Есть три способа.
А) Точечное удаление (самый честный). После записи в базу явно удаляем соответствующий ключ: cache()->delete('product:' . $id). Следующий читатель через read-through вытащит свежее. Чисто, но требует дисциплины: любой код, который меняет товар, обязан тронуть кэш. Забыли в одном месте — пользователи неделю видят старую цену.
Б) Перезапись (write-through). Как в Части II: кладём в кэш сразу новое значение вместо удаления.
В) Именование-версионирование. Вместо удаления меняем «номер версии» в шаблоне ключей: весь кэш категории автоматически считается устаревшим.
Чтобы инвалидация не превратилась в лотерею, имена ключей должны быть системными. Ниже — соглашение, которое делает очистку предсказуемой.
{
"rule": "domain:object:id[:variant], всегда строчными, без пробелов",
"examples": {
"product:42": "карточка товара id 42",
"product:42:price": "только цена товара 42 (короткий TTL)",
"category:7:list:page1": "первая страница товаров категории 7",
"user:1001:profile": "профиль пользователя 1001",
"page:home:html": "готовый HTML главной",
"sales:top:24h": "топ продаж за сутки"
},
"versioning": {
"pattern": "cache_version",
"how": "ключ содержит текущий номер версии, например 'cat:7:list:v18:page1'",
"invalidate": "изменили данные категории 7 — подняли глобальную версию, старые ключи стали мусором и вытеснились по TTL"
},
"anti_patterns": [
"ключи вроде 'data', 'temp', 'cache1' — их невозможно осмысленно удалить",
"ключи с PII (пароль, токен) внутри имени — утекают в логи и мониторинг",
"очень длинные ключи с полным запросом — раздувают память"
]
}
Почему это работает: если все ключи товаров начинаются с product: и содержат :id, инвалидация товара после записи сводится к одному предсказуемому DEL product:42 (плюс, при желании, сброс версий в ключах category:*). Версионирование удобно для «массового» устаревания: правка, влияющая на тысячи страниц, просто поднимает глобальный счётчик версии, и все старые ключи перестают читаться, а по TTL тихо вычищаются из памяти.
14. Cache stampede: лавина одинаковых запросов
Cache stampede («топтание кэша», он же «удар стада») — ситуация, когда большое число запросов одновременно промахивается и бросается в один и тот же медленный источник. Три типичных варианта:
- Одновременное протухание. Все страницы каталога получили одинаковый TTL в 900 секунд и истекли в одну секунду — в момент истечения база получила удар от всех онлайн-пользователей разом.
- Популярный «холодный» ключ. Самая горячая страница только что была удалена для обновления, а на неё зашли 500 человек за секунду.
- Массовый сброс после релиза/деплоя. Новый релиз обнулил весь кэш, а трафик остался прежний.
Признаки: CPU базы в 100 %, очередь запросов, latency взлетает, запросы «висят» — потому что каждый параллельный читатель независимо считает одно и то же значение.
Как предотвратить (от простого к надёжному):
- Разброс TTL (jitter). Как в коде Части IV: к базовому TTL добавляем случайную величину, чтобы ключи не протухали толпой.
- Блокировка пересчёта (single-flight / lock). Пока значение пересчитывается, другие просители ждут уже вычисленный результат, а не считают заново.
<?php
// Защита от stampede: кто первый — тот считает и держит замок, остальные ждут.
function cached_locked(string $key, int $ttl, callable $callback, int $waitMs = 1000)
{
if (($raw = $redis()->get($key)) !== false) {
return unserialize($raw); // hit — выходим сразу
}
$lock = $key . ':lock';
// SET с NX (ставим только если ключа нет) — кто выиграл гонку, тот пересчитывает.
if ($redis()->set($lock, 1, ['NX', 'EX' => 10])) {
try {
$value = $callback();
$redis()->setex($key, $ttl + random_int(0, $ttl / 10), serialize($value));
return $value;
} finally {
$redis()->del($lock); // снять замок в любом случае
}
}
// Не мы держим замок — коротко подождать и перечитать готовое значение.
$deadline = microtime(true) + $waitMs / 1000;
while (microtime(true) < $deadline) {
if (($raw = $redis()->get($key)) !== false) {
return unserialize($raw);
}
usleep(20000); // 20 мс пауза
}
return $callback(); // на худой конец считаем сами
}- Прогрев (warm-up) заранее. Перед пиком трафика (или сразу после деплоя) «прогреваем» горячие ключи: сами, без пользователей, обращаемся к ним, чтобы кэш заполнился. Это особенно важно при массовом сбросе после релиза.
- Не удалять всё разом. После правки, затрагивающей тысячи страниц, не делайте глобальный
FLUSH. Поднимайте версию ключей (см. JSON выше) или удаляйте точечно — тогда промахов будет меньше и они распределятся во времени. - Короткий «shadow TTL» (stale-while-revalidate идея). Кэшируем значение на долгое время, а рядом ставим короткий «флаг свежести»: если свежести нет — отдаём старое, но фоновым процессом пересчитываем. Пользователь в момент пика почти всегда попадает, база не получает шторм.
15. Где ещё нужна осторожность
- Расхождение «кэш против базы» (cache coherence, согласованность кэша). Классический баг: пользователь обновил профиль, а на одной машине (веб) кэш уже новый, а на второй (балансировщик) ещё старый — видно «не то, что сохранил». Решения: одна и та же распределённая база кэша (Redis/Memcached) для всех веб-машин вместо локальных; короткое время жизни для персонализированных данных; инвалидация по событию. При балансировке нагрузки (см. Балансировка нагрузки) распределённый кэш снимает большую часть этих проблем.
- Кэш + очередь. Тяжёлые пересчёты лучше выносить в очередь сообщений, а в кэш класть готовый результат: как это связать — в Очереди сообщений.
- Готовый HTML на краю сети. Страничный кэш можно поднимать на CDN: там живут свои сроки годности (
Cache-Control), см. CDN и edge-вычисления. Кэш приложения и CDN-кэш решают разные задачи, но складываются в один эффект.
Часть VI. Практика: ускоряем WordPress объект-кэшем
WordPress устроен так, что почти каждая страница генерируется набором SQL-запросов к MySQL: меню, виджеты, посты, комментарии, таксономии, настройки. На тихом сайте это не видно, на оживлённом — именно база становится узким местом. Быстрый фикс без переписывания кода — объектный кэш (object cache).
16. Как это работает в WordPress
В WordPress есть встроенный слой кэширования: функция wp_cache_get() / wp_cache_set(). До подключения внешнего кэша он живёт только на время одного запроса (in-memory на одну загрузку страницы) и между запросами ничего не хранит. После подключения внешнего хранилища — через файл object-cache.php в папке wp-content/ — тот же самый код начинает складывать данные в Redis или Memcached, и они переживают переход между запросами. Это и есть объект-кэш.
Что реально ускоряется:
- повторяющиеся
SELECTизwp_options,wp_postmeta,wp_termsиwp_term_relationships; - результаты
WP_Queryс теми же параметрами; - счётчики и транзитные задачи (transients).
Важно понимать границы: объект-кэш не заменяет полный страничный кэш (готовый HTML) и не панацея для страниц, где каждый запрос уникален. Объектный кэш снимает нагрузку с базы для повторяющихся запросов; страничный кэш (сидит на CDN или в плагинах) убирает вообще всю генерацию. Они дополняют друг друга.
17. Подключение Redis к WordPress
Типовой путь на VPS (PHP 8.2+):
- Поставить сервер Redis (
redis-server) и PHP-расширениеredis(pecl install redis). - Положить в
wp-content/файлobject-cache.phpиз официального плагина Redis Object Cache (или аналога). - В
wp-config.phpуказать, где живёт Redis, и при желании размер кэша. - В админке включить объект-кэш и нажать «Flush cache» для прогрева.
<?php
// wp-config.php — фрагмент: адрес Redis и настройки поведения кэша.
define('WP_REDIS_HOST', '127.0.0.1');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_DATABASE', 0); // изолированный «шаг» (database) под WordPress
define('WP_REDIS_MAXTTL', 86400); // потолок времени жизни ключа, секунд
define('WP_CACHE_KEY_PREFIX', 'wp_main'); // префикс, чтобы не сталкиваться с соседним сайтом
// Если Redis на другой машине и нужен пароль:
// define('WP_REDIS_PASSWORD', getenv('REDIS_PASSWORD'));После включения обязательно прогрейте кэш: зайдите как обычный пользователь на главную и пару типичных страниц — первые запросы заполнят Redis. С этого момента база заметно разгружается.
18. Что смотреть и как мерить результат
Нельзя «включить кэш и поверить на глаз» — надо мерить. Порядок действий:
- Замерить latency до и после. Инструмент вроде
wrk,abилиk6с последовательными прогонами; или просто время генерации страницы в логах. Подробные методики нагрузочного теста — в Нагрузочное тестирование. - Считать hit rate Redis. Команды
redis-cli INFO stats(см. Часть IV):keyspace_hitsпротивkeyspace_misses. Здоровый объект-кэш WordPress часто даёт 80–95 %. - Смотреть медленные запросы в базе. Если после включения кэша в MySQL всё ещё долгие
SELECT— это кандидаты на явное кэширование через transients или на оптимизацию (индексы). Об этом — в Оптимизация баз данных. - Не перекэшлировать. Проверьте, что вы не кэшируете персональные данные (корзина, залогиненное состояние) надолго: иначе один пользователь увидит чужую корзину.
# Hit rate в процентах одной строкой (для grep по выводу INFO)
redis-cli INFO stats | awk -F: '/keyspace_hits/{h=$2} /keyspace_misses/{m=$2} \
END {printf "hit_rate=%.1f%%\n", 100*h/(h+m)}'
# Сколько ключей и памяти занято после прогрева
redis-cli DBSIZE
redis-cli INFO memory | grep used_memory_human
19. Типичные грабли и как их убрать
- Redis умер — сайт упал. Обёртка (см. код Части IV) обязана работать в режиме «без кэша», а не падать. Проверьте: выдерните Redis и убедитесь, что WordPress продолжает отдавать страницы (медленнее, но живо).
- Кэш стареет, а пользователь видит устаревшее. Значит не хватает инвалидации: меняете товар/страницу — удаляйте или перезаписывайте конкретный ключ.
- Stampede после Flush. «Flush cache» в плагине стирает всё разом — в этот момент база получает максимум запросов. Делайте это в тишину, прогревайте сразу после и не трогайте часто.
- Раздувание памяти. Если ключей много и TTL слишком длинные, память кончится и Redis начнёт вытеснять полезное. Ставьте разумный
maxmemoryиallkeys-lru/volatile-lru. - Один Redis на несколько сайтов без префиксов. Задавайте
WP_CACHE_KEY_PREFIXкаждому сайту, иначе ключи сталкиваются и один сайт читает данные другого.
Чек-лист: настройка кэша без самообмана
- Начните с замера. Включите счётчик hit rate и latency до кэша и после. Без этих двух чисел вы не понимаете, стало ли лучше.
- Кэшируйте только то, что можно описать по сроку годности. Не можете сказать, когда значение станет неправдой — не кэшируйте.
- Прочитайте read-through как дефолт, write-through как гарантия. Слиянием read + точечная инвалидация при записи закрывает 80 % задач.
- Выберите LRU как политику вытеснения. Он редко проигрывает и почти всегда уместен; TTL добавляйте везде.
- TTL — всегда с разбросом. Один и тот же TTL на тысячи ключей = гарантированный stampede в момент истечения.
- Ставьте блокировку пересчёта на горячих ключах.
SETNX-замок или single-flight спасает базу в пик. - Имейте соглашение об именах ключей.
domain:object:id:variant— чтобы инвалидация была предсказуемой и не требовалаKEYS *. - Redis — по умолчанию для новых проектов. Он универсальнее; Memcached берите, если нужен только простой blob-кэш.
- WordPress: включите объект-кэш и прогрейте его. Через
object-cache.phpи Redis. И проверьте, что при падении Redis сайт живёт дальше. - Страничный и CDN-кэш — это отдельный слой. Объектный кэш снимает нагрузку с базы, готовый HTML и CDN убирают саму генерацию. Не путайте и не дублируйте бессмысленно.
- Измеряйте медленные запросы базы. Кэш — не оправдание плохих индексов: оптимизация БД даёт эффект, который не отменяет даже самый горячий кэш.
Итог. Кэш — это дисциплина, а не кнопка
Кэш превращает дорогое действие в дешёвое и почти всегда окупается: те же серверы, а ответов в разы больше, и база отдыхает. Мы прошли всю цепочку — от базовых метрик hit rate и latency, через стратегии записи (write-through, write-back, read-through) и алгоритмы очистки (LRU, LFU, FIFO, TTL), к разнице Redis и Memcached, и дальше — к самому важному: аккуратной инвалидации и защите от cache stampede.
Самое ценное, что стоит запомнить: кэш хорош ровно настолько, насколько вы контролируете момент, когда он устаревает. Ускорить — одна работа; не обмануть пользователя и не обрушить базу в пик — вторая, более тонкая. Именно поэтому мы столько места отдали инвалидации, именам ключей, блокировке пересчёта и прогреву, а не просто «включил Redis и забыл».
Кэш отлично работает с готовым HTML: когда страницу не надо ни генерировать, ни читать из базы, а она уже лежит на краю сети, — это следующий уровень ускорения, и про него статья про CDN и edge-вычисления. Но и самый быстрый кэш бессилен, если запросы приходят неравномерно и один сервер не держит пик — здесь начинается роль балансировки нагрузки, которая раскладывает трафик между машинами, опираясь как раз на общий распределённый кэш.
Материал написан простым языком без лишнего жаргона. Все аббревиатуры (hit rate, latency, LRU, LFU, FIFO, TTL, PII, NX) расшифрованы по-русски при первом упоминании. Примеры кода (PHP, bash, JSON) — учебные и показывают идею, а не готовый прод-модуль; конкретные плагины и версии внедрения опускаются сознательно, сверяйте поддержку по официальной документации Redis, Memcached и WordPress. Практика опирается на стек PHP 8.2+, MySQL и самостоятельный хостинг на VPS, который используется в этой серии.
👤 Зарегистрируйтесь, чтобы добавить материал в закладки.
