Part 04 · PostgreSQL & Redis · 4.1.10

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 familyCandidate keysKhi phù hợp
noevictionKhô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/randomMọi key.Instance thuần cache, mọi key đều có thể bị loại.
volatile-lru/lfu/randomChỉ keys có TTL.Trộn persistent và expiring keys có chủ đích.
volatile-ttlKeys 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.

Volatile policy có thể hành xử như noeviction: nếu không còn key có TTL để chọn, writes vẫn có thể fail dù instance còn nhiều persistent keys. Audit tỷ lệ keys có expiry và đừng trộn cache với authoritative data một cách vô thức.

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 versionNgăn write cũ ghi đè version mới; cần monotonic version từ source of truth.
Transactional outbox + invalidation consumerNối DB commit với event đáng tin cậy; vẫn cần idempotency, retry và lag budget.
Short TTL + jitterGiớ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 invalidationCó 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.

English interview answer: “Cache invalidation depends on tolerated staleness and ownership. With cache-aside I update the database first, invalidate the cache, use TTL as a safety net and protect hot-key rebuilds with jitter and single-flight behavior. I also design for cache failure so the database does not collapse when Redis is unavailable.”

Hot keys và big keys

Vấn đềẢnh hưởngHướng xử lý
Hot keyDồ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 stringLarge response, replication và memory-copy latency.Giới hạn payload, chunk theo access pattern, compress có đo CPU/latency.
Big collectionO(N) command/delete/iteration, hot shard và long blocking.Bound cardinality, time-bucket/partition structure, incremental commands.
Synchronous deleteFree 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.

Chọn primitive gần invariant: nếu invariant nằm trong PostgreSQL, unique constraint, conditional update, row/advisory lock hoặc serializable transaction thường dễ chứng minh hơn một distributed lock ngoài database.

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

LayerSignalsCâu hỏi
Business/cacheHit 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?
CommandsOps/sec, p50/p95/p99, slow log, timeouts, rejected writes.Command hoặc key class nào gây tail latency?
MemoryUsed memory/RSS, fragmentation, evicted/expired keys, client buffers.Dataset hay overhead đang đẩy instance tới giới hạn?
Durability/HAReplica lag/backlog, failover state, fork/COW, AOF fsync/rewrite.Node còn đáp ứng data-loss và recovery assumptions?
ClusterSlot 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.

Production question: “Redis down thì sao?” Câu trả lời đầy đủ phải nói rõ timeout, fallback concurrency, stale policy, load shedding, database protection, recovery warmup và metric chứng minh hệ thống không chuyển một cache incident thành database incident.
Nguồn tham khảo