Настройка Redis для кэширования данных веб-приложения
Redis как кэш работает по простому принципу: читать из Redis на порядок быстрее, чем из PostgreSQL или вычислять заново. Latency Redis — 0.1–1 мс против 5–50 мс для простого SQL запроса и 100–500 мс для тяжёлого. Задача не в том, чтобы кэшировать всё, а в том, чтобы кэшировать правильно: знать, что кэшировать, на сколько, и как инвалидировать.
Установка и базовая конфигурация
apt install redis-server
/etc/redis/redis.conf — ключевые параметры:
# Привязка только к localhost, если Redis на том же сервере
bind 127.0.0.1
# Пароль (обязательно для продакшена)
requirepass YourStrongRedisPassword
# Максимальный объём памяти
maxmemory 2gb
# Политика вытеснения при достижении лимита
maxmemory-policy allkeys-lru
# Персистентность: для чистого кэша можно отключить
save ""
appendonly no
# Количество баз данных
databases 16
# Таймаут клиентского соединения
timeout 300
# TCP keepalive
tcp-keepalive 300
maxmemory-policy:
-
allkeys-lru— выгружать давно неиспользуемые ключи из всей БД. Лучший выбор для чистого кэша. -
volatile-lru— выгружать только ключи с TTL. Если набор ключей без TTL, они никогда не выгоняются. -
allkeys-lfu— Least Frequently Used — лучше LRU для Zipf-распределения (маленькая часть ключей используется чаще всего). -
noeviction— при исчерпании памяти возвращать ошибку. Для не-кэша (очереди, сессии).
Базовые паттерны кэширования
Cache-Aside (Lazy Loading) — приложение само управляет кэшем: сначала читает из Redis, при промахе — из базы, кладёт в Redis.
PHP пример:
class ProductRepository
{
private const CACHE_TTL = 3600; // 1 час
public function findById(int $id): ?Product
{
$cacheKey = "product:{$id}";
$cached = $this->redis->get($cacheKey);
if ($cached !== false) {
return unserialize($cached);
}
$product = $this->db->find(Product::class, $id);
if ($product) {
$this->redis->setex($cacheKey, self::CACHE_TTL, serialize($product));
}
return $product;
}
public function save(Product $product): void
{
$this->db->persist($product);
$this->db->flush();
// Инвалидация кэша после сохранения
$this->redis->del("product:{$product->getId()}");
}
}
Write-Through — при записи в базу одновременно обновлять кэш. Плюс: кэш всегда актуален. Минус: запись медленнее, кэшируются данные, которые, возможно, не будут читаться.
Read-Through — кэш сам запрашивает данные из базы при промахе. Реализуется через Redis-модули или внешние библиотеки (например, RedisGears).
Laravel cache
Laravel поддерживает Redis как cache driver из коробки:
// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],
],
config/database.php:
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'), // phpredis быстрее predis
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', 'myapp_'),
],
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'), // Отдельная БД для кэша
],
],
Использование в коде:
// Кэш с автоматическим вычислением при промахе
$products = Cache::remember('products:featured', 3600, function () {
return Product::where('is_featured', true)->with('category')->get();
});
// Теги для групповой инвалидации
$product = Cache::tags(['products', 'category:5'])->remember(
"product:{$id}",
3600,
fn() => Product::find($id)
);
// Инвалидация всей группы
Cache::tags(['products'])->flush();
Теги Redis требуют redis или memcached драйвера — они не работают с file/database кэшем.
Кэширование запросов к базе
Для тяжёлых агрегационных запросов, которые редко меняются:
$stats = Cache::remember('dashboard:stats', 300, function () {
return DB::table('orders')
->selectRaw('
COUNT(*) as total_orders,
SUM(total_amount) as revenue,
AVG(total_amount) as avg_order
')
->whereDate('created_at', today())
->first();
});
5 минут TTL для статистики дашборда — разумный компромисс между актуальностью и нагрузкой.
Мониторинг кэша
# Статистика Redis
redis-cli -a password INFO stats | grep -E "keyspace_hits|keyspace_misses|used_memory_human"
# Hit rate = hits / (hits + misses)
# Хорошее значение: > 90%
# Активные ключи и TTL
redis-cli -a password SCAN 0 MATCH "product:*" COUNT 100
# Размер каждой группы ключей
redis-cli -a password --bigkeys
# Топ используемых команд
redis-cli -a password MONITOR # Осторожно: нагружает сервер, только для дебага
Для production мониторинга — Redis Exporter + Grafana:
docker run -d \
--name redis_exporter \
-p 9121:9121 \
oliver006/redis_exporter \
--redis.addr=redis://localhost:6379 \
--redis.password=YourPassword
Ключевые метрики Grafana дашборда (ID 11835): redis_keyspace_hits_total, redis_keyspace_misses_total, redis_memory_used_bytes, redis_connected_clients.
Сериализация данных
Serialized PHP объекты занимают больше места, чем JSON. Для простых структур — JSON быстрее и читаемее:
// Медленно и объёмно
$this->redis->set($key, serialize($object));
$object = unserialize($this->redis->get($key));
// Быстрее для простых DTO
$this->redis->set($key, json_encode($data));
$data = json_decode($this->redis->get($key), true);
// Для больших объектов — igbinary (расширение PHP)
$this->redis->set($key, igbinary_serialize($object));
$object = igbinary_unserialize($this->redis->get($key));
igbinary даёт сжатие ~50% по сравнению с serialize и работает быстрее.
Сроки
Базовая установка Redis с настройкой Laravel cache driver — 4–6 часов. Внедрение кэширования в конкретных контроллерах/репозиториях с правильной инвалидацией — 1–2 дня в зависимости от количества мест. Настройка мониторинга — полдня.







