Налаштування 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 тижні пост-підтримки
Вартість налаштування розраховується індивідуально залежно від складності та обсягу робіт.
| Етап роботи | Тривалість |
|---|---|
| Аудит та аналіз | від 1 дня |
| Встановлення та конфіг | 1–2 дні |
| Інтеграція патернів | 2–3 дні |
| Сесії та rate limiting | 1–2 дні |
| Sentinel / Cluster | 1–3 дні |
| Тестування та документація | 1–2 дні |
Наш досвід та гарантії
У нас за плечима багаторічний досвід роботи з Redis та 50+ проектів, де ми налаштовували кешування для високонавантажених додатків. Ми гарантуємо стабільну роботу — всі конфігурації проходять навантажувальне тестування. Якщо Redis не дасть очікуваного приросту продуктивності, ми доналаштуємо безкоштовно.
Зв'яжіться з нами, щоб обговорити ваш проект. Замовте налаштування Redis — отримайте консультацію інженера з досвідом продакшену.







