Redis: когда база данных — это кэш
Redis не БД
Redis — in-memory хранилище. Может использоваться как БД, но обычно — как кэш.
Ключевое:
- Данные в RAM.
- Persistence — опционально.
- Потеря данных при сбое — приемлемо для кэша.
- Микросекундные операции.
Правило: Redis — для данных, которые можно пересчитать.
Когда Redis как кэш
Классические сценарии:
- Результаты тяжёлых SQL.
- Внешние API.
- Настройки, справочники.
- HTML-фрагменты.
- Сессии.
- Rate limiting.
- Счётчики.
- Очереди.
Cache-Aside
Основной паттерн:
$user = Cache::remember("user:{$id}", 3600, function () use ($id) {
return User::findOrFail($id);
});
Что происходит:
- Проверяем Redis.
- Есть — отдаём.
- Нет — идём в БД.
- Кладём в Redis.
- Отдаём.
Инвалидация
При записи:
$user->update($data);
Cache::forget("user:{$user->id}");
Теги:
Cache::tags(['users'])->put("user:{$id}", $user, 3600);
Cache::tags(['users'])->flush();
Версионирование:
$version = Cache::get('users:version', 1);
$key = "user:{$id}:v{$version}";
// инвалидация
Cache::increment('users:version');
Правило: TTL + явная инвалидация при записи.
TTL — обязательно
Cache::put('key', $value, 3600);
Почему TTL:
- Автоочистка.
- Ограничение устаревших.
- RAM не уходит.
Правило: всегда TTL. Исключения — справочники с явной инвалидацией.
Что кэшировать
Хорошо:
- Тяжёлые запросы с JOIN.
- Внешние API.
- Справочники.
- Топ-10 товаров.
- Профиль пользователя.
- Настройки.
Плохо:
- Данные, которые меняются каждую секунду.
- Персонально чувствительные данные (пароли, токены).
- Много уникальных ключей (не будут попадать в cache).
- Транзакционные данные.
Что НЕ кэшировать
Деньги:
Баланс в Redis — рассинхрон
Транзакции:
Заказ в Redis → потеря при рестарте
Данные с частой записью:
Постоянная инвалидация — нет смысла в кэше
Правило: кэш — для данных, которые часто читают, редко пишут.
Кэш на уровне приложения
Laravel:
Cache::remember('users:list', 3600, fn () => User::all());
Cache::forget('users:list');
Cache::tags(['users'])->flush();
Node.js:
async function getUser(id) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.users.find(id);
await redis.setex(`user:${id}`, 3600, JSON.stringify(user));
return user;
}
Python:
import json
import redis
r = redis.Redis()
cached = r.get(f'user:{user_id}')
if cached:
return json.loads(cached)
user = db.users.find(user_id)
r.setex(f'user:{user_id}', 3600, json.dumps(user))
return user
Сессии
Идея: сессия в Redis вместо файлов/БД.
Laravel:
SESSION_DRIVER=redis
Node.js (express-session):
import session from 'express-session';
import RedisStore from 'connect-redis';
app.use(session({
store: new RedisStore({ client: redis }),
secret: 'xxx',
resave: false,
saveUninitialized: false,
cookie: { maxAge: 86400 * 1000 },
}));
Что даёт:
- Быстрое чтение.
- Общие сессии между инстансами.
- Автоочистка по TTL.
Очереди
Redis — драйвер для Laravel Queue, BullMQ.
Laravel:
QUEUE_CONNECTION=redis
Node.js (BullMQ):
import { Queue, Worker } from 'bullmq';
const queue = new Queue('emails', { connection: redis });
await queue.add('welcome', { userId: 1 });
new Worker('emails', async (job) => {
await sendEmail(job.data);
}, { connection: redis });
Что важно:
- Persistence — иначе задачи теряются при рестарте.
- AOF для надёжности.
- Horizon (Laravel) или Bull Board для UI.
Rate limiting
Простой счётчик:
INCR rate:user:5
EXPIRE rate:user:5 60
Sliding window:
ZADD rate:user:5 <timestamp> <request_id>
ZREMRANGEBYSCORE rate:user:5 0 <timestamp - window>
ZCARD rate:user:5
Laravel:
RateLimiter::for('api', fn ($request) =>
Limit::perMinute(60)->by($request->user()?->id)
);
Distributed locks
Простой lock:
SET lock:job:5 abc NX EX 30
Что даёт: только один инстанс возьмёт lock.
Laravel:
Cache::lock('job:' . $id, 30)->get(function () {
// критическая секция
});
Node.js (redlock):
import Redlock from 'redlock';
const redlock = new Redlock([redis], { retryCount: 0 });
const lock = await redlock.acquire(['lock:job:5'], 30000);
try {
// критическая секция
} finally {
await lock.release();
}
Счётчики и лимиты
Просмотры:
INCR post:5:views
Онлайн-пользователи:
SADD online:users user5
SREM online:users user5
SCARD online:users
Реакции:
HSET post:5:reactions likes 10 hearts 5
HINCRBY post:5:reactions likes 1
Что даёт: быстрые счётчики без записи в БД. Периодически — sync в БД.
Pub/Sub
PUBLISH notifications '{"userId": 5, "msg": "hi"}'
SUBSCRIBE notifications
Что даёт: real-time сообщения.
Пример — Laravel → Node.js:
Redis::publish('app:events', json_encode([
'channel' => "user:{$userId}",
'event' => 'order.created',
'data' => $data,
]));
sub.subscribe('app:events');
sub.on('message', (ch, msg) => {
const { channel, event, data } = JSON.parse(msg);
io.to(channel).emit(event, data);
});
Что даёт: мост между сервисами без прямой связи.
Ошибка Redis
Проблема: Redis упал. Приложение падает без кэша?
Решение: graceful degradation.
try {
$user = Cache::remember('user:' . $id, 3600, fn () => User::find($id));
} catch (\Throwable $e) {
// Redis недоступен — читаем напрямую
Log::warning('Cache unavailable', ['error' => $e->getMessage()]);
$user = User::find($id);
}
Правило: кэш не должен ломать приложение при сбое.
Метрики
INFO stats
Что смотреть:
keyspace_hits,keyspace_misses— hit rate.evicted_keys— вытеснение.used_memory— RAM.connected_clients.instantaneous_ops_per_sec.
Hit rate: > 90%.
Persistence
RDB — snapshot:
save 900 1
save 300 10
save 60 10000
AOF — append-only file:
appendonly yes
appendfsync everysec
Правило:
- Чистый кэш — persistence не нужен.
- Сессии, очереди — AOF.
- Критичные данные — Redis не подходит как primary.
HA и Cluster
Sentinel:
sentinel monitor mymaster 10.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
Cluster:
redis-cli --cluster create host1:6379 host2:6379 host3:6379
Правило:
- Кэш — обычный Redis с Sentinel.
- Большие данные — Cluster.
Maxmemory
maxmemory 2gb
maxmemory-policy allkeys-lru
Для кэша: allkeys-lru — вытесняет давно неиспользуемые.
Для очередей: noeviction — не терять задачи.
Правило: разные инстансы для кэша и очередей, чтобы не вытесняли друг друга.
Безопасность
Что делать:
requirepass— пароль.bind 127.0.0.1— только локально.- Firewall — не открывать 6379.
rename-commandдля опасных команд.
⚠️ Публичный Redis — массово взламывают (майнеры, шифрование).
Когда Redis НЕ кэш
Иногда Redis — primary:
- Счётчики просмотров — можно жить с потерей.
- Онлайн-статусы — пересчитываются.
- Leaderboards — можно пересчитать.
- Очереди — Redis как брокер.
- Pub/Sub — real-time.
Правило: если данные можно восстановить из основного источника — Redis ок как primary для них.
Частые ошибки
1. Redis как единственная БД:
Рестарт → потеря
Решение: Postgres для источника правды.
2. Кэш без TTL:
RAM кончается
Решение: всегда TTL.
3. Кэш без инвалидации:
Старые данные
Решение: forget при записи.
4. KEYS * в проде:
Блокирует Redis
Решение: SCAN.
5. Публичный Redis:
Взлом
Решение: firewall + пароль.
6. Redis для очередей без AOF:
Задачи теряются
Решение: AOF.
7. Кэш ломает приложение:
Redis недоступен → 500
Решение: graceful degradation.
8. Один Redis для всего:
Кэш вытесняет задачи
Решение: разные инстансы / БД.
Практика
1. Настройте Redis для Laravel/Node.
2. Кэшируйте запрос к БД — замерьте разницу.
3. Настройте TTL и инвалидацию.
4. Rate limiting через INCR+EXPIRE.
5. Distributed lock через SET NX EX.
6. Сессии в Redis.
7. Очередь в Redis.
8. Метрики hit rate.
Итог
- Redis — кэш, сессии, очереди, счётчики, locks, pub/sub.
- Cache-Aside — основной паттерн.
- TTL + инвалидация — обязательно.
- Persistence — для очередей/сессий, не для кэша.
- HA — Sentinel.
- Maxmemory —
allkeys-lruдля кэша. - Безопасность — firewall, пароль.
- Не primary для критичных данных.
- Graceful degradation — обязательно.
- Метрики — hit rate > 90%.
Дальше — кэширование: стратегии и паттерны.