Асаліны кэшавання ў Redis: шаблоны, апасцярожнасці і праблемы пад час спрачаў
Кэшаванне з дапамогою Redis у Node.js: метод cache-aside, тайм-ауты TTL, захист ад атакам типу stampede і правілы выключэння данных з кэша, а таксама пытанні пад час співбеседы па проектаванні систем, якія стосуюцца гэтага.
Крыніцы: 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
Мета не ў тым, каб цэлком пазбавіцца базы дадзэнняў.
Це ў тым, каб зменшыць колькасць работ у базе дадзэнняў, якія не патрабуюцца два разы.
Успешны запит да кэша проты неуспешнага
Этыя два панявы часта з’яўляюцца, таму варта ўжо знать іх.
Успешны запит да кэша
Гэта выклікаецца тады, калі дадзеныя, якія вы шукаеце, вже знаходзяцца ў кэшы.
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
Для систем, паблікаваных на прынцыпе кэша-асайд, простая вычысценне афектаванага элемента кэша праз кожны запіс часто ўсё простае і безпечней, чым прабавы стаяць кэш у вечныя сінхронізацыі з базай дадзеных.
Тры проблемы кэша, якія вам трэба знать
Этыя тэмы часта выклікаюць ажыятлівую дыскусію пад час спрацоўкі.
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 як шар для падышчы канэктывацыі, які працуе разам з вашай системай, а не як повныя заменнік для галоўной базы дадзенаў.
Локальны кэш проты распадзеленага кэшу
Уявіце сабе прыклад аплікацыі Node.js, якая працуе на трох разных серверах:
Server A → Cache A
Server B → Cache B
Server C → Cache C
Кожны з гэтых сервераў стварывае сабой незалежны кэш.
Пользователь можа зайсці на Сервер А і атрымаць даны з кэшу, а пазней — на Сервер Б і не атрымаць тыя ж самыя даны.
Уведэнне спакульнае інстанцыі 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.
Тепер вы розумеете, як системы з вялікім трафікам захоўваюць свае базы дадзеных, заставаючыся швальнымі.