Skip to content

Chiến lược đọc ghi ​

Mỗi chiến lược trả lời hai câu: khi đọc mà cache không có thì ai đi lấy dữ liệu, và khi ghi thì cache được cập nhật thế nào.

Chiến lượcCách chạyRủi ro chínhDùng khi
Cache-asideApp đọc cache; miss thì đọc database rồi SET vào cache. Khi ghi: ghi database, commit, rồi xoá keyDữ liệu cũ nếu xoá sai thứ tự (Nhất quán); stampede (Sự cố)Mặc định cho web backend. Cache sập vẫn chạy được
Read-throughApp chỉ hỏi cache; lớp cache tự gọi hàm nạp (loader) khi missApp phụ thuộc lớp cacheCache trong process (Caffeine LoadingCache), DynamoDB DAX, CDN
Write-throughGhi qua cache, cache ghi database đồng bộGhi chậm hơn (hai nơi); cache chứa dữ liệu ít ai đọcDữ liệu được đọc ngay sau khi ghi
Write-aroundChỉ ghi database; lần đọc sau mới nạp cacheMiss ngay sau khi ghiGhi nhiều, ít đọc lại
Write-back (write-behind)Chỉ ghi cache; cache ghi xuống database theo lôMất dữ liệu nếu cache sập trước khi ghi xuốngCounter, thống kê chịu được mất mát nhỏ
Refresh-aheadLàm mới key sắp hết hạn trước khi nó hết hạnTốn công làm mới cả key không còn ai đọcKey nóng, query nặng; X-Fetch là một cách làm (Sự cố)

Cache-aside trong code ​

js
// đọc
async function getProduct(id) {
  const key = `product:${id}`
  const cached = await redis.get(key)
  if (cached !== null) return JSON.parse(cached)

  const row = await db.query('SELECT … FROM products WHERE id = ?', [id])
  const ttl = 300 + Math.floor(Math.random() * 60)   // TTL có jitter
  await redis.set(key, JSON.stringify(row), 'EX', ttl)
  return row
}

// ghi
async function updatePrice(id, price) {
  await db.query('UPDATE products SET price = ? WHERE id = ?', [price, id])  // commit trước
  await redis.del(`product:${id}`)                                          // rồi mới xoá key
}

Ba điểm quan trọng:

  1. Xoá key, không SET giá trị mới khi ghi. Hai writer song song SET chen nhau có thể để lại giá trị cũ; xoá thì luôn an toàn. Xem mô phỏng ở Nhất quán dữ liệu.
  2. Ghi database trước, xoá cache sau. Làm ngược lại thì một reader chen vào giữa sẽ nạp lại giá trị cũ.
  3. Luôn có TTL, thêm phần ngẫu nhiên. TTL là lưới an toàn cuối cùng: mọi sai lệch đều tự hết sau tối đa một TTL.

Đổi định dạng dữ liệu trong cache ​

Đổi cấu trúc object (thêm trường, đổi cách serialize) mà giữ nguyên tên key thì phiên bản code mới có thể đọc phải dữ liệu cũ. Đặt version vào tên key (v2:product:42) và tăng version khi đổi định dạng; key cũ tự hết hạn.

Ghi nhớ

  1. Cache-aside là mặc định: đọc miss thì app nạp; ghi thì ghi database rồi xoá key.
  2. Write-back nhanh nhất nhưng có thể mất dữ liệu; write-through ghi chậm hơn.
  3. TTL có jitter cho mọi key; đổi định dạng thì đổi version trong tên key.