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

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

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

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

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

Типові кейси

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

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

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 робить прибирання автоматичним:

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

3. Rate limiting

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

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-сервіси та вибір бази даних. А якщо ціль – системна підготовка, є план на 30 днів.