APCu — это встроенный в PHP модуль кэширования пользовательских данных в общей памяти. Он обеспечивает микросекундный доступ и идеально подходит для кэширования результатов вычислений, сериализованных структур (массивов, DTO), небольших фрагментов конфигурации и метаданных. В отличие от OPcache, который хранит байткод, APCu хранит данные приложения. Их synergy даёт существенный прирост производительности: OPcache экономит на компиляции, APCu — на повторных вычислениях и запросах к внешним ресурсам.
Когда APCu уместен, а когда лучше воздержаться
APCu — локальный кэш узла: память общая для PHP-процессов в пределах одной инсталляции (обычно один мастер php-fpm и его воркеры). Он не распределённый, не шарится между серверами и не переживает рестарт процесса. Это делает его идеальным для:
- Кэширования «горячих» вычислений и небольших наборов справочных данных.
- Кэширования редко меняющихся результатов запросов к БД или API.
- Негативного кэширования (ошибки/пустые результаты с малым TTL).
- Счётчиков и атомарных инкрементов (ограниченно и без жёстких SLA).
Избегайте APCu, если вам нужен:
- Распределённый кэш между несколькими узлами (лучше смотреть в сторону Redis/Memcached).
- Кэш больших объектов (десятки мегабайт) — это быстро фрагментирует память сегмента.
- Жизненно важный персистентный storage — APCu волатилен, очистится при рестарте.
Если нужен распределённый кэш, посмотрите сравнение Redis и Memcached для PHP-кэша.
Базовое правило: APCu — сверхбыстрый кэш для одного узла. Если у вас несколько узлов за балансировщиком без sticky-сессий, используйте его для вычислений и метаданных, но не как источник истины.
Установка и базовая настройка
На большинстве дистрибутивов ставится через пакетный менеджер. После установки проверьте, что модуль загружен, и отрегулируйте объём общей памяти и TTL. На типичных тарифах виртуальный хостинг и на собственном VDS APCu и OPcache обычно доступны и легко настраиваются.
# Debian/Ubuntu
sudo apt-get update
sudo apt-get install php-apcu
# AlmaLinux/Rocky/Oracle (в зависимости от репозиториев)
sudo dnf install php-pecl-apcu
# Проверка
php -m | grep -i apcu
php -i | grep -i apcu
Минимальная настройка в php.ini (или в отдельном apcu.ini):
; Включить APCu для FPM/Apache
apc.enabled=1
; Объём сегмента общей памяти (подбирайте по нагрузке)
apc.shm_size=128M
; Глобальный TTL по умолчанию для ключей без индивидуального TTL
apc.ttl=0
; Время жизни удалённых объектов перед сборкой (смягчает фрагментацию)
apc.gc_ttl=3600
; Для CLI обычно выключено, чтобы не плодить отдельные кэши
apc.enable_cli=0
; Если используете igbinary, это может уменьшить размер сериализации
; apc.serializer=igbinary
Важно: включение APCu в CLI создаст отдельную область кэша и не поможет «прогреть» кэш для php-fpm. Для прогрева делайте HTTP-запросы к самому приложению либо вызывайте код через FPM-пул.

Быстрый старт: API APCu
Чаще всего хватает следующих функций: apcu_store, apcu_fetch, apcu_add, apcu_delete, apcu_exists, apcu_inc/apcu_dec, а также удобной обёртки apcu_entry.
<?php
// Кэш-асайд: сначала пробуем взять из кэша, иначе считаем и кладём
function cache_get_or_compute(string $key, callable $compute, int $ttl = 300) {
$success = false;
$value = apcu_fetch($key, $success);
if ($success) {
return $value;
}
$value = $compute();
apcu_store($key, $value, $ttl);
return $value;
}
// Пример использования
$userCount = cache_get_or_compute('stats:user_count', function () {
// тяжёлый запрос
return (int) db_query_scalar('SELECT COUNT(*) FROM users');
}, 600);
apcu_entry (если доступна) упрощает атомарное получение или вычисление значения:
<?php
$result = apcu_entry('catalog:popular:page1', function () {
return build_popular_items_page1();
}, 120);
TTL-стратегии: абсолютный, «мягкий» и с джиттером
Абсолютный TTL
Самый простой вариант — задавать TTL для каждого ключа. Например, справочники на 1 час, статистики на 5 минут, негативные результаты на 10–30 секунд. Плюсы: предсказуемо. Минусы: в момент истечения сразу все запросы одновременно начнут перестраивать кэш (stampede).
«Мягкий» TTL и «твёрдый» TTL
APCu сам по себе не поддерживает «скользящий» TTL или хранение просроченного значения, но это легко сделать на уровне приложения, сохранив вместе с данными метаданные и проверяя их при чтении. Идея:
- Хранить структуру вида:
['payload' => ... , 'ts' => time(), 'soft_ttl' => 30, 'hard_ttl' => 300]. - Если не истёк
hard_ttl, можно отдавать «устаревшее» значение доsoft_ttlи параллельно инициировать асинхронное обновление.
<?php
function cache_soft_hard(string $key, callable $compute, int $softTtl, int $hardTtl) {
$ok = false;
$entry = apcu_fetch($key, $ok);
$now = time();
if ($ok && isset($entry['payload'], $entry['ts'])) {
$age = $now - $entry['ts'];
if ($age <= $softTtl) {
return $entry['payload'];
}
if ($age <= $hardTtl) {
// Отдаём старое, но запускаем обновление в фоне (по месту — без блокировок)
// В проде используйте очередь/фоновый воркер; здесь синхронно с блокировкой
if (apcu_add('lock:' . $key, 1, 5)) {
try {
$payload = $compute();
apcu_store($key, ['payload' => $payload, 'ts' => $now], $hardTtl);
} finally {
apcu_delete('lock:' . $key);
}
}
return $entry['payload'];
}
}
$payload = $compute();
apcu_store($key, ['payload' => $payload, 'ts' => $now], $hardTtl);
return $payload;
}
Так вы снижаете «ступеньку холодного старта» и защищаетесь от массовой регенерации при истечении TTL.
Джиттер (рандомизация TTL)
Чтобы не истекали тысячи ключей одновременно, используйте джиттер: прибавляйте к TTL случайное число в пределах 5–20%. Это распределяет инвалидации во времени.
<?php
function with_jitter(int $ttl, float $ratio = 0.1): int {
$delta = (int) floor($ttl * $ratio);
return $ttl + random_int(-$delta, $delta);
}
Безопасная инвалидация: ключи, namespace и atomics
Прямая инвалидация ключей
Самый очевидный способ — apcu_delete($key). Он подходит, когда вы точно знаете, какие ключи затронуты. Минус — сложно, если у вас много производных ключей и шаблонов.
Версионирование namespace (namespaced keys)
Паттерн для массовой инвалидации: держите «версию» пространства имен и включайте её в ключ. При изменении данных просто увеличивайте версию — все старые ключи станут «невидимы».
<?php
function ns_key(string $ns, string $key): string {
$v = apcu_fetch('ns:' . $ns);
if ($v === false) {
$v = 1;
apcu_add('ns:' . $ns, $v);
}
return 'v' . $v . ':' . $ns . ':' . $key;
}
function ns_bump(string $ns): int {
// Атомарное увеличение версии namespace
$ok = false;
$v = apcu_fetch('ns:' . $ns, $ok);
if (!$ok) {
apcu_add('ns:' . $ns, 1);
return 1;
}
return apcu_inc('ns:' . $ns);
}
// Использование
$key = ns_key('catalog', 'popular:page1');
$value = cache_get_or_compute($key, fn() => build_popular_items_page1(), 300);
// Массовая инвалидация каталога
ns_bump('catalog');
Плюсы: O(1) по времени, нет перебора ключей. Минусы: старые объекты висят в памяти до истечения их TTL или until eviction.
Замки и защита от cache stampede
Используйте лёгкий «лок» на ключ через apcu_add('lock:'.$key, 1, 5), чтобы единственный процесс делал тяжёлую регенерацию, остальные отдавали старое или ждали короткое время.
<?php
function compute_with_lock(string $key, callable $compute, int $ttl = 300, int $lockTtl = 5) {
$ok = false;
$val = apcu_fetch($key, $ok);
if ($ok) return $val;
if (!apcu_add('lock:' . $key, 1, $lockTtl)) {
// Кто-то уже считает. Подождать чуть-чуть и попробовать взять снова
usleep(200000);
$val = apcu_fetch($key, $ok);
if ($ok) return $val;
}
try {
$val = $compute();
apcu_store($key, $val, $ttl);
return $val;
} finally {
apcu_delete('lock:' . $key);
}
}
CAS и счётчики
Для условных обновлений пригодится apcu_cas (compare-and-swap). Он позволяет обновить значение, если оно равно ожидаемому, что полезно для примитивных очередей/флагов. Для счётчиков используйте apcu_inc/apcu_dec.

Проектирование ключей и размер объектов
- Используйте короткие предсказуемые префиксы:
app:ns:key. Не увлекайтесь длинными строками. - Если ключ составной (много параметров), применяйте хэш:
hash('xxh3', json_encode($params))и храните короткий хэш в ключе. - Следите за размером значений: старайтесь держать один объект до 1–2 МБ. Большие значения повышают риск фрагментации и вытеснений.
- Для PHP-объектов оцените переход к массивам или сериализаторам (например, igbinary) — часто это экономит память.
Настройка памяти и фрагментация
apc.shm_size — ключевой параметр. Начните со 128–256 МБ для среднего проекта и замерьте метрики. Если наблюдаются частые вытеснения и высокий miss rate, увеличивайте сегмент. apc.gc_ttl помогает сборщику реже трогать только что удалённые блоки, смягчая фрагментацию.
Следите за статистикой:
<?php
$cache = apcu_cache_info(true);
$sma = apcu_sma_info(true);
// $cache['num_hits'], $cache['num_misses'], $cache['expunges']
// $sma['avail_mem'], $sma['num_seg'] и т.д.
Признаки проблем: растущий expunges, высокий miss rate при стабильном трафике, быстрый рост фрагментации и снижение avail_mem при умеренных объёмах данных.
Совместная работа с PHP-FPM и CLI
В пределах одного php-fpm мастера APCu кэш общий для воркеров. Если у вас несколько версий PHP, отдельных мастеров или строгая изоляция, кэш будет разделён по инсталляциям. Помните:
- CLI-процессы обычно используют отдельный кэш или вообще отключённый APCu (
apc.enable_cli=0). Не рассчитывайте на них для прогрева. - Не храните в APCu чувствительные данные — память общая для всех воркеров данной инстанции PHP, и ошибки изоляции могут привести к утечкам между пулами.
- При деплое и рестарте php-fpm кэш обнуляется. Заложите это в стратегию прогрева и TTL.
Synergy с OPcache
OPcache экономит на парсинге и компиляции PHP-кода, APCu — на данных. В связке это даёт кратный рост производительности при типовой нагрузке «много небольших PHP-запросов». Рекомендации:
- Включите OPcache и настройте разумные лимиты, чтобы скрипты не вытеснялись.
- Используйте APCu для объектов, зависящих от БД или внешних API, и для результатов тяжёлых функций.
- Добавьте джиттер/soft TTL, чтобы избежать синхронного «обвала» кэша.
- Если используете Composer, рассмотрите опцию кэша автозагрузчика (Composer умеет использовать APCu для map).
Для кэширования фрагментов на уровне веб-сервера посмотрите технику SSI и сабреквестов в материале кэширование через Nginx SSI и subrequest.
Негативное кэширование и дросселирование ошибок
Если внешний API отвечает 404/empty, запишите «пустой» результат с коротким TTL (например, 15–60 секунд). Это снизит давление при массовых повторных запросах и защитит от лавинообразных ретраев. Важно отличать «пусто» от «ошибка»: на ошибки ставьте ещё более короткий TTL (например, 5–10 секунд) и логируйте событие.
Шаблон «мягкий фоллбек» при инвалидации
Когда меняете данные (например, админ обновил запись), используйте версионирование namespace и механизм «мягкого фоллбека»: сразу после инвалидации допускайте кратковременную отдачу старого значения (если оно закешировано под старой версией и у вас есть доступ к нему на уровне объекта), пока генератор не обновит кэш. Это снижает вероятность резких скачков latency.
Тестирование на проде без боли
- Включите детальные метрики APCu на админском endpoint (защищённом доступе).
- Добавьте логирование причин пересборки: «miss», «expired», «lock-timeout», «ns-bump».
- Внесите фиче-флаги TTL в конфиг, чтобы временно увеличивать/уменьшать TTL для отдельных namespaces без релиза.
Чек-лист внедрения APCu
- Оцените, где кэш даёт максимальную экономию CPU/IO: сложные джоины, агрегации, внешние API.
- Спроектируйте ключи: короткие, детерминированные, с namespace и версией схемы.
- Выберите стратегию TTL: абсолютный + джиттер, либо soft/hard TTL с фоновым обновлением.
- Реализуйте защиту от stampede: замки на ключ,
apcu_entry, «раннее обновление» перед истечением. - Настройте
apc.shm_size, следите заhits/misses/expunges, тестируйте под нагрузкой. - Продумайте инвалидацию: точечная и массовая через
ns_bump. Логируйте события. - Не храните секреты, не кладите в кэш большие объекты без крайней необходимости.
- Закройте админские инструменты статистики ACL-ами и не светите ключи наружу.
Тонкости производительности
- Сериализация стоит денег. Если данные читаются чаще, чем записываются, APCu окупается. Если наоборот — подумайте о другом слое (например, кэшировать уже отрендеренные блоки).
- Инкременты и счётчики в APCu быстры, но не предназначены для строгих лимитов с длительным хранением — лучше держать их в персистентном хранилище, а APCu использовать как слой ускорения.
- Старайтесь минимизировать контенцию на «горячих» ключах: используйте шардирование (несколько ключей с разными суффиксами) и агрегацию при чтении.
Отладка и типичные ошибки
- «В CLI всё работает, а в вебе пусто» — вы включили APCu в CLI и считаете, что прогрели кэш. На самом деле это отдельный кэш. Прогревайте через веб-приложение.
- «После деплоя всплеск латентности» — пересобирается кэш. Используйте soft TTL, джиттер и/или последовательный прогрев.
- «Ключи пропадают» — сегмент мал, вытеснение/фрагментация. Увеличьте
apc.shm_size, уменьшите размер объектов, настройте TTL. - «Утечки между пулами» — оцените модель изоляции php-fpm. Для многопуловых инсталляций разных проектов используйте отдельные мастера/версии PHP.
Итоги
APCu — один из самых недооценённых инструментов оптимизации PHP. При правильном проектировании ключей, продуманной стратегии TTL и безопасной инвалидации он обеспечивает значимый прирост производительности без внешних зависимостей. В связке с OPcache это превращает ваш PHP-стек в эффективную систему для высоконагруженных сайтов и API. Начните с небольших участков кода, добавьте защиту от stampede, настройте мониторинг — и вы быстро увидите результат по метрикам CPU, латентности и пропускной способности.


