Кеширование в PHP: opcache, APCu и Redis без путаницы
Три слоя кеша в PHP решают три разные задачи, и подмена одного другим бьёт по производительности. Разбираем, что кешировать в opcache, что в APCu, а что выносить в Redis.

Разработчики часто валят opcache, APCu и Redis в одну кучу «PHP-кеш» и ждут, что включение любого из них ускорит приложение. На практике это три инструмента для трёх разных уровней: opcache хранит скомпилированный байткод скриптов, APCu — данные в памяти одного процесса PHP-FPM, Redis — данные, общие для всех серверов приложения. Поставить не тот слой не на своё место — значит либо не получить эффекта, либо потерять данные при масштабировании. Разбираем на PHP 8.3, где что уместно и чем платишь за каждый выбор.
Формат — практика: вы уже пишете на PHP и упёрлись в вопрос, какой кеш куда. Установку и базовые команды не разжёвываем.
Три слоя и что они на самом деле кешируют
Ошибка в том, что все три называют «кешем», хотя предмет хранения у них разный.
- opcache — кеш скомпилированного байткода. PHP при каждом запросе парсит и компилирует ваши
.php-файлы. opcache сохраняет результат компиляции в разделяемой памяти, чтобы не делать это заново. Вы им не управляете из кода — он работает прозрачно. - APCu — кеш пользовательских данных в разделяемой памяти одного инстанса PHP. Быстрый, но живёт внутри одного сервера: два разных FPM-пула или две машины APCu не разделяют.
- Redis — отдельный сетевой сервер хранения ключ-значение. Данные общие для всех процессов и всех серверов приложения. Платите за это сетевым раундтрипом и сериализацией.
opcache ускоряет сам PHP. APCu и Redis ускоряют ваше приложение. Это разные задачи, и первый не заменяет двух других.
opcache: почти всегда нужен, почти не требует кода
opcache входит в состав PHP и включается директивами в php.ini. Для веб-приложения на PHP 8.x он должен быть включён всегда — это единственный слой, дающий эффект без единой строки в коде. Типичная база настроек для прода:
; php.ini, PHP 8.3
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.jit=tracing
opcache.jit_buffer_size=100MКлючевой параметр — opcache.validate_timestamps. При значении 1 PHP на каждый запрос проверяет, не изменился ли файл на диске, и при изменении перекомпилирует. Удобно в разработке, лишняя работа в проде. При 0 проверок нет, но после деплоя нужно вручную сбросить opcache — иначе крутится старый байткод.
JIT (opcache.jit) в PHP 8.x даёт заметный выигрыш на вычислительных задачах, но почти никакого — на типичных веб-приложениях, где время съедают запросы к БД и сети, а не арифметика. Не включайте JIT «на всякий случай» — сначала измерьте, где узкое место.
APCu: локальный кеш без сети
APCu ставится отдельным расширением (пакет php-apcu в большинстве дистрибутивов). API минимальный:
<?php
// PHP 8.3, расширение apcu
$key = 'user_settings_42';
$settings = apcu_fetch($key, $success);
if ($success === false) {
// промах кеша — берём из БД
$settings = загрузитьИзБД(42);
// TTL в секундах, третий аргумент
apcu_store($key, $settings, 300);
}
// удаление по ключу
apcu_delete($key);Плюс — скорость: данные в локальной памяти, без сериализации по сети. Минус — они живут только в этом процессе PHP-FPM на этой машине. Как только вы запускаете второй сервер приложения за балансировщиком, APCu на них рассинхронизированы. Ещё один нюанс: перезапуск FPM или opcache_reset() при определённых конфигурациях чистит и APCu — не храните там ничего, что нельзя пересобрать.
Redis: общий кеш и не только
Redis нужен там, где данные должны быть видны всем инстансам: сессии, очереди задач, счётчики, кеш, переживающий рестарт приложения. Работа через расширение phpredis либо через клиент вроде Predis. Пример на phpredis:
<?php
// PHP 8.3, расширение phpredis
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'product:1001';
$cached = $redis->get($key);
if ($cached === false) {
$product = загрузитьТовар(1001);
// сериализуем сами, TTL через setex
$redis->setex($key, 600, json_encode($product));
} else {
$product = json_decode($cached, true);
}Redis платит сетевым раундтрипом (даже на localhost это микросекунды против наносекунд APCu) и необходимостью сериализации. Взамен даёт общий кеш, персистентность и структуры данных сложнее ключ-значение — списки, множества, sorted sets.
Сравнение слоёв
| Параметр | opcache | APCu | Redis |
|---|---|---|---|
| Что хранит | Байткод скриптов | Данные приложения | Данные приложения |
| Область видимости | Один процесс PHP | Один сервер | Все серверы |
| Управление из кода | Нет (прозрачно) | Да | Да |
| Скорость доступа | — | Наносекунды | Микросекунды + сеть |
| Переживает рестарт FPM | Нет | Нет | Да |
| Нужен отдельный сервис | Нет | Нет | Да |
Когда что выбирать
Критерий простой — задайте два вопроса: нужно ли делиться данными между серверами и переживать ли им рестарт.
- opcache — включайте всегда на проде. Вопрос не «нужен ли», а «правильно ли настроен
validate_timestampsи сброс при деплое». - APCu — когда данные локальны и легко пересобираются: мемоизация тяжёлого вычисления в пределах одного сервера, кеш конфигурации, справочники. Один сервер или данные, которые не страшно иметь рассинхронизированными.
- Redis — когда данные общие для кластера или должны жить дольше процесса: сессии за балансировщиком, кеш каталога, счётчики, распределённые блокировки.
Частая рабочая связка — двухуровневый кеш: сначала проверяем APCu (быстро, локально), при промахе идём в Redis (медленнее, но общий), при промахе там — в БД. Так горячие данные отдаются из локальной памяти, а согласованность держит Redis.
Что ломается на практике
- Старый байткод после деплоя. С
validate_timestamps=0без явного сброса opcache сервер отдаёт код прошлой версии. Решается вызовомopcache_reset()или перезапуском FPM в деплой-скрипте. Проверяйте, что сброс реально отрабатывает — через FPM он выполняется в контексте своего процесса. - APCu на кластере. Сохранили инвалидацию флага в APCu, а на втором сервере флаг всё ещё старый. Пользователь видит разное поведение в зависимости от того, куда его закинул балансировщик. Для общих данных — только Redis.
- Переполнение памяти APCu. При нехватке места APCu может очистить весь кеш целиком, а не вытеснить по LRU. Резкие провалы hit rate — повод проверить лимит памяти расширения.
- Отсутствие TTL или разрушение при рестарте. APCu не переживает перезапуск FPM. Если код полагается на «данные там точно есть» — он упадёт или начнёт бить в БД лавиной запросов сразу после рестарта.
- Redis как единственная копия истины. Кеш — не хранилище. Настраивайте persistence осознанно, но не полагайтесь на Redis как на замену БД для данных, которые нельзя потерять.
Что делать дальше
Начните с измерения, а не с включения всего подряд. Снимите opcache hit rate через opcache_get_status() — если он ниже 99% на проде, проверьте max_accelerated_files и память. Затем найдите в приложении места, куда бьёте в БД по одним и тем же данным, и решите по критерию выше: локальные — в APCu, общие — в Redis. JIT и двухуровневый кеш — следующий шаг, только после того как базовые слои настроены и измерены.
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: PHP Manual — OPcache, PHP Manual — APCu
Частые вопросы
Нужен ли APCu, если уже есть Redis?
У меня другая версия PHP, например 7.4 — приёмы применимы?
После деплоя сайт отдаёт старый код — почему?
opcache сам по себе ускоряет запросы к базе?
Что будет, если APCu переполнится в проде?
Можно ли хранить сессии в APCu вместо Redis?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.