Оптимизация PHP приложений: кэширование по уникальному идентификатору

Раздел: Администрирование и оптимизация -> Оптимизация производительности

Кэширование по ID в PHP: методы и реализации

Кэширование данных по уникальному идентификатору (ID) позволяет существенно снизить нагрузку на базу данных и ускорить выполнение скриптов. Основная задача - сохранить результат дорогостоящих операций (например, запросов к БД, вычислений) и при повторном обращении с тем же ID отдать закэшированное значение. В статье рассмотрены разные подходы, от простых файловых хранилищ до профессиональных решений на Memcached или Redis.

Основное решение: Memcached с ключом по ID

Как реализовать высокопроизводительное кэширование по ID с автоматическим истечением?

Memcached - распределённая система кэширования в оперативной памяти. Она идеально подходит для сценариев, где требуется быстрый доступ к данным по ключу. Ключ формируется на основе ID и префикса для избежания коллизий. Время жизни (TTL) задаётся при сохранении.

$memcached = new Memcached();
$memcached->addServer('localhost', 11211);

$userId = 42;
$cacheKey = 'user_profile_' . $userId;

$data = $memcached->get($cacheKey);
if ($data === false) {
    // Данных нет в кэше - извлекаем из БД
    $data = fetchUserFromDatabase($userId);
    // Сохраняем на 3600 секунд (1 час)
    $memcached->set($cacheKey, $data, 3600);
}
// Используем $data

Cache php id (кэширование по id в php)

Ключевые моменты: проверка на false - Memcached возвращает false, если ключ не найден или истёк. TTL - время, через которое данные удаляются автоматически.

Возможные проблемы

  • При переполнении памяти Memcached может вытеснять старые ключи (LRU). Это нормально, но стоит увеличить память сервера.
  • Отсутствие сериализации сложных типов - Memcached автоматически сериализует объекты/массивы через PHP-сериализатор, что может быть медленным при больших объёмах. Рекомендуется хранить простые строки JSON.
  • Потеря соединения - код должен обрабатывать исключения и при недоступности кэша падать на БД.

Вариант 1: Файловое кэширование

Как организовать кэширование по ID без внешних сервисов?

Файловое кэширование - хранить сериализованные данные в файлах, название которых строится на основе ID. Подходит для небольших проектов или когда нет доступа к Memcached/Redis. Критично правильно настроить истечение и очистку.

$cacheDir = '/tmp/cache/';
$userId = 42;
$cacheFile = $cacheDir . 'user_' . $userId . '.cache';

if (file_exists($cacheFile) && (filemtime($cacheFile) + 3600) > time()) {
    $data = unserialize(file_get_contents($cacheFile));
} else {
    $data = fetchUserFromDatabase($userId);
    file_put_contents($cacheFile, serialize($data));
}

Php http cache (http-кэширование в php)

Типичные ошибки

  • Конкуренция запросов - два запроса одновременно могут записывать файл. Решение: блокировка flock.
  • Заполнение диска - нужно регулярно удалять старые файлы (cron или проверка при каждом запросе).
  • Медленный I/O при большом количестве файлов - лучше использовать хеширование поддиректорий.

Вариант 2: Кэширование в массиве (время жизни запроса)

Как избежать повторных запросов к БД в пределах одного HTTP-запроса?

Самый простой способ - статический массив, где ключом выступает ID. Данные существуют только пока выполняется скрипт. Полезно для многократных обращений к одной и той же сущности.

$localCache = [];

function getCachedUser($userId) {
    global $localCache;
    if (isset($localCache[$userId])) {
        return $localCache[$userId];
    }
    $data = fetchUserFromDatabase($userId);
    $localCache[$userId] = $data;
    return $data;
}

Cache index php (кэширование индекса в php)

Проблемы: данные не сохраняются между запросами, при большом количестве разных ID память может расти, но в рамках одного запроса это обычно безопасно.

Вариант 3: APCu (Alternative PHP Cache user cache)

Как получить быстрый общий кэш между процессами без установки отдельного сервера?

APCu хранит данные в разделяемой памяти PHP. Подходит для одностороннего кэширования (без сетевых задержек). Ключи тоже формируются на основе ID.

$userId = 42;
$cacheKey = 'user_' . $userId;

if (apcu_exists($cacheKey)) {
    $data = apcu_fetch($cacheKey);
} else {
    $data = fetchUserFromDatabase($userId);
    apcu_store($cacheKey, $data, 3600);
}

Ограничения

  • APCu работает только в CLI-FPM (не для многопоточных сред).
  • Ограниченный объём памяти, выделяемый для APCu.
  • При перезагрузке PHP-FPM кэш очищается.

Выбор метода зависит от масштаба проекта, требований к скорости и доступности инфраструктуры. Для высоконагруженных проектов рекомендуется Memcached или Redis.

- Bitrix php cache (кэширование в bitrix (php))

Расширенные примеры кэширования по ID

Пример с Redis и групповой инвалидацией по тегам

Как удалить кэш для всех ID, относящихся к одному типу (например, все пользователи из группы)?

Redis не поддерживает теги напрямую, но можно хранить отдельные множества (set) со списком ключей.

Пример
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$groupId = 5;
$userIds = [10, 20, 30];

// Сохраняем профиль каждого пользователя и добавляем ключ в множество группы
foreach ($userIds as $userId) {
    $cacheKey = 'user:' . $userId;
    $data = fetchUserFromDatabase($userId);
    $redis->setex($cacheKey, 3600, serialize($data));
    // Добавляем ключ в множество 'group:users:' . $groupId
    $redis->sAdd('group:users:' . $groupId, $cacheKey);
}

// Когда нужно очистить кэш для всей группы:
$keys = $redis->sMembers('group:users:' . $groupId);
if ($keys) {
    $redis->del($keys);
    $redis->del('group:users:' . $groupId);
}
Результат: все ключи, соответствующие группе, удаляются одной командой.

Пример с блокировкой (mutex) при перестроении кэша

Как предотвратить многократное перестроение одного и того же кэша при конкурентных запросах?

Используется «blocking cache»: первый запрос блокирует ключ, остальные ждут или получают устаревшие данные.

Пример
$memcached = new Memcached();
$memcached->addServer('localhost', 11211);

$cacheKey = 'heavy_data_' . $itemId;
$lockKey = $cacheKey . '_lock';

$data = $memcached->get($cacheKey);
if ($data === false) {
    // Пытаемся захватить блокировку на 5 секунд
    if ($memcached->add($lockKey, 1, 5)) {
        // Мы владеем блокировкой – генерируем данные
        $data = computeExpensiveData($itemId);
        $memcached->set($cacheKey, $data, 600);
        $memcached->delete($lockKey);
    } else {
        // Другой процесс генерирует – ждём небольшой интервал
        usleep(50000); // 50 мс
        // Пробуем снова (или отдаём устаревшие данные)
        $data = $memcached->get($cacheKey);
        if ($data === false) {
            // экстренный случай – генерация без блокировки
            $data = computeExpensiveData($itemId);
        }
    }
}
Результат: только один процесс выполняет тяжёлую работу, остальные получают данные из кэша или ждут.

Пример с PSR-6 (Symfony Cache)

Как использовать стандартизированный интерфейс кэша для разных адаптеров?

Symfony Cache предоставляет абстракцию над файловым, APCu, Redis и т.д.

Пример
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$cache = new FilesystemAdapter('app.cache', 3600, '/path/to/cache');

$userId = 42;
$cacheKey = 'user_' . $userId;

$cacheItem = $cache->getItem($cacheKey);
if (!$cacheItem->isHit()) {
    $data = fetchUserFromDatabase($userId);
    $cacheItem->set($data);
    $cache->save($cacheItem);
} else {
    $data = $cacheItem->get();
}
Код не зависит от конкретного хранилища – легко переключиться на Redis, просто заменив адаптер.

Кэширование по ID в PHP - comments

En
Cache php id (php)