Налаштування 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 — отримайте консультацію інженера з досвідом продакшену.







