# Redis і кешування: коли кеш потрібен, а коли шкодить

> Що таке Redis і чим він відрізняється від бази даних: кеш запитів, сесії, rate limiting, стратегія cache-aside з кодом, інвалідація через TTL і типові запитання зі співбесід.

- Автор: Юра Скиба (https://cookiesoftware.io)
- Опубліковано: 2026-09-24
- Категорія: Backend і System Design
- Canonical: https://cookiesoftware.io/blog/redis-koly-potriben-kesh

---

**Коротка відповідь.** Кеш потрібен тоді, коли ті самі дані читаються значно частіше, ніж змінюються, а джерело даних дороге: повільний запит до бази, зовнішній API, важке обчислення. Кеш шкодить, коли дані мають бути завжди актуальними або коли його додають «про запас» без виміряної проблеми. Redis – найпоширеніший інструмент кешування: сховище структур даних у пам'яті з часом відповіді, що вимірюється частками мілісекунди.

## Що таке Redis і чим він відрізняється від бази

Redis тримає дані в оперативній пам'яті й працює зі структурами: рядки, хеші, списки, множини, sorted sets. Звідси і сила, і межі:

- **Швидкість.** Читання з пам'яті на порядки швидше за дисковий запит із JOIN-ами.
- **Простота моделі.** Ключ → значення. Немає JOIN, немає ad-hoc запитів «знайди всі замовлення з товаром X».
- **Пам'ять скінченна.** Redis зазвичай зберігає гарячу підмножину даних, а не все.
- **Довговічність – опційна.** Персистентність налаштовується, але типова роль Redis – дані, які МОЖНА втратити і перебудувати з основної бази.

Тому правильна ментальна модель: Redis доповнює базу, а не замінює її. Першоджерело для деталей: [redis.io/docs](https://redis.io/docs/).

## Типові кейси

### 1. Кеш дорогих запитів (cache-aside)

Найпоширеніший патерн: спершу дивимось у кеш, на промах ідемо в базу і кладемо результат у кеш із TTL:

```ts
const CACHE_TTL_SECONDS = 300;

async function getProductCard(productId: string): Promise<ProductCard> {
  const cacheKey = `product:card:${productId}`;

  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached) as ProductCard;
  }

  // Промах: читаємо з бази (дорогий запит із JOIN-ами)
  const card = await db.loadProductCard(productId);

  await redis.set(cacheKey, JSON.stringify(card), 'EX', CACHE_TTL_SECONDS);
  return card;
}

async function updateProduct(productId: string, patch: ProductPatch) {
  await db.updateProduct(productId, patch);
  // Інвалідація: видаляємо ключ, наступне читання перебудує кеш
  await redis.del(`product:card:${productId}`);
}
```

Дві речі, про які питають на співбесіді: що станеться, якщо забути `del` при оновленні (користувачі бачитимуть старі дані до закінчення TTL), і чому TTL потрібен навіть із інвалідацією (страховка від пропущених оновлень і обмеження пам'яті).

### 2. Сесії та тимчасові дані

Сесії, коди підтвердження, стани візардів – дані з природним терміном життя. TTL робить прибирання автоматичним:

```ts
await redis.set(`session:${sessionId}`, JSON.stringify(sessionData), 'EX', 60 * 60 * 24);
```

### 3. Rate limiting

Лічильник запитів на користувача у вікні часу:

```ts
async function isRateLimited(userId: string): Promise<boolean> {
  const key = `rate:${userId}:${Math.floor(Date.now() / 60000)}`; // хвилинне вікно
  const count = await redis.incr(key);
  if (count === 1) {
    await redis.expire(key, 90);
  }
  return count > 60; // максимум 60 запитів на хвилину
}
```

`INCR` атомарний, тому лічильник коректний і при паралельних запитах – це важлива деталь для відповіді.

### 4. Черги і pub/sub – коротко

Списки й streams у Redis використовують як легкі черги задач (відкладене надсилання листів, обробка зображень). Для співбесіди достатньо знати, що це можливо, і чесно сказати, коли варто брати спеціалізований брокер: гарантії доставлення, ретраї, dead letter queues.

## Стратегії інвалідації

- **TTL (time to live).** Найпростіша і найнадійніша: дані застарівають самі. Підходить, коли невелика затримка актуальності прийнятна.
- **Інвалідація при записі (del у транзакції оновлення).** Точніша, але вимагає дисципліни: КОЖЕН шлях оновлення даних має чистити кеш.
- **Write-through.** Запис іде і в базу, і в кеш одразу. Кеш завжди теплий, але запис повільніший і код складніший. На практиці найчастіше комбінують cache-aside + TTL.

Класична цитата індустрії: інвалідація кешу – одна з двох по-справжньому складних задач у програмуванні. На співбесіді це не жарт, а натяк: покажи, що бачиш складність.

## Redis чи кеш у пам'яті процесу?

Найдешевший кеш – звичайна `Map` у пам'яті самого сервера: нуль інфраструктури, нуль мережі. Для одного інстанса і невеликих довідникових даних цього часто досатньо. Redis стає потрібним, коли:

- інстансів кілька, і кеш має бути СПІЛЬНИМ (інакше кожен сервер тримає свою версію даних і інвалідація перетворюється на лотерею);
- дані мають пережити рестарт процесу (деплой скидає in-memory кеш повністю – і кожен деплой б'є по базі холодним стартом);
- потрібні атомарні операції між інстансами: лічильники, rate limiting, блокування.

Це теж хороша відповідь на співбесіді: показати, що ти обираєш найпростіший інструмент, який вирішує задачу, а не одразу тягнеш інфраструктуру.

## Коли кеш шкодить

- **Дані мають бути точними зараз.** Залишки товару на оплаті, баланс рахунку. Кешувати можна відображення, але рішення ухвалюється по базі.
- **Проблеми ще немає.** Кеш до виміряного вузького місця – це додаткова інфраструктура, нові класи багів (розсинхрон) і складніше налагодження. Спочатку профілювання й індекси, потім кеш.
- **Дані змінюються частіше, ніж читаються.** Кеш промахується постійно і лише додає латентність.
- **Кеш став єдиним джерелом правди.** Якщо втрата Redis кладе продукт, це вже не кеш, а база без гарантій бази.

## Типові помилки при роботі з кешем

1. **Кешування на рівні «всього».** Кеш без вимірювання: незрозуміло, що він прискорив, зате зрозуміло, що дані інколи старі. Спершу знайди повільне місце профілюванням, потім кешуй саме його.
2. **Один TTL на все.** Картка товару може жити 5 хвилин, курс валют – 30 секунд, довідник країн – добу. TTL – це продуктове рішення про допустиму застарілість, а не константа з туторіалу.
3. **Ключі без схеми іменування.** `product:card:{id}` можна знайти і масово інвалідувати; ключ `cache_1234` через місяць не розшифрує ніхто. Домовся про префікси одразу.
4. **Великі об'єкти в кеші.** Класти в Redis мегабайтні JSON-и означає платити пам'яттю і мережею за дані, з яких потрібні три поля. Кешуй те, що реально споживається.
5. **Логіка на кеші як на базі.** Перевірка «чи існує користувач» по кешу, який може протухнути, – джерело важковідтворюваних багів. Рішення ухвалюються по джерелу правди.

## Запитання зі співбесід

**Що станеться, коли Redis впаде?**
Правильна відповідь залежить від ролі: якщо це кеш, застосунок працює повільніше, навантаження йде в базу (і варто мати захист від cache stampede); якщо там сесії, користувачі розлогіняться. Висновок, який хочуть почути: падіння кешу не має бути падінням продукту.

**Що таке cache stampede і як захиститися?**
Популярний ключ протух, тисяча запитів одночасно пішла в базу перебудовувати той самий кеш. Захист: блокування на перебудову (лише один запит будує, решта чекає або віддає stale), рознесені TTL.

**Чому не тримати всі дані в Redis, якщо він такий швидкий?**
Пам'ять дорога і скінченна, модель ключ-значення не дає складних запитів, довговічність слабша за дискову базу з WAL. Швидкість – не єдина вимога до сховища.

## Що потренувати

1. Реалізуй cache-aside з прикладу вище над будь-яким повільним запитом свого pet-проєкту і виміряй різницю.
2. Зламай його: онови дані без інвалідації і подивись на наслідки. Відчуття «звідки беруться stale-дані» коштує більше за конспект.
3. Сформулюй одним абзацом, що саме ти кешуєш у своєму проєкті й чому саме з таким TTL.

Кеш – типова тема другої половини backend-співбесіди; перша зазвичай про [Node.js і HTTP-сервіси](/blog/nodejs-backend-interview) та [вибір бази даних](/blog/sql-vs-nosql). А якщо ціль – системна підготовка, є [план на 30 днів](/blog/pidhotovka-do-tekhnichnoi-spivbesidy).
