Архітектура кешування: Redis, Memcached
Архітектура кешування: Redis, Memcached
1) Коли і навіщо кеш
Цілі: знизити латентність, розвантажити БД/PSP/зовнішні API і пом'якшити піки.
Шари кешів часто багаторівневі: in-process (L1) → service-level (Redis/Memcached L2) → edge/CDN. Внутрішній кеш прискорює «гарячі» читання, L2 - загальний для сервісів, edge - для публічного контенту.
2) Redis vs Memcached - коротко
Правило: якщо потрібні складні структури даних, персистентність, pub/sub/стріми/скрипти - беріть Redis. Якщо супер-проста, швидка, дешева KV-кеш-прошарок без durability - Memcached.
3) Шаблони кешування
3. 1 Cache-aside (lazy)
Додаток читає з кешу → промах → читає з БД → кладе в кеш з TTL.
Простий контроль TTL, незалежність від кешу. − Можливий «шторм» при промахах.
3. 2 Read-through
Клієнт/проксі сам підтягує з origin при промаху і кладе в кеш.
Централізована логіка. − Складніше інфраструктурно.
3. 3 Write-through / Write-behind
Write-through: запис спочатку в кеш, потім в БД.
Write-behind: запис в чергу, асинхронний флаш в БД (потенційна втрата при краше - потрібен журнал).
3. 4 Two-tier (L1+L2)
L1 (in-process) c коротким TTL і soft TTL, L2 (Redis/Memcached) - «істина кеша». Інвалідація через pub/sub.
4) TTL, шторми і консистентність
TTL: задавайте близько до частоти зміни даних. Для гарячих ключів використовуйте рандомізацію TTL (jitter): `ttl = base ± rand(0..base0. 1)'- знімає синхронні закінчення.
Dogpile (thundering herd): захистіть промахи:- Singleflight: тільки один процес перегенерує значення (див. приклад Lua).
- Soft-TTL + background refresh: після'soft _ ttl'віддавайте застаріле (stale) і оновлюйте тлом.
- Semaphore/lock: `SET key:lock value NX PX=2000`.
- Near-stale: 'stale-while-revalidate'для API відповідей (див. розділ 8).
5) Ключі, неймспейси, серіалізація
5. 1 Іменування ключів
Шаблон: `{domain}:{entity}:{id}:{field}`
Приклади:- `user:profile:42` `catalog:product:1001:v2` `psp:rates:2025-11-03`
Додавайте версію схеми (':v2') - це полегшує масову інвалідацію.
5. 2 Неймспейси через «версію простору»
Тримайте ключ'ns:catalog = 17`. Реальні ключі: `catalog:17:product:1001`. Для глобальної інвалідації каталогу просто інкрементуйте'ns:catalog`.
5. 3 Серіалізація/компресія
JSON - зручний, але важкий. Використовуйте MessagePack/CBOR.
Включайте компресію (LZ4/ZSTD) для великих payload (> 1-2 KB). У Redis - на стороні клієнта.
6) Гарячі ключі та шардування
Hot-keys: моніторьте top-N по hit/miss/byte. Для екстремально гарячих ключів:- Replicated read pattern: дублюйте значення в кілька shard-ключів'hot:k:1. N', вибирайте випадковий при читанні.
- Local L1: тримайте в пам'яті процесу з підпискою на інвалідацію.
- Redis Cluster - нативно (16384 hash-слотів).
- Memcached - клієнтський консистентний хеш.
- Hash-tag в Redis'{...}'фіксує слот для набору ключів: `user:{42}:profile` и `user:{42}:limits'опиняться на одному шарді.
7) Політики витіснення і розміри
Redis `maxmemory-policy`: `allkeys-lru`, `volatile-lru`, `allkeys-lfu`, `noeviction` и т. д. Для кешу зазвичай'allkeys-lru '/' allkeys-lfu'.
Memcached — LRU на item-slab.
Розмір ключа і value: слідкуйте за max item size (Memcached за замовчуванням 1 МБ, тюнінг slab).
Перевищення пам'яті повинно деградувати передбачувано: не'noeviction'на активному шляху.
maxmemory 32gb maxmemory-policy allkeys-lfu hz 50 tcp-keepalive 60
8) Патерни захисту від штормів - код
8. 1 Redis Lua singleflight (псевдо)
lua
-- KEYS[1] = data_key, KEYS[2] = lock_key
-- ARGV[1] = now_ms, ARGV[2] = soft_ttl_ms, ARGV[3] = hard_ttl_ms, ARGV[4] = lock_ttl_ms local payload = redis. call("GET", KEYS[1])
if payload then local meta = redis. call("HGETALL", KEYS[1].. ":meta")
local last = tonumber(meta[2] or "0")
if tonumber(ARGV[1]) - last < tonumber(ARGV[2]) then return { "HIT", payload }
end if redis. call ("SET," KEYS [2], "1," "NX," "PX," ARGV [4]) then return {"REFRESH," payload} - one worker updates, the rest give stale end return {"STALE," payload}
end if redis. call("SET", KEYS[2], "1", "NX", "PX", ARGV[4]) then return { "MISS", nil }
end return { "BUSY", nil }
8. 2 Node. js cache-aside (спрощено)
js const v = await redis. get(key);
if (v) return decode(v);
const lock = await redis. setNX(key+":lock", "1", { PX: 1500 });
if (lock) {
const fresh = await loadFromDB(id);
await redis. set(key, encode(fresh), { EX: ttl, NX: false });
await redis. del(key+":lock");
return fresh;
} else {
await sleep(60); // short backoff const retry = await redis. get (key) ;//give someone's already filled return decode (retry);
}
9) Інвалідація та узгодженість
За подією: при зміні в БД публікуйте'pub/sub'подія'invalidate:{ns}:{id}'→ передплатники видаляють ключі.
За таймером: короткий TTL для часто мінливих даних.
Версіонування: див. "ns:'ключі.
Outbox: гарантія доставки інвалідації (подія в лог/топік, ретраї).
Ідемпотентність операцій з кешем: використовуйте'SETXX/SETNX', версії ('etag') і хеш-поля для інкременту.
10) Реплікація, кластер, failover
10. 1 Redis
Sentinel: автоматичний failover master-replica (стейтFUL IP/ім'я).
Cluster: шардування + автоматичний failover; клієнти повинні підтримувати редиректи'MOVED/ASK'.
AOF/RDB: для кешу зазвичай'appendfsync everysec', можна без персистентності (як чистий кеш).
10. 2 Memcached
Немає реплікації з коробки. Надійність - через багатосерверний шард + повтор'n'( client-side).
При падінні ноди - зростання промахів і «переучування» кешу.
10. 3 K8s та мережеві аспекти
Redis/Memcached не люблять часту пересоздачу pod'ів; використовуйте StatefulSet + антиподи по AZ, фіксовані PVC/POD IP.
Ставте PodDisruptionBudget і TopologySpreadConstraints.
11) Транзакції, скрипти та атомарність (Redis)
INCR/DECR, HINCRBY - лічильники, квоти, rate-limits (тільки враховуйте persist).
MULTI/EXEC - пачка атомарних команд.
Lua (EVAL) - read-modify-write без гонок.
Pipeline - зменшує RTT (особливо при мережевому хопі).
lua
-- KEYS[1]=bucket, ARGV[1]=capacity, ARGV[2]=refill_rate_per_sec, ARGV[3]=now_ms
-- Returns 1 if the token is issued, otherwise 0
12) Черги, pub/sub і Streams (Redis)
Pub/Sub: Інвалідація, сигнали. Без збереження, тільки онлайн-слухачі.
Streams: черги подій з підтвердженням (ACK), групи споживачів, ретраї - зручно для write-behind/фан-аутів.
Lists (`BRPOP`): Прості черги.
Не використовуйте Redis як «єдину шину всього» без бекапа - це кеш/швидка шина, не Kafka.
13) Безпека та доступ
Ізоляція мереж/VPC, mTLS на ingress-рівні, ACL/паролі ('requirepass '/ACL в Redis 6 +).
Disable dangerous команд в Redis ('CONFIG','FLUSHALL','KEYS') через ACL.
Для Memcached - не слухати публічні інтерфейси,'-U 0'( без UDP), тільки приватні мережі.
PII не зберігати; якщо потрібно - короткий TTL + шифрування на рівні програми.
14) Спостережуваність і обслуговування
Ключові метрики:- Hit ratio/Miss ratio (за namespace/маршрутом).
- Latency p95/p99 команд'GET/SET/MGET', timeouts.
- Evictions и OOM errors.
- Replication lag (Redis), cluster state, migrate/rehash events.
- Top-N keys за трафіком/байтами (семплінгом).
- Логи: повільні команди («slowlog»), помилки мережі.
- Дашборди: загальне (CPU/RAM/connections), команди, слоти кластера, sentinels, пропускаючи через Prometheus-експортери.
15) Конфіги і розгортання - приклади
15. 1 Redis Sentinel (фрагмент)
port 6379 protected-mode yes appendonly yes appendfsync everysec maxmemory-policy allkeys-lfu
`sentinel. conf`:
sentinel monitor m1 10. 0. 0. 11 6379 2 sentinel auth-pass m1 sentinel down-after-milliseconds m1 5000 sentinel failover-timeout m1 60000
15. 2 Redis Cluster (helm values, спрощено)
yaml cluster:
enabled: true nodes: 6 # 3 masters + 3 replicas persistence:
size: 100Gi resources:
requests: { cpu: "500m", memory: "2Gi" }
15. 3 Memcached (deployment)
yaml containers:
- image: memcached:1. 6 args: ["-m", "32768", "-I", "2m", "-v", "-t", "8", "-o", "modern"]
ports: [{ containerPort: 11211 }]
15. 4 NGINX як read-through проксі (контур API)
nginx proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api:100m max_size=10g inactive=10m;
map $request_uri $cache_key { default "api:$request_uri"; }
location /api/ {
proxy_cache api;
proxy_cache_valid 200 1m;
proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
proxy_cache_lock on; # singleflight на уровне NGINX proxy_cache_key $cache_key;
proxy_pass http://backend;
}
16) Тестування та гейти
Навантажувальні профілі «холодний/теплий/гарячий кеш».
Ін'єкція промахів (purge масово) - origin повинен витримати «переучування».
Алерти: різке падіння hit-ratio, зростання miss latency, лавина evictions, зростання timeouts.
17) Анти-патерни
Зберігати «істину» в Redis без AOF/RDB і без резервування.
TTL = 0 (безстроково) для волатильних даних → вічна неконсистентність.
Масовий «KEYS» в проді.
Відсутність jitter/soft-TTL → синхронні витікання і шторму.
Один інстанс на всі команди без шардування/реплік.
Використовувати Memcached для завдань, що вимагають атомарності/скриптів.
18) Чек-лист впровадження (0-45 днів)
0-10 днів
Вибрати шаблон (cache-aside + L1/L2), описати ключі, TTL, неймспейси.
Увімкнути jitter/soft-TTL, singleflight; базові алерти/дашборди.
Для Redis - налаштувати ACL, protected-mode, slowlog, maxmemory-policy.
11-25 днів
Перейти на шардування (Redis Cluster або клієнтський хеш), репліки.
Інвалідація через pub/sub або версію неймспейсу; outbox в БД.
Навантажувальні тести «переучування» кешу; limiting origin.
26-45 днів
Автопромо/канарські TTL, прогрівши перед релізом.
Streams для write-behind/фонових перегородок.
Щотижневі звіти по hit-ratio, топ-ключам, вартості пам'яті.
19) Метрики зрілості
Hit-ratio L2 ≥ 80% (статистика по маршрутах/неймспейсам).
P95 GET <2-3 ms (in-DC), промахи <SLO origin.
0 шторму при масовій інвалідації (доведено тестами).
Автоматична інвалідація та версіонування неймспейсів.
Шардування/реплікація покривають відмову 1 вузла без помітної деградації.
20) Висновок
Сильна архітектура кеша - це дисципліна ключів і TTL, захист від шторму, правильне шардування і передбачуване витіснення. Redis дає багату семантику, персистентність і атомарність; Memcached - максимум простоти і швидкості. Додайте спостережуваність, інвалідацію по подіям, L1 + L2, і кеш стане прискорювачем платформи, а не джерелом випадкових падінь і «містичних» багів.