Настройка Redis для кэширования веб-приложения
Redis — не просто кэш. Это структуры данных в памяти: строки, хэши, списки, множества, sorted sets, потоки, pub/sub. Правильно выбранная структура часто даёт более чистое решение, чем попытка сделать то же самое в реляционной базе. На практике мы сталкивались с проектами, где замена N+1 запросов на кэшированный хэш снижала время ответа с 2 секунд до 50 мс — прирост в 40 раз. При этом неправильная конфигурация Redis (например, отсутствие лимита памяти или неверная политика вытеснения) приводила к падению продакшена. Как этого избежать — расскажем ниже. Экономия бюджета на инфраструктуре может достигать 70% при правильной настройке.
Почему Redis быстрее классического кэширования в БД?
Кэширование в реляционной базе (например, MySQL MEMORY table) имеет ограничения по объёму, скорости и гибкости структур данных. Redis хранит данные в оперативной памяти с прямым доступом, поддерживает атомарные операции и сложные структуры (sorted sets для рейтингов, streams для очередей). В наших тестах Redis обрабатывает 100–200 тысяч операций чтения/записи в секунду на одном ядре — в 10–20 раз быстрее, чем кэширование через БД. Это достигается за счёт однопоточной модели и оптимизации под RAM. В одном проекте мы перешли с MySQL-кэша на Redis и снизили нагрузку на БД на 80% — база перестала быть узким местом. Как указано в документации Redis: "Redis is an open source, in-memory data structure store, used as a database, cache, and message broker."Redis documentation
Как выбрать стратегию инвалидации кэша?
Cache-aside — самый распространённый паттерн: приложение сначала проверяет кэш, при промахе загружает из БД и сохраняет в кэш с TTL. Идеален для данных, которые обновляются редко. Write-through — запись в кэш и БД синхронно. Подходит для данных с высокой частотой изменений (например, корзина пользователя).
Реализуем cache-aside на TypeScript с ioredis:
import { Redis } from 'ioredis'
export class CacheService {
constructor(private redis: Redis) {}
async getOrSet<T>(
key: string,
ttlSeconds: number,
factory: () => Promise<T>
): Promise<T> {
const cached = await this.redis.get(key)
if (cached !== null) {
return JSON.parse(cached) as T
}
const value = await factory()
await this.redis.setex(key, ttlSeconds, JSON.stringify(value))
return value
}
async invalidate(pattern: string): Promise<void> {
let cursor = '0'
do {
const [nextCursor, keys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100)
cursor = nextCursor
if (keys.length > 0) {
await this.redis.unlink(...keys)
}
} while (cursor !== '0')
}
}
const product = await cache.getOrSet(
`product:${id}`,
300,
() => db.products.findOne({ id })
)
await cache.invalidate(`product:${id}`)Write-through — запись в кэш и БД синхронно:
async updateProduct(id: string, data: UpdateProductDto) {
const updated = await db.products.update(id, data)
await redis.setex(`product:${id}`, 600, JSON.stringify(updated))
return updated
} Сравнение cache-aside и write-through
| Параметр | Cache-aside | Write-through |
|---|---|---|
| Задержка записи | Низкая (пишется только БД) | Выше (ожидание записи в кэш) |
| Риск устаревания данных | Высокий (при изменении без инвалидации) | Низкий (согласованность) |
| Сложность реализации | Низкая | Средняя |
| Нагрузка на БД | Выше при промахах | Ниже (кэш всегда актуален) |
Как настроить Redis для кэширования: пошаговая инструкция
- Установите Redis через пакетный менеджер:
sudo apt install redis. - Настройте
maxmemoryи политику вытеснения, напримерallkeys-lru. - Включите персистентность: AOF с
fsync everysec. - Реализуйте паттерн кэширования (cache-aside или write-through).
- Настройте мониторинг:
slowlog,info stats,--latency-history. - Для отказоустойчивости настройте Sentinel или Cluster.
- Протестируйте под нагрузкой и оптимизируйте TTL.
Сессии пользователей в Redis
Хранение сессий в Redis — стандарт для высоконагруженных приложений. Используем sliding window с автоматическим продлением TTL:
const SESSION_TTL = 86400 * 7
export class SessionService {
constructor(private redis: Redis) {}
async create(userId: string, metadata: SessionMeta): Promise<string> {
const sessionId = crypto.randomUUID()
const key = `session:${sessionId}`
await this.redis.hset(key, {
userId,
createdAt: Date.now(),
ip: metadata.ip,
userAgent: metadata.userAgent
})
await this.redis.expire(key, SESSION_TTL)
await this.redis.zadd(`user:${userId}:sessions`, Date.now(), sessionId)
await this.redis.expire(`user:${userId}:sessions`, SESSION_TTL)
return sessionId
}
async get(sessionId: string): Promise<SessionData | null> {
const data = await this.redis.hgetall(`session:${sessionId}`)
if (!Object.keys(data).length) return null
await this.redis.expire(`session:${sessionId}`, SESSION_TTL)
return data as SessionData
}
async destroyAll(userId: string): Promise<void> {
const sessionIds = await this.redis.zrange(`user:${userId}:sessions`, 0, -1)
if (sessionIds.length) {
const keys = sessionIds.map(id => `session:${id}`)
await this.redis.unlink(...keys, `user:${userId}:sessions`)
}
}
} Rate limiting через sliding window
async function rateLimit(
redis: Redis,
key: string,
limit: number,
windowMs: number
): Promise<{ allowed: boolean; remaining: number; resetAt: number }> {
const now = Date.now()
const windowStart = now - windowMs
const pipeline = redis.pipeline()
pipeline.zremrangebyscore(key, '-inf', windowStart)
pipeline.zadd(key, now, `${now}-${Math.random()}`)
pipeline.zcard(key)
pipeline.pexpire(key, windowMs)
const results = await pipeline.exec()
const count = results![2][1] as number
return {
allowed: count <= limit,
remaining: Math.max(0, limit - count),
resetAt: now + windowMs
}
} Sentinel для высокой доступности
Для критичных систем настраиваем Redis Sentinel с автоматическим failover. Пример конфигурации Sentinel
sentinel monitor mymaster 10.0.0.1 6379 2
sentinel auth-pass mymaster your_password
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1Мониторинг выполняем через redis-cli --latency-history и redis-cli info stats. Отслеживаем evicted_keys и rejected_connections — резкий рост сигнализирует о проблемах. За многолетнюю работу с Redis мы не допустили ни одного отказа из-за неправильной конфигурации на наших объектах.
Что делать при переполнении памяти?
При переполнении памяти Redis начинает вытеснять ключи согласно политике maxmemory-policy. По умолчанию в некоторых версиях используется noeviction, что приводит к ошибкам записи. Рекомендуем allkeys-lru — вытесняет наименее используемые ключи. Также настройте maxmemory на 70-80% от доступной RAM, оставляя запас для операционной системы.
Что входит в настройку Redis под ключ
- Аудит текущей инфраструктуры и нагрузки
- Установка и конфигурация Redis с учётом maxmemory, персистентности, AOF
- Реализация паттернов кэширования (cache-aside, write-through)
- Интеграция сессий и rate limiting
- Настройка Sentinel или Cluster (при необходимости)
- Документация по эксплуатации и инвалидации кэша
- Передача доступов и обучение команды
- 2 недели пост-поддержки
Стоимость настройки варьируется от 1000 до 5000 BYN в зависимости от сложности и объёма работ, но точную цену рассчитываем индивидуально.
| Этап работы | Длительность |
|---|---|
| Аудит и анализ | от 1 дня |
| Установка и конфиг | 1–2 дня |
| Интеграция паттернов | 2–3 дня |
| Сессии и rate limiting | 1–2 дня |
| Sentinel / Cluster | 1–3 дня |
| Тестирование и документация | 1–2 дня |
Наш опыт и гарантии
У нас за плечами многолетний опыт работы с Redis и 50+ проектов, где мы настраивали кэширование для высоконагруженных приложений. Мы гарантируем стабильную работу — все конфигурации проходят нагрузочное тестирование. Если Redis не даст ожидаемого прироста производительности, мы донастроим бесплатно.
Свяжитесь с нами, чтобы обсудить ваш проект. Закажите настройку Redis — получите консультацию инженера с опытом продакшена.







