Bỏ qua để đến nội dung
Knowledge Hub

Redis expiration và eviction: key biến mất vì hết hạn hay vì thiếu memory? Cơ bản

Thời gian đọc ước tính: 8 phút

API đọc đơn hàng từ PostgreSQL và lưu bản sao vào Redis 10 phút. Một buổi chiều database tăng tải vì cache miss nhiều hơn, và key order:42 biến mất trước hạn. Kết luận vội “TTL của Redis sai” thường sai: key có thể mất theo hai đường độc lập.

Expiration Eviction
Nguyên nhân Thời hạn của key đã qua Redis chạm maxmemory và cần nhường chỗ
Có thể xảy ra trước TTL? Không Có
Metric expired_keys evicted_keys
Xử lý Chỉnh TTL, jitter Thêm RAM, giảm big key, đổi policy

Tăng TTL khi Redis đang evict vì thiếu memory chỉ làm tình hình xấu hơn.

Cấu hình Default Hệ quả
maxmemory 0 (không giới hạn, trên hệ 64-bit) Không bao giờ evict; Redis lớn tới khi OOM của hệ điều hành
maxmemory-policy noeviction Khi đầy: lệnh ghi bị từ chối, lệnh đọc vẫn chạy
maxmemory-samples 5 LRU/LFU chỉ xấp xỉ

Vì thế một Redis dùng làm cache mà không đặt maxmemory sẽ không bao giờ evict, và đặt maxmemory mà không đổi policy sẽ trả lỗi ghi khi đầy. Cache thuần cần đặt cả hai.

TTL là thời gian còn lại của key. SET key v EX 600 ghi giá trị cùng hạn; TTL trả -1 nếu key tồn tại nhưng không có hạn, -2 nếu key không tồn tại. SET ghi đè xoá TTL của key (dùng KEEPTTL để giữ), còn INCR, LPUSH, HSET giữ nguyên TTL.

  1. Lazy (thụ động): key bị xoá khi một lệnh truy cập nó và thấy đã quá hạn.
  2. Active (chủ động): theo tài liệu latency của Redis, chu kỳ chạy 10 lần mỗi giây; mỗi chu kỳ lấy mẫu 20 key có TTL, xoá các key đã hết hạn, và lặp lại nếu hơn 25% mẫu đã hết hạn.

Hệ quả: key đã hết hạn có thể vẫn chiếm RAM một lúc, và nếu rất nhiều key hết hạn cùng giây (chiếm từ 25% số key có TTL), Redis có thể bị chặn để dọn. Đây là lý do cần jitter TTL.

Master xoá key rồi gửi DEL cho replica; replica không tự expire độc lập (nhưng giữ đủ thông tin hạn để tiếp quản khi lên làm master). Hệ quả:

  • Trong lúc chờ DEL đến, key đã quá hạn vẫn chiếm memory trên replica.
  • Theo tài liệu replication của Redis, replica dùng đồng hồ logic để báo key không tồn tại với lệnh đọc, nên client đọc từ replica thường không thấy giá trị đã hết hạn.

Ngược lại, theo redis.conf, từ Redis 5 replica mặc định bỏ qua maxmemory của chính nó (replica-ignore-maxmemory yes); eviction diễn ra ở master.

Policy Ứng viên Ghi chú
noeviction (default) Không có Ghi bị từ chối khi đầy
allkeys-lru Mọi key LRU xấp xỉ; Redis khuyên làm lựa chọn mặc định khi không có lý do khác
allkeys-lfu Mọi key Giữ key truy cập thường xuyên; tinh chỉnh bằng lfu-log-factor (10), lfu-decay-time (1 phút)
allkeys-random Mọi key Hợp khi các key được truy cập đều nhau
volatile-lru/lfu/random Chỉ key có TTL Hợp khi một instance vừa cache vừa lưu key bền
volatile-ttl Key có TTL Evict key sắp hết hạn trước

Mọi policy volatile-* hành xử như noeviction khi không còn key nào có TTL. maxmemory-samples (mặc định 5) là số key lấy mẫu mỗi lần evict; tăng lên 10 xấp xỉ LRU thật hơn với chút CPU thêm.

Workload Policy Lý do
Cache thuần, vài key nóng allkeys-lru (hoặc allkeys-lfu) Tận dụng phân bố truy cập lệch
Cache thuần, truy cập đều allkeys-random Không có key “nóng” để ưu tiên
Cache lẫn session/key bền trong một instance Tách instance; nếu bắt buộc, volatile-* allkeys-* có thể evict cả session
Queue, lock, dữ liệu không được mất noeviction Thà lỗi ghi còn hơn mất dữ liệu lặng lẽ
TTL và giới hạn memory là hai nguyên nhân mất key khác nhau
TTL và giới hạn memory là hai nguyên nhân mất key khác nhauBước 1/4: Ứng dụng ghi cache key cùng TTL. Redis lưu giá trị và thời điểm hết hạn.ClientRedisMemorySET EX
Client → RedisSET EX
Bước 1/4

Ứng dụng ghi cache key cùng TTL. Redis lưu giá trị và thời điểm hết hạn.

Timeline một key SET EX 2:

Mốc Quan sát Ghi chú
T0 Key có giá trị, TTL khoảng 2
T0 + 1 s Còn, hoặc đã bị evict nếu áp lực memory Eviction nhìn từ ứng dụng giống expiration
T0 + 2,3 s GET trả nil, TTL = -2 Byte được giải phóng sớm hay muộn là chuyện nội bộ

TTL là giới hạn tối đa của độ cũ, không đảm bảo dữ liệu mới trong khoảng đó. Đơn hàng có thể đổi trong PostgreSQL ngay sau khi ghi cache; xem cache-aside.

Đang tải diagram…

Lệnh cần chạy:

Terminal window
redis-cli INFO stats | grep -E "expired_keys|evicted_keys|keyspace_hits|keyspace_misses"
redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human|mem_not_counted_for_evict"
redis-cli CONFIG GET maxmemory-policy

Hit rate = keyspace_hits / (keyspace_hits + keyspace_misses). Đừng cộng expired_keys + evicted_keys rồi coi là số miss: một key hết hạn mà không ai đọc không gây miss, còn một hot key bị evict gây hàng nghìn miss.

Terminal window
docker compose -f examples/docker-compose.yml up -d redis
npx --prefix examples ts-node examples/src/redis-expiration/ttl-demo.ts
docker compose -f examples/docker-compose.yml stop redis

Script làm ba bước:

  1. Ghi một key sống 2 giây, in giá trị và TTL (before: cached-order, TTL gần 2).
  2. Đợi 2,3 giây rồi đọc lại (after: null TTL: -2).
  3. In maxmemory-policy của container, thường là noeviction theo default ở trên.

Demo chỉ chứng minh expiration; nó không đẩy Redis tới maxmemory. Để thấy eviction, dùng một Redis test riêng với maxmemory nhỏ và đọc INFO stats; đừng CONFIG SET maxmemory trên instance đang phục vụ dữ liệu thật.

TTL và policy đem lại gì

  • TTL đặt giới hạn tồn tại cho cache entry và tự dọn dữ liệu không ai đọc
  • Eviction cho Redis tự kiểm soát memory khi cache tăng ngoài dự tính
  • Nhiều policy cho phép ưu tiên hot key hoặc key sắp hết hạn

Giới hạn cần dự liệu

  • TTL dài tăng nguy cơ stale; TTL ngắn tăng miss và tải DB
  • Eviction có thể xoá key còn TTL, kéo hit rate xuống
  • Policy sai với workload hỗn hợp có thể evict dữ liệu quan trọng hoặc làm ghi thất bại
  1. Đặt maxmemory rõ ràng và chừa RAM cho replication/AOF buffer (xem mem_not_counted_for_evict trong INFO memory); các buffer này không tính vào maxmemory.
  2. Đặt maxmemory-policy đúng workload; không để noeviction mặc định cho cache thuần.
  3. Không trộn cache với dữ liệu không được mất trong một instance; tách instance.
  4. TTL có jitter cho key nạp hàng loạt.
  5. Cảnh báo theo tốc độ tăng của evicted_keys và hit rate, không chỉ used_memory.
  6. Replica: key hết hạn vẫn chiếm memory tới khi DEL từ master đến (lệnh đọc trên replica đã báo key không tồn tại); theo dõi memory của replica.
  7. Redis không phải nơi lưu duy nhất của dữ liệu nghiệp vụ nếu có eviction.

Tự kiểm tra: (1) evicted_keys tăng trong khi nhiều key còn TTL: tăng TTL có giúp không? (2) Vì sao volatile-ttl có thể làm ghi lỗi dù còn key không TTL? (3) Vì sao SET ghi đè là bẫy với TTL?