Cache consistency và production operations
Cache chỉ có giá trị khi giảm tải mà vẫn giữ correctness trong giới hạn đã định. TTL, invalidation, stampede control, failure fallback và observability phải được thiết kế như một protocol giữa cache, source of truth và clients.
Expiration và eviction là hai cơ chế khác nhau
Expiration loại key khi TTL hết hạn bằng cả passive expiration lúc key được truy cập và active sampling ở background. Key có thể tồn tại vật lý thêm một khoảng ngắn sau deadline, nhưng command sẽ không trả value đã được nhận diện là expired. Không dựa TTL như một scheduler chính xác cho business event.
Eviction xảy ra khi memory vượt maxmemory theo policy. LRU/LFU là xấp xỉ dựa sampling; policy phải phù hợp việc key có TTL hay không.
| Policy family | Candidate keys | Khi phù hợp |
|---|---|---|
noeviction | Không evict; write cần thêm memory trả lỗi. | Không được tự ý mất key, caller xử lý capacity failure. |
allkeys-lru/lfu/random | Mọi key. | Instance thuần cache, mọi key đều có thể bị loại. |
volatile-lru/lfu/random | Chỉ keys có TTL. | Trộn persistent và expiring keys có chủ đích. |
volatile-ttl | Keys có TTL, ưu tiên TTL ngắn. | Lifetime phản ánh đúng priority; vẫn là heuristic. |
maxmemory không phải toàn bộ RSS budget: replication/AOF buffers và allocator overhead có accounting riêng, và một command lớn có thể tạm đẩy memory vượt limit trước khi eviction kéo xuống. Capacity phải chừa headroom cho buffers, fork/COW, fragmentation và peak command size.
Cache-aside và race condition
READ: cache miss --> database read --> cache set
WRITE: database commit --> cache invalidate
Cache-aside đơn giản và source of truth vẫn ở database. Tuy nhiên một reader có thể đọc value cũ từ DB, writer commit rồi invalidate cache, sau đó reader mới set lại value cũ vào cache. TTL chỉ giới hạn thời gian stale, không xóa race.
| Mitigation | Điều kiện / trade-off |
|---|---|
| Versioned key/value hoặc compare version | Ngăn write cũ ghi đè version mới; cần monotonic version từ source of truth. |
| Transactional outbox + invalidation consumer | Nối DB commit với event đáng tin cậy; vẫn cần idempotency, retry và lag budget. |
| Short TTL + jitter | Giới hạn stale window và tránh đồng loạt expire; tăng miss/load. |
| Write-through / single writer ownership | Đơn giản hóa ordering nếu mọi writes đi qua cùng protocol; không bảo vệ writers đi đường khác. |
| Delayed second invalidation | Có thể giảm một race window nhưng delay là heuristic, không phải correctness proof. |
Cache key phải bao gồm mọi dimension ảnh hưởng response như tenant, authorization scope, locale và version. Nếu không, cache hit nhanh có thể trả dữ liệu sai user — nghiêm trọng hơn cache miss.
Stampede, penetration và avalanche
Stampede xảy ra khi nhiều requests cùng miss một hot key và dồn về database. Penetration là requests lặp cho dữ liệu không tồn tại. Avalanche là nhiều keys expire hoặc cache mất cùng lúc, tạo load burst diện rộng.
- Single-flight/coalescing trong mỗi process để một request load, các request khác chờ cùng result.
- Distributed lease khi cần phối hợp nhiều instances; holder crash cần expiry, waiter cần deadline và fallback.
- Stale-while-revalidate hoặc probabilistic early refresh để giữ serving path khi refresh diễn ra.
- TTL jitter để tránh hàng loạt key cùng expiration boundary.
- Negative cache cho “not found” với TTL ngắn, đúng tenant/auth scope và invalidation khi object được tạo.
- Rate limit, circuit breaker và bounded DB concurrency để cache outage không hạ luôn source of truth.
Hot keys và big keys
| Vấn đề | Ảnh hưởng | Hướng xử lý |
|---|---|---|
| Hot key | Dồn CPU/network vào một node hoặc slot; thêm shards không tự chia key. | Local cache có invalidation, read replicas nếu stale được phép, request coalescing, logical sharding hoặc precomputation. |
| Big string | Large response, replication và memory-copy latency. | Giới hạn payload, chunk theo access pattern, compress có đo CPU/latency. |
| Big collection | O(N) command/delete/iteration, hot shard và long blocking. | Bound cardinality, time-bucket/partition structure, incremental commands. |
| Synchronous delete | Free object lớn có thể block event loop. | Dùng UNLINK khi asynchronous reclaim phù hợp; vẫn theo dõi lazy-free backlog/memory. |
Dùng SCAN, MEMORY USAGE, redis-cli --bigkeys/--memkeys, slow log và latency tools theo quy trình an toàn. Không chạy KEYS * hoặc full-value diagnostics trên production hot path.
Distributed lock và fencing
Pattern tối thiểu trên một Redis primary là acquire bằng SET lock-key random-token NX PX lease, rồi release bằng script/function compare token và delete atomically. Token ngăn owner cũ xóa lock của owner mới sau khi lease đã hết.
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
end
return 0
Lease không ngăn stale owner tiếp tục ghi downstream sau GC pause, process suspension hoặc network delay. Fencing token tăng đơn điệu phải được downstream resource kiểm tra và từ chối token cũ. Async replication/failover cũng có thể làm lock vừa acquire biến mất trên promoted replica, nên lock safety phải được đánh giá theo topology và failure model.
Client-side caching
Redis server-assisted client-side caching dùng tracking để gửi invalidation khi keys đã đọc thay đổi. Nó giảm network và server load nhưng thêm một cache level: client phải bound memory, xử lý reconnect, flush state khi mất invalidations và xác định behavior khi tracking unavailable.
Opt-in/broadcast modes có trade-off tracking state và invalidation volume. Client-side cache không phù hợp strong consistency nếu application không có cơ chế phát hiện gap hoặc fallback; cache key vẫn phải bao gồm security/tenant context.
Observability theo user outcome
| Layer | Signals | Câu hỏi |
|---|---|---|
| Business/cache | Hit ratio theo endpoint/key class, stale/error rate, rebuild time, origin load avoided. | Cache có thực sự giảm latency/cost mà không sai dữ liệu? |
| Commands | Ops/sec, p50/p95/p99, slow log, timeouts, rejected writes. | Command hoặc key class nào gây tail latency? |
| Memory | Used memory/RSS, fragmentation, evicted/expired keys, client buffers. | Dataset hay overhead đang đẩy instance tới giới hạn? |
| Durability/HA | Replica lag/backlog, failover state, fork/COW, AOF fsync/rewrite. | Node còn đáp ứng data-loss và recovery assumptions? |
| Cluster | Slot coverage/skew, MOVED/ASK, node state, hot shard. | Traffic và memory có cân bằng theo topology? |
Global hit ratio có thể che một endpoint critical luôn miss hoặc một hot key phục vụ stale data. Dashboard và SLO cần segment theo use case, tenant/key class và source-of-truth dependency.
Graceful degradation khi cache lỗi
Redis healthy => normal cache-aside path
Redis slow => short timeout + bounded retry only if safe
Redis down => coalesced/rate-limited origin fallback
Origin at risk => shed optional traffic / serve bounded stale data
Recovery => rate-controlled warmup, not all keys at once
Timeout cache phải nhỏ hơn request deadline và để lại ngân sách cho fallback. Không retry Redis vô hạn hoặc để mỗi request tự đánh DB. Stale response chỉ hợp khi freshness class cho phép; authorization, balance hoặc inventory có thể cần fail closed. Warmup ưu tiên hot keys, có concurrency cap và quan sát origin saturation.