Основи кешування в Redis: шаблони, поширені помилки та концепції для співбесід.
Кешування в Redis у Node.js: метод cache-aside, TTL, захист від надмірного навантаження та політики видалення даних, а також запитання до співбесіди з проектування систем, пов’язані з цими аспектами.
Джерела: Redis Isn’t Just a Cache: The Concepts Every Backend Developer Should Know
Чому нам потрібна кеш-пам’ять, якщо у нас вже є база даних?
GET /products/123
Клієнт звертається до цього маршруту, і ваш сервер починає запитувати MongoDB:
API → MongoDB → Product
База даних повертає запис про продукт, і все працює без проблем.
10 000 людей, які запитують про той самий продукт приблизно в один і той самий час.
У результаті доведеться тисячі разів повторювати абсолютно однакові операції:
Request
↓
Database
↓
Same Product
Ваша база даних постійно виконує ідентичну роботу для однакових запитів.
кешування.
Що таке кешування?
Кеш — це швидший рівень зберігання, який тримає даних, що запитуються часто, протягом короткого часу, щоб ваш додаток не був змушений щоразу звертатися до повільнішого основного сховища даних.
Замість звичайного алгоритму:
Request → Database → Response
ми пропонуємо скорочений шлях:
Request
↓
Cache
↓
Hit? ── Yes → Response
│
No
↓
Database
↓
Cache
↓
Response
Мета не в тому, щоб повністю позбутися бази даних.
Це для того, щоб зменшити кількість операцій у базі даних, які не потрібно виконувати двічі.
Успішний пошук у кеші vs Неуспішний пошук у кеші
Ці два поняття зустрічаються постійно, тож варто знати їх наизусть.
Успішний пошук у кеші
Це трапляється, коли дані, які ви шукаєте, вже знаходяться у кеші.
Request
↓
Redis
↓
Found ✅
↓
Response
Неуспішний пошук у кеші
Це трапляється, коли у кеші немає потрібних даних.
Request
↓
Redis
↓
Not Found ❌
↓
Database
↓
Store in Redis
↓
Response
Корисним показником тут є коефіцієнт успішних пошуків у кеші:
Cache Hit Ratio =
Cache Hits / Total Cache Requests
Вищий коефіцієнт зазвичай свідчить про те, що більше запитів обробляється безпосередньо з кешу, а не з бази даних.
Проте вище число не означає автоматично успіху. Якщо кеш заповнений застарілими чи менш важливими даними, високий коефіцієнт успішних запитів не робить його корисним.
То яке місце тут займає Redis?
Redis — це система зберігання даних у оперативній пам’яті, яка часто використовується для кешування, хоча її можливості значно перевищують цей один сценарій використання.
Вона пропонує низку структур даних, зокрема:
- Рядки
- Хеші
- Списки
- Множини
- Впорядковані множини
- Стріми
- JSON
- А також інші спеціалізовані типи
Для простого кешу можна уявити його ось так:
Key Value
────────────────────────────
product:123 Product data
user:42 User data
Оскільки Redis зберігає активні дані в пам’яті, операції читання з кешу можуть виконуватися дуже швидко.
Redis у додатку Node.js
Щоб використовувати клієнт node-redis, його встановлюють за допомогою:
npm install redis
Налаштування базового з’єднання вимагає лише кількох рядків коду:
import { createClient } from "redis";
const redis = createClient();
await redis.connect();
Після цього зберігання та отримання значень є простими:
await redis.set("user:42", "Hamza");
const user = await redis.get("user:42");
Офіційна документація Redis наразі рекомендує node-redis як клієнта для проектів Node.js.
Однак знання команд SET та GET допомагають лише частково.
Справжнє питання, на яке варто відповісти, звучить так:
Як насправді проектувати додаток з використанням Redis?
Найважливіший паттерн: Cache-Aside
Якщо існує один шаблон, який кожен розробник бекенду повинен спочатку опанувати, то це Cache-Aside.
У цьому шаблоні сама програма відповідає за керування кешем.
Request
↓
Check Redis
↓
┌───────────────┐
│ Cache Hit? │
└───────────────┘
↓ Yes ↓ No
Response Database
↓
Redis
↓
Response
Спрощена версія в коді виглядає так:
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);const product = await Product.findById(id);await redis.set(key, JSON.stringify(product), {
EX: 300
});
Параметр EX: 300 встановлює TTL у 300 секунд для запису в кеші.
Cache-Aside, мабуть, є найпоширенішим шаблоном кешування з Redis.
TTL: Кеш не повинен існувати вічно
Припустимо, ви зберегли у кеші наступне:
product:123
Що має статися, якщо ціну товару пізніше оновити?
Без терміну закінчення дії користувачі могли б безкінечно бачити стару ціну.
Ось чому існує поняття TTL, або Time To Live.
Product stored
↓
TTL = 300 seconds
↓
Expires
↓
Next request → Database → Fresh cache
TTL встановлює обмеження на те, як довго можуть надаватися застарілі дані.
Проте утримуйтеся від бажання обрати надто довгий TTL.
Правильне значення залежить виключно від того, наскільки застарілі дані може допустити ваша програма.
Анулювання кешу: відома складна проблема
Розгляньмо таку ситуацію:
Database:
price = ₹799
Redis:
price = ₹799
Потім база даних оновлюється:
Database:
price = ₹899
Але Redis все ще зберігає:
price = ₹799
На цьому етапі ваша програма надає несумісні дані залежно від того, звідки вона їх читає.
Поширеним рішенням є очищення кешу щоразу, коли змінюються базові дані:
await Product.findByIdAndUpdate(id, update);
await redis.del(`product:${id}`);
Після цього наступний запит проходить цим шляхом:
Redis → Miss
↓
Database → ₹899
↓
Redis → Store fresh value
Для систем, побудованих на принципі cache-aside, просте видалення відповідної запису кешу після кожного збереження зазвичай є простішим та безпечнішим, ніж спроби постійно підтримувати синхронність кешу з базою даних.
Три проблеми кешу, про які варто знати
Це чудові теми для обговорень під час співбесід.
1. Cache Stampede
product:123 → EXPIRED
Відразу після цього надходить 1 000 запитів одночасно.
Кожен з них не знаходить інформації в кешу.
Кожен з них передається до бази даних.
1000 requests
↓
1000 DB queries 💥
Кеш мав захищати базу даних від надмірного навантаження, але сама подія закінчення терміну дії лише спричинила стрибок навантаження.
Типові способи захисту від цього включають:
- Блокування або послідовні запити, щоб лише один запит оновлював кеш, поки інші чекають
У власній документації Redis прямо описано стратегії захисту від надмірних запитів до «гарячих» ключів.
2. Проникнення в кеш
Що відбувається, коли клієнти постійно запитують дані, яких просто не існує?
GET /users/999999
Redis повертає результат «не знайдено».
База даних повертає повідомлення «не знайдено».
Наступний ідентичний запит повторює точно такий самий цикл.
Зловмисник або несправний клієнт може скористатися цим, щоб завантажувати базу даних марними запитами.
Корисним заходом протидії є негативний кешування — кешування інформації про відсутність чогось:
user:999999 → NOT_FOUND
TTL → short
Завдяки цьому повторні пошуки того самого відсутнього запису можна виконувати безпосередньо у Redis, замість того щоб щоразу звертатися до бази даних.
3. Аваланш кешу
Тепер уявіть собі велику кількість ключів у кеші, які закінчують свою дію приблизно в один і той самий момент.
Key A → expired
Key B → expired
Key C → expired
Key D → expired
↓
Massive DB traffic
Цю ситуацію називають аваланшем кешу.
Типовим способом зменшення ризику є уникнення призначення однакових термінів закінчення дії для великих груп ключів — наприклад, шляхом додавання невеликої випадкової похибки до кожного TTL.
Видалення з кешу: що відбувається, коли у Redis закінчується пам’ять?
Пам’ять Redis не є нескінченною.
Як тільки використання пам’яті досягає налаштованого ліміту, Redis починає видаляти ключі згідно з правилом видалення, яке ви налаштували. У Redis є кілька таких режимів видалення, які дозволяють видаляти найрідше використовувані записи у всьому просторі ключів або лише серед ключів із встановленим терміном дії, видаляти найрідше використовувані записи у будь-якому з цих двох діапазонів, видаляти записи залежно від того, які ключі найближче до закінчення терміну дії, або повністю блокувати нові записи, коли пам’ять заповнена.
LRU по суті означає:
Позбутися того, чим давно не користувалися.
LFU по суті означає:
Позбутися того, до чого дуже рідко звертаються.
Вибір найкращого режиму залежить від вашої конкретної моделі доступу.
Redis — це не лише кеш
Це питання часто виникає під час інтерв’ю.
Оскільки Redis пропонує кілька вбудованих структур даних, він може використовуватися не лише для кешування:
Strings → Caching
Hashes → Objects / sessions
Lists → Queues
Sets → Unique collections
Sorted Sets → Leaderboards
Streams → Event processing
Отже, твердження, що:
"Redis — це база даних для кешування"
розповідає лише частину історії.
Більш точний опис такий:
Redis — це сховище структур даних у оперативній пам’яті, яке зазвичай використовується для кешування, але також здатне обробляти сесії, черги, топ-листи, обмеження швидкості, системи повідомлень типу pub/sub та багато іншого.
Офіційна документація Redis щодо типів даних розглядає ці структури разом із іншими спеціалізованими типами, які він підтримує.
А як щодо стабільності даних у Redis?
Оскільки Redis знаходиться в оперативній пам’яті, чи призводить перезапуск до втрати всього?
Не обов’язково.
Redis пропонує кілька стратегій збереження даних:
- RDB — періодичні знімки даних у певний момент часу
- AOF — журнал, який фіксує кожну операцію запису
- RDB + AOF — поєднання обох методів
- Без збереження даних взагалі
Якщо Redis використовується лише як кеш, втрата його вмісту після перезавантаження часто не має значення, оскільки додаток може просто відновити дані з основної бази даних.
Але якщо Redis зберігає важливі дані самостійно, налаштування збереження стають значно важливішими.
Поширена пастка на співбесіді
«Чому б просто не зберігати все в Redis?»
Тому що пам’ять Redis — це обмежений та відносно дорогий ресурс порівняно з традиційними методами постійного збереження.
Це означає, що не варто бездумно використовувати кешування.
Хороші кандидати на кешування мають такі характеристики:
- Їх часто запитують
- Їх отримання чи обчислення є дорогим
- Певний старіння даних є прийнятним
- Вони варті витрат пам’яті на їх зберігання поблизу
Розглядайте Redis як шар покращення продуктивності, який працює разом із вашою системою, а не як повноцінну заміну основній базі даних.
Локальний кеш проти розподіленого кешу
Server A → Cache A
Server B → Cache B
Server C → Cache C
Кожен з цих серверів у кінцевому підсумку підтримує власний незалежний кеш.
Користувач може потрапити на сервер A та отримати дані з кешу, а потім — на сервер B та не знайти ці самі дані в кешу.
Введення спільної інстанції Redis змінює ситуацію:
┌→ Server A ─┐
Client ──────┼→ Server B ─┼→ Redis
└→ Server C ─┘
Тепер кожна інстанція додатку користується одним і тим самим спільним кешем.
Ця перевага спільного стану є важливою причиною того, чому Redis особливо ефективний під час горизонтального масштабування додатку.
Деталі інтерв’ю
Припустимо, співрозмовник під час інтерв’ю поставить таке завдання:
"Розробіть стратегію кешування для API продукту з високим трафіком."
Слабка відповідь обмежується таким:
"Я використаю Redis."
Краща відповідь містить обґрунтування:
Request
↓
Redis
↙ ↘
Hit Miss
↓ ↓
Response Database
↓
Redis
↓
Response
Далі описуються кожні з цих рішень щодо проектування:
1. Шаблон кешування
Використовуємо підхід cache-aside.
2. TTL
Встановлюємо його залежно від того, наскільки довго дані продукту можуть залишатися застарілими.
3. Анулювання даних
Видаляємо відповідний ключ щоразу, коли оновлюються базові дані.
4. Захист від надмірного навантаження
Використовуйте техніки блокування, об’єднання запитів чи передчасного оновлення даних.
5. Видалення елементів
Виберіть відповідну політику LRU чи LFU для конкретного навантаження.
6. Проектування ключів
product:{id}
user:{id}
7. Моніторинг
Стежте за:
- Коефіцієнтом успішних запитів
- Коефіцієнтом невдалих запитів
- Затримкою виконання
- Використанням пам’яті
- Кількістю видалених елементів
- Популярними ключами
- Помилками Redis
Саме такий багатошаровий підхід дозволяє надати значно переконливішу відповідь щодо проектування системи, ніж просте твердження про те, що "Redis працює швидко."
Швидкий огляд інтерв’ю
Навіщо взагалі використовувати кешування?
Воно зменшує затримку та допомагає основному сховищу даних уникати постійного оброблення дорогих операцій читання.
Чому саме Redis?
Він забезпечує швидкий доступ відразу з пам’яті, підтримку терміну дії, універсальні структури даних та функції, розроблені для розподілених систем.
Що означає «cache-aside»?
Додаток спочатку шукає дані в кеші; якщо їх там немає, він запитує базу даних, а потім записує отриманий результат назад у кеш.
Що означає TTL?
Це термін існування запису в кеші, після якого він автоматично закінчує свою дію.
Що означає анулювання кешу?
Це означає очищення або оновлення записів у кеші щоразу, коли змінюються вихідні дані.
Що таке «cache stampede»?
Це ситуація, коли велика кількість запитів одночасно не знаходить даних у кеші та починає активно запитувати базу даних.
LRU проти LFU?
LRU видаляє ті дані, які використовувалися останньо. LFU видаляє ті дані, які використовувалися найрідше загалом.
Чи кожен запит заслуговує на кешування?
Зовсім ні. Ухвалюйте рішення, ґрунтуючись на тому, як часто доступуються дані, наскільки дорогим є їх отримання, наскільки допустимою є старість даних та скільки пам’яті у вас є вільною.
Модель мислення, яку варто зберегти
Не намагайтеся запам’ятати команди Redis як початкову точку. Натомість зосередьтесь на основній архітектурі:
┌─────────────┐
Request ─────→│ Redis │
└──────┬──────┘
│
Cache Hit?
↙ ↘
YES NO
↓ ↓
Response Database
↓
Redis
↓
Response
Потім розгляньте чотири запитання:
Що має знаходитися в кеші?
Як довго воно має там залишатися?
Коли його потрібно анулювати?
Що станеться, якщо кеш зламається або закінчить термін дії під великим навантаженням?
Розгляд цих запитань — ось що відрізняє поверхневі знання про кешування від мислення рівня продакшну.
Підсумок
Кешування — це не просто:
"Розмістіть Redis перед MongoDB."
Якісна стратегія кешування ґрунтується переважно на компромісах.
Ви змінюєте:
Пам’ять на швидкість
а іноді:
Актуальність на продуктивність
Redis надає механізми, але саме логіка вашого додатку визначає, як ними скористатися.
Якщо ви готуєтесь до співбесід, не обмежуйтеся відповіддю:
"Redis — це база даних у пам’яті.“
Будьте готові детально розповісти про:
модель кешування типу cache-aside, TTL, анулювання даних, коефіцієнт успішних запитів, видалення даних з кешу, ефекти масового доступу, проникнення, лавинний ефект, розподілене кешування та послідовність даних.
Як тільки ви зрозумієте, як ці концепції взаємопов’язані, ви вийдете за межі простого знання Redis.
Тепер ви розумієте, як системи з високим трафіком захищають свої бази даних, зберігаючи при цьому швидкість.