Danh sách bài viết

Bài 4: TTL-First Thinking — Tư Duy Dữ Liệu Có Thời Hạn

Phần lớn dữ liệu trong Redis nên có thời hạn (ephemeral by default). Bài này xây dựng thói quen hỏi "key này sống bao lâu?" mỗi khi ghi, rồi đi qua toàn bộ lệnh TTL của Redis 7.x: EXPIRE, PEXPIRE, EXPIREAT, SET ... EX/PX, TTL/PTTL, PERSIST và các option NX|XX|GT|LT. Bạn sẽ hiểu bẫy mất TTL khi SET ghi đè và cách giữ bằng KEEPTTL, cơ chế lazy + active expiration của Redis (vì sao RAM không giảm tức thì), TTL điển hình cho cache/session/lock/OTP/rate-limit, kỹ thuật TTL jitter chống hết hạn đồng loạt, kèm code ioredis và redis-py.

25/05/2026
13 phút đọc
0 lượt xem
1

Mục Tiêu Bài Học

  • Hình thành phản xạ ephemeral by default: mỗi lần ghi vào Redis đều tự hỏi "key này sống bao lâu?".
  • Dùng đúng bộ lệnh TTL: EXPIRE, PEXPIRE, EXPIREAT, PEXPIREAT, SET ... EX|PX, đọc TTL/PTTL, bỏ hạn bằng PERSIST, kèm option NX|XX|GT|LT của Redis 7.
  • Tránh bẫy mất TTL khi SET ghi đè, dùng KEEPTTL để giữ.
  • Hiểu cơ chế lazy expiration + active expiration và hệ quả về memory.
  • Chọn TTL điển hình cho cache, session, distributed lock, OTP, rate-limit; áp dụng TTL jitter.
  • Viết được code set TTL, đọc TTL, giữ TTL, tính TTL có jitter bằng ioredis (TS) và redis-py (Python).
2

Ephemeral By Default — Đổi Câu Hỏi Khi Ghi

Khi làm việc với database truyền thống, ta mặc định mọi bản ghi tồn tại mãi mãi cho tới khi có lệnh xoá. Tư duy đó mang sang Redis là sai lầm phổ biến nhất. Redis sống trong RAM — một tài nguyên có giới hạn cứng. Nếu coi Redis như nơi lưu trữ vĩnh viễn, RAM sẽ phình tới khi gặp OOM hoặc bị eviction policy đẩy key ra ngoài một cách không kiểm soát.

TTL-First Thinking đảo ngược mặc định: phần lớn dữ liệu trong Redis là tạm thời (ephemeral), trừ khi có lý do nghiệp vụ rõ ràng để giữ vĩnh viễn. Mỗi lần bạn chuẩn bị ghi một key, hãy dừng lại hỏi:

  • "Key này sống bao lâu?" — nếu trả lời được bằng một con số (60 giây, 1 giờ, 30 ngày) thì set TTL ngay tại lệnh ghi.
  • "Nếu key này mất đi, hệ thống có còn đúng không?" — với cache, session, lock, OTP, rate-limit window, câu trả lời là "có, hệ thống tự phục hồi". Đó là dấu hiệu dữ liệu nên ephemeral.
  • "Đây có thật sự là source of truth không?" — nếu đúng là nguồn dữ liệu gốc duy nhất (chưa có ở DB khác), cân nhắc kỹ trước khi cho nó hết hạn.

Đặt TTL không chỉ để tiết kiệm RAM. Nó còn là một contract về tính tươi (freshness) của dữ liệu: bạn tuyên bố "sau khoảng thời gian này, giá trị có thể không còn đáng tin, hãy lấy lại từ nguồn gốc". Tư duy này gắn chặt với mọi pattern caching và coordination ở các module sau.

# Phản xạ cũ (database mindset) — key sống mãi
SET user:1001:profile "{...}"

# Phản xạ TTL-First — luôn kèm thời hạn ngay khi ghi
SET user:1001:profile "{...}" EX 3600   # cache 1 giờ
3

Bộ Lệnh TTL Trong Redis 7

Đặt hạn theo khoảng thời gian tương đối. EXPIRE tính bằng giây, PEXPIRE tính bằng mili-giây. Cả hai chỉ tác động lên key đã tồn tại.

SET session:abc "data"
EXPIRE session:abc 1800        # hết hạn sau 1800 giây = 30 phút
PEXPIRE session:abc 1800000    # tương đương, đơn vị mili-giây

Đặt hạn theo mốc thời gian tuyệt đối (Unix timestamp). EXPIREAT dùng giây, PEXPIREAT dùng mili-giây. Hữu ích khi muốn key chết đúng một thời điểm cụ thể, ví dụ "hết hạn lúc 0h sáng mai".

EXPIREAT daily:counter 1748278800       # hết hạn tại timestamp (giây)
PEXPIREAT daily:counter 1748278800000   # cùng thời điểm, đơn vị mili-giây

Set kèm hạn trong một lệnh (atomic). Đây là cách được khuyến nghị vì tránh được race condition giữa SETEXPIRE tách rời. Option EX tính giây, PX tính mili-giây; ngoài ra còn EXAT/PXAT cho timestamp tuyệt đối.

SET otp:0987654321 "382910" EX 60      # OTP sống 60 giây
SET lock:order:42 "node-1" PX 5000     # lock sống 5000 ms

Đọc thời hạn còn lại. TTL trả về số giây, PTTL trả về mili-giây. Hai giá trị đặc biệt cần nhớ: -1 nghĩa là key tồn tại nhưng không có TTL (sống vĩnh viễn); -2 nghĩa là key không tồn tại (đã hết hạn hoặc chưa bao giờ được tạo).

TTL session:abc      # ví dụ: 1792  (còn 1792 giây)
TTL user:1001        # -1  → tồn tại nhưng không có hạn
TTL ghost:key        # -2  → không tồn tại

Bỏ thời hạn. PERSIST gỡ TTL khỏi key, biến nó thành key vĩnh viễn. Trả về 1 nếu gỡ thành công, 0 nếu key không tồn tại hoặc vốn đã không có TTL.

PERSIST session:abc   # 1 → đã gỡ TTL, key giờ sống mãi

Option NX | XX | GT | LT (Redis 7.0+). Bổ sung cho họ lệnh EXPIRE/PEXPIRE/EXPIREAT/PEXPIREAT để đặt hạn có điều kiện, tránh phải đọc TTL rồi mới ghi:

  • NX — chỉ set TTL nếu key chưa có TTL nào.
  • XX — chỉ set TTL nếu key đã có sẵn TTL.
  • GT — chỉ set khi TTL mới lớn hơn hạn hiện tại (gia hạn, không bao giờ rút ngắn). Lưu ý: key không có TTL được coi như "vô hạn", nên GT sẽ không áp lên key vĩnh viễn.
  • LT — chỉ set khi TTL mới nhỏ hơn hạn hiện tại (chỉ cho phép rút ngắn). Với LT, key không có TTL được coi là vô hạn nên TTL mới luôn nhỏ hơn và sẽ được áp.
# Chỉ gia hạn, không bao giờ làm key chết sớm hơn
EXPIRE session:abc 3600 GT

# Chỉ đặt TTL nếu key đang là vĩnh viễn (lần đầu gắn hạn)
EXPIRE config:flag 86400 NX
4

Bẫy Mất TTL Khi SET Ghi Đè — KEEPTTL

Đây là một trong những bẫy hay gặp nhất với người mới. Khi bạn dùng SET để ghi đè một key đang có TTL, Redis mặc định xoá luôn TTL cũ — key trở thành vĩnh viễn. Lý do: SET được hiểu là tạo lại key từ đầu, mọi metadata cũ (bao gồm TTL) bị reset.

SET session:abc "v1" EX 1800   # TTL = 1800
TTL session:abc                # 1800

SET session:abc "v2"           # ghi đè không kèm hạn
TTL session:abc                # -1  → TTL đã BIẾN MẤT, key sống mãi!

Hệ quả trong production: một key cache/session lẽ ra phải tự chết lại tồn tại vĩnh viễn vì một lần ghi đè vô tình. Nhân lên hàng triệu key, đây chính là một nguồn memory leak âm thầm.

Cách giữ TTL: option KEEPTTL (Redis 6.0+). Khi ghi đè giá trị nhưng muốn giữ nguyên hạn cũ, thêm KEEPTTL vào lệnh SET:

SET session:abc "v1" EX 1800
SET session:abc "v2" KEEPTTL   # giữ nguyên TTL còn lại
TTL session:abc                # vẫn ~1800 (trừ thời gian đã trôi qua)

Quy tắc ghi nhớ: khi cập nhật giá trị một key có hạn, hãy chọn một trong hai — hoặc đặt lại hạn mới (EX/PX), hoặc giữ hạn cũ (KEEPTTL). Tuyệt đối tránh SET trần vì nó âm thầm biến key thành vĩnh viễn. Lưu ý KEEPTTLEX/PX/EXAT/PXAT loại trừ lẫn nhau, không dùng chung trong một lệnh.

Một số lệnh khác cũng giữ TTL theo bản chất của chúng (vì chúng sửa giá trị tại chỗ chứ không tạo lại key): INCR/DECR, APPEND, HSET trên hash đã tồn tại, SETRANGE, LPUSH/RPUSH... Còn GETSET (và SET trần) thì xoá TTL. Khi nghi ngờ, hãy kiểm chứng bằng TTL ngay trên redis-cli.

5

Redis Xoá Key Hết Hạn Thế Nào

Một hiểu lầm thường gặp: "TTL về 0 thì key biến mất ngay lập tức và RAM được giải phóng tức thì". Thực tế Redis dùng hai cơ chế kết hợp, và cả hai đều không đảm bảo xoá đúng mili-giây hết hạn.

1. Lazy expiration (passive). Khi một client truy cập key (GET, HGET, ...), Redis kiểm tra xem key đã hết hạn chưa. Nếu rồi, nó xoá key ngay lúc đó và trả về như thể key không tồn tại. Ưu điểm: không tốn CPU nếu key không bao giờ được đụng tới. Nhược điểm: nếu một key hết hạn nhưng không ai truy cập, nó vẫn nằm trong bộ nhớ, tiếp tục chiếm RAM.

2. Active expiration (active). Để dọn những key hết hạn mà không ai đụng tới, Redis chạy một vòng nền định kỳ (mặc định khoảng 10 lần/giây). Mỗi vòng nó lấy mẫu ngẫu nhiên (sampling) một số key trong tập key có TTL, xoá những key đã hết hạn; nếu tỷ lệ key hết hạn trong mẫu còn cao, nó lặp thêm vòng nữa cho tới khi tỷ lệ xuống dưới ngưỡng. Đây là thuật toán xác suất, không quét toàn bộ keyspace để tránh chặn event loop (Redis single-threaded).

Hệ quả thực tế bạn phải nhớ:

  • Một key đã hết hạn vẫn có thể chiếm RAM trong một khoảng (cho tới khi bị truy cập hoặc bị vòng sampling chạm tới). Vì vậy memory không giảm tức thì ngay khi TTL về 0.
  • Nếu rất nhiều key hết hạn cùng lúc, việc dọn có thể trải ra trong nhiều vòng sampling — đây là một lý do nữa để dùng TTL jitter (mục 7).
  • Trên replica, key hết hạn không tự xoá độc lập; replica chờ lệnh DEL được propagate từ master để giữ tính nhất quán. Một lệnh đọc trên replica sẽ không trả về giá trị đã hết hạn (theo logic), nhưng việc giải phóng bộ nhớ phụ thuộc vào master.
  • Đừng dựa vào độ chính xác mili-giây của thời điểm xoá; hãy coi TTL là "key sẽ không còn hợp lệ sau khoảng thời gian này", không phải "key biến mất đúng tích tắc đó".
6

Thiết Kế Ephemeral State Theo Use Case

TTL không phải con số tuỳ tiện — nó phải bám theo ý nghĩa nghiệp vụ của dữ liệu. Dưới đây là các loại ephemeral state phổ biến và TTL điển hình (con số mang tính tham khảo, luôn điều chỉnh theo hệ thống của bạn).

  • Cache — TTL theo độ tươi cho phép của dữ liệu. Dữ liệu ít đổi (cấu hình, danh mục) có thể vài giờ tới 1 ngày; dữ liệu hay đổi (giá, tồn kho) chỉ vài giây tới vài phút. Điển hình: 30 giây – 1 giờ.
  • Session — TTL = thời gian sống mong muốn của một phiên đăng nhập, thường được gia hạn mỗi lần có hoạt động (sliding session). Điển hình: 30 phút – vài giờ; "remember me" có thể tới vài ngày tới vài tuần.
  • Distributed lock — TTL là cơ chế chống deadlock: nếu process giữ lock chết mà không kịp unlock, TTL sẽ tự nhả lock. Phải dài hơn thời gian xử lý tối đa kỳ vọng nhưng không quá dài. Điển hình: vài giây – vài chục giây. Chi tiết ở Module 4.
  • OTP / mã xác thực — TTL ngắn để giảm cửa sổ tấn công. Điển hình: 30 giây – 5 phút.
  • Rate-limit window — TTL bằng đúng độ dài cửa sổ giới hạn; hết cửa sổ thì counter tự reset. Điển hình: 1 giây / 1 phút / 1 giờ tuỳ chính sách. Chi tiết ở Module 3.
  • Idempotency key — TTL đủ dài để chống request trùng trong khoảng retry hợp lý. Điển hình: vài phút – 24 giờ.

Một mẹo thiết kế: ghi TTL như một hằng số có tên trong code (ví dụ SESSION_TTL_SECONDS = 1800) thay vì hard-code số rải rác. Vừa dễ đọc, vừa dễ tinh chỉnh tập trung khi cần.

7

TTL Jitter — Chống Hết Hạn Đồng Loạt

Hãy tưởng tượng bạn warm-up cache cho 10.000 sản phẩm cùng một lúc, tất cả với EX 3600. Đúng 1 giờ sau, cả 10.000 key hết hạn cùng một thời điểm. Ngay sau đó, một loạt request đồng thời không tìm thấy key trong cache và cùng dồn xuống database để lấy lại dữ liệu — hiện tượng cache stampede (thundering herd). Database có thể quá tải chỉ vì các key chết đồng bộ.

TTL jitter là kỹ thuật thêm một lượng ngẫu nhiên vào TTL để làm thời điểm hết hạn của các key bị phân tán ra, thay vì dồn về một mốc. Ý tưởng cốt lõi:

ttl = base_ttl + random(0, jitter_range)

Hoặc dạng theo tỷ lệ phần trăm (thường gặp hơn vì co giãn theo base):

ttl = base_ttl × (1 + random(-jitter_pct, +jitter_pct))

Ví dụ với base_ttl = 3600 giây và jitter ±10%: mỗi key nhận một TTL ngẫu nhiên trong khoảng 3240 – 3960 giây. Thay vì cùng chết tại mốc 3600, các key giờ trải đều trên một cửa sổ ~720 giây, làm phẳng đỉnh tải khi cache miss.

# Minh hoạ ý tưởng — TTL khác nhau cho từng key
SET product:1 "..." EX 3540
SET product:2 "..." EX 3712
SET product:3 "..." EX 3399
# ... mỗi key một con số trong khoảng [3240, 3960]

Đây mới chỉ là tuyến phòng thủ đầu tiên và rẻ nhất chống stampede. Các kỹ thuật mạnh hơn (mutex lock, stale-while-revalidate) sẽ được đào sâu ở Module 1. Code tính TTL có jitter cho cả TS và Python ở mục tiếp theo.

8

Code ioredis & redis-py

TypeScript với ioredis. Lưu ý cú pháp truyền option EX, KEEPTTL; ttl() trả về số (giây, hoặc -1/-2).

import Redis from "ioredis";

const redis = new Redis();

const SESSION_TTL = 1800; // 30 phút — hằng số có tên

async function demo() {
  // 1. SET kèm hạn (atomic) — ephemeral by default
  await redis.set("session:abc", "v1", "EX", SESSION_TTL);

  // 2. Đọc TTL còn lại
  console.log(await redis.ttl("session:abc")); // ~1800
  console.log(await redis.ttl("ghost:key"));   // -2 (không tồn tại)

  // 3. EXPIRE tách rời (chỉ áp lên key đã tồn tại)
  await redis.set("user:1001:profile", "{...}");
  await redis.expire("user:1001:profile", 3600);

  // 4. Bẫy: SET trần xoá TTL
  await redis.set("session:abc", "v2");          // mất TTL!
  console.log(await redis.ttl("session:abc"));   // -1 (vĩnh viễn)

  // 5. Giữ TTL khi ghi đè bằng KEEPTTL
  await redis.set("session:abc", "v3", "EX", SESSION_TTL);
  await redis.set("session:abc", "v4", "KEEPTTL");
  console.log(await redis.ttl("session:abc"));   // vẫn ~1800

  // 6. Option GT (Redis 7) — chỉ gia hạn, không rút ngắn
  await redis.expire("session:abc", 3600, "GT");

  // 7. Gỡ TTL
  await redis.persist("session:abc");            // -> key vĩnh viễn
}

// TTL có jitter theo phần trăm: tránh hết hạn đồng loạt
function ttlWithJitter(baseTtl: number, jitterPct = 0.1): number {
  // hệ số ngẫu nhiên trong [1 - jitterPct, 1 + jitterPct]
  const factor = 1 + (Math.random() * 2 - 1) * jitterPct;
  return Math.max(1, Math.round(baseTtl * factor));
}

async function cacheProduct(id: number, json: string) {
  const ttl = ttlWithJitter(3600, 0.1); // ~[3240, 3960]
  await redis.set(`product:${id}`, json, "EX", ttl);
}

demo();

Python với redis-py. Trong redis-py, set() nhận ex= (giây), px= (mili-giây), keepttl=True; ttl() trả về số nguyên (-1/-2 như Redis). Option NX/XX/GT/LT của expire() có từ redis-py 4.x.

import random
import redis

r = redis.Redis(decode_responses=True)

SESSION_TTL = 1800  # 30 phút — hằng số có tên

# 1. SET kèm hạn (atomic)
r.set("session:abc", "v1", ex=SESSION_TTL)

# 2. Đọc TTL còn lại
print(r.ttl("session:abc"))  # ~1800
print(r.ttl("ghost:key"))    # -2 (không tồn tại)

# 3. EXPIRE tách rời
r.set("user:1001:profile", "{...}")
r.expire("user:1001:profile", 3600)

# 4. Bẫy: SET trần xoá TTL
r.set("session:abc", "v2")        # mất TTL!
print(r.ttl("session:abc"))       # -1 (vĩnh viễn)

# 5. Giữ TTL khi ghi đè bằng keepttl=True (KEEPTTL)
r.set("session:abc", "v3", ex=SESSION_TTL)
r.set("session:abc", "v4", keepttl=True)
print(r.ttl("session:abc"))       # vẫn ~1800

# 6. Option GT (Redis 7) — chỉ gia hạn, không rút ngắn
r.expire("session:abc", 3600, gt=True)

# 7. Gỡ TTL
r.persist("session:abc")          # -> key vĩnh viễn


def ttl_with_jitter(base_ttl: int, jitter_pct: float = 0.1) -> int:
    """TTL có jitter theo phần trăm để tránh hết hạn đồng loạt."""
    factor = 1 + random.uniform(-jitter_pct, jitter_pct)
    return max(1, round(base_ttl * factor))


def cache_product(product_id: int, payload: str) -> None:
    ttl = ttl_with_jitter(3600, 0.1)  # ~[3240, 3960]
    r.set(f"product:{product_id}", payload, ex=ttl)


cache_product(1, "{...}")
9

Trade-off & Cách Cân TTL

Chọn TTL là một bài toán cân bằng, không có con số đúng tuyệt đối. Hai cực đoan đều có giá phải trả:

  • TTL quá ngắn → cache hết hạn liên tục → tỷ lệ cache miss cao → nhiều request dồn xuống database → tăng tải DB, tăng latency. Hit rate thấp đồng nghĩa cache gần như vô dụng.
  • TTL quá dài → dữ liệu dễ stale (lỗi thời) so với nguồn gốc → người dùng thấy dữ liệu cũ; đồng thời key tồn tại lâu → chiếm RAM lâu hơn, đẩy nguy cơ OOM/eviction.

Cách cân thực tế dựa trên nghiệp vụ:

  • Hỏi: "Dữ liệu này cũ tối đa bao lâu thì người dùng vẫn chấp nhận được?" Đó là trần trên của TTL.
  • Hỏi: "Database chịu được bao nhiêu request/giây khi cache miss?" Điều này gợi ý sàn dưới của TTL và mức độ cần jitter.
  • Đo hit rate thực tế sau khi triển khai và điều chỉnh dần. Hit rate quá thấp → tăng TTL; dữ liệu stale gây phàn nàn → giảm TTL hoặc thêm cơ chế invalidation chủ động.
  • Tách TTL theo loại dữ liệu thay vì một con số chung: dữ liệu nóng/ít đổi để dài, dữ liệu thay đổi thường xuyên để ngắn.

Ghi nhớ: TTL chỉ là một trong nhiều công cụ giữ tính tươi. Khi cần consistency mạnh hơn, kết hợp với invalidation chủ động (xoá/cập nhật key khi nguồn thay đổi) — chủ đề của Module 1.

10

Pitfalls & Anti-patterns

  • Quên set TTL (nguyên nhân #1 gây memory leak / OOM). Mỗi key không có hạn là một "rác vĩnh viễn" tiềm năng. Với hệ thống tạo key động (theo user, theo request), thiếu TTL là cách chắc chắn nhất để RAM phình tới khi Redis sập hoặc eviction policy bắt đầu đẩy key quan trọng ra ngoài.
  • SET ghi đè làm mất TTL (quên KEEPTTL). Như mục 4: SET trần biến key có hạn thành vĩnh viễn. Khi cập nhật giá trị một key ephemeral, luôn dùng KEEPTTL hoặc đặt lại EX/PX.
  • Set TTL âm hoặc bằng 0 → xoá key ngay. EXPIRE key 0 hay EXPIRE key -1 không phải "set vĩnh viễn" — Redis hiểu là đã hết hạn và xoá key lập tức. Tương tự, SET ... EX 0 sẽ lỗi. Hãy kiểm tra biến TTL trước khi truyền: một lỗi tính toán cho ra số 0 hoặc số âm có thể vô tình xoá dữ liệu.
  • Giả định key biến mất đúng mili-giây nó hết hạn. Vì lazy + active expiration (mục 5), key đã hết hạn vẫn có thể tồn tại trong RAM một lúc. Đừng dùng "key còn tồn tại" làm tín hiệu thời gian chính xác; logic nghiệp vụ phụ thuộc thời điểm nên dùng timestamp lưu trong giá trị, không dựa vào thời điểm xoá vật lý.
  • Trông chờ memory giảm tức thì sau khi key hết hạn. Monitoring memory có thể không phản ánh ngay việc key đã "logic hết hạn". Nếu cần giải phóng dứt khoát, DEL/UNLINK chủ động.
  • Set TTL rồi quên ngay sau đó RENAME. RENAME giữ TTL của key nguồn cho key đích — điểm tốt — nhưng nhiều lệnh tạo lại key (như SET trần, GETSET) thì xoá TTL. Khi nghi ngờ một lệnh có giữ TTL không, kiểm chứng bằng TTL trên redis-cli.
  • Dùng cùng base TTL cho hàng loạt key warm-up. Gây cache stampede khi chúng hết hạn đồng loạt. Luôn thêm jitter (mục 7).
11

Tổng Kết & Quiz

Tổng kết

  • Ephemeral by default: mỗi lần ghi vào Redis, hỏi "key này sống bao lâu?" và set TTL nếu có câu trả lời.
  • Bộ lệnh TTL: EXPIRE/PEXPIRE (tương đối), EXPIREAT/PEXPIREAT (tuyệt đối), SET ... EX|PX (atomic), đọc bằng TTL/PTTL (-1 = vĩnh viễn, -2 = không tồn tại), bỏ hạn bằng PERSIST. Redis 7 thêm NX|XX|GT|LT.
  • SET trần xoá TTL khi ghi đè — dùng KEEPTTL (Redis 6.0+) để giữ.
  • Redis dùng lazy + active (sampling) expiration → key hết hạn vẫn có thể chiếm RAM tới khi bị quét/truy cập; memory không giảm tức thì.
  • TTL điển hình: cache 30s–1h, session 30 phút–vài giờ, lock vài–vài chục giây, OTP 30s–5 phút, rate-limit = độ dài cửa sổ.
  • TTL jitter (±%) phân tán thời điểm hết hạn, chống cache stampede.
  • Trade-off: TTL ngắn → nhiều cache miss; TTL dài → stale + tốn RAM. Cân theo nghiệp vụ và đo hit rate.

Quiz 5 câu

  1. Sau SET k "a" EX 100 rồi SET k "b" (không option), TTL k trả về gì và vì sao?
  2. TTL trả -1-2 khác nhau như thế nào?
  3. Giải thích sự khác nhau giữa lazy expiration và active expiration. Vì sao một key đã hết hạn vẫn có thể chiếm RAM?
  4. Bạn warm-up 5000 key cùng lúc với EX 3600. Vấn đề gì sẽ xảy ra sau 1 giờ và kỹ thuật nào khắc phục?
  5. Option GT của EXPIRE làm gì? Nó áp dụng thế nào với một key vốn không có TTL?

Đáp án gợi ý

  1. Trả về -1. SET trần ghi đè coi như tạo lại key nên xoá TTL cũ, key thành vĩnh viễn. Muốn giữ hạn phải dùng SET k "b" KEEPTTL.
  2. -1: key tồn tại nhưng không có TTL (vĩnh viễn). -2: key không tồn tại (đã hết hạn hoặc chưa từng tạo).
  3. Lazy: xoá key khi có client truy cập và phát hiện đã hết hạn. Active: vòng nền định kỳ lấy mẫu ngẫu nhiên các key có TTL để xoá. Vì cả hai không quét toàn bộ ngay tức thì, một key hết hạn mà không ai truy cập sẽ chờ tới lượt sampling chạm tới mới được xoá → vẫn chiếm RAM tạm thời.
  4. Cả 5000 key hết hạn cùng lúc → loạt cache miss đồng thời dồn xuống database (cache stampede). Khắc phục bằng TTL jitter để phân tán thời điểm hết hạn (và mạnh hơn là mutex lock / stale-while-revalidate).
  5. GT chỉ set TTL mới khi nó lớn hơn TTL hiện tại (chỉ gia hạn, không rút ngắn). Với key không có TTL, Redis coi như TTL vô hạn nên TTL mới không thể lớn hơn → GT không áp dụng (key vẫn vĩnh viễn).

Bài tiếp theo

Sau khi nắm tư duy dữ liệu có thời hạn, bài tiếp theo sẽ điểm qua toàn cảnh các kiểu dữ liệu Redis để chọn đúng cấu trúc cho từng bài toán: Bài 5: Tổng Quan Redis Data Structures.

Tham khảo