DDevArchive
Đăng nhập

Caching với Redis: cache-aside, stampede, invalidation

Cache là thứ làm endpoint chậm thành nhanh chỉ sau một đêm. Nhưng cache sai cách còn tệ hơn không cache: dữ liệu cũ, thất thoát khi Redis chết, và cache stampede khi key đồng loạt hết hạn.

Cache-aside: đọc cache trước, DB sau

// File: cache-aside.js
async function getCourse(id) {
  const hit = await redis.get(`course:${id}`);
  if (hit) return JSON.parse(hit);         // cache hit

  const row = await db.course.findUnique({ where: { id } });
  if (row) {
    await redis.set(`course:${id}`, JSON.stringify(row), {
      EX: 300,                              // hết hạn sau 5 phút
    });
  }
  return row;
}

Cache stampede: khi ngàn request cùng miss

Key hết hạn lúc 12:00. 3000 request cùng miss, cùng lao vào DB — đó là DDoS tự gây. Chống bằng khoá nạp lại (SET NX) hoặc hết hạn sớm theo xác suất.

// File: stampede.js
async function getWithLock(key, loader) {
  const hit = await redis.get(key);
  if (hit) return JSON.parse(hit);

  const got = await redis.set(key + ':lock', '1', { NX: true, EX: 10 });
  if (!got) {
    await sleep(50);                // người khác đang nạp, chờ
    return getWithLock(key, loader);
  }
  const data = await loader();
  await redis.set(key, JSON.stringify(data), { EX: 300 });
  await redis.del(key + ':lock');
  return data;
}
💡 Thêm nhiễu vào TTL

Nếu 10.000 key đều TTL 300s, đúng giây thứ 300 chúng hết hạn cùng lúc. Thêm EX: 300 + Math.floor(Math.random() * 60) để dàn trải.

Invalidation: xoá cache đúng lúc

Xoá cache sau khi transaction commit, không phải trước. Xoá trước sẽ có cửa sổ request khác nạp lại giá trị cũ vào cache.

// File: invalidate.js
await db.$transaction(async (tx) => {
  await tx.course.update({ where: { id }, data });
});
await redis.del(`course:${id}`);      // sau khi commit
await redis.del("courses:list:v1");  // cũng xoá cả danh sách

Ba chiến lược, ba mức độ phức tạp

Chiến lượcĐộ tươiChi phí vận hành
TTL ngắn, không xoá thủ côngTrễ tối đa bằng TTLThấp nhất
Xoá theo key khi ghiGần như tức thìPhải biết mọi key liên quan
Đánh phiên bản khoáTức thìTốn bộ nhớ cho key cũ

❓ Key cache hết hạn 12:00, cùng lúc 3000 request tới. Vấn đề chính là gì?

  • Triển khai cache-aside có TTL
  • Chống stampede bằng SET NX hoặc TTL nhiễu
  • Xoá cache sau khi commit
  • Chọn chiến lược invalidation phù hợp tần suất ghi