Giao diện
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ược | Cách chạy | Rủi ro chính | Dùng khi |
|---|---|---|---|
| Cache-aside | App đọc cache; miss thì đọc database rồi SET vào cache. Khi ghi: ghi database, commit, rồi xoá key | Dữ 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-through | App chỉ hỏi cache; lớp cache tự gọi hàm nạp (loader) khi miss | App phụ thuộc lớp cache | Cache trong process (Caffeine LoadingCache), DynamoDB DAX, CDN |
| Write-through | Ghi qua cache, cache ghi database đồng bộ | Ghi chậm hơn (hai nơi); cache chứa dữ liệu ít ai đọc | Dữ liệu được đọc ngay sau khi ghi |
| Write-around | Chỉ ghi database; lần đọc sau mới nạp cache | Miss ngay sau khi ghi | Ghi 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ống | Counter, thống kê chịu được mất mát nhỏ |
| Refresh-ahead | Làm mới key sắp hết hạn trước khi nó hết hạn | Tốn công làm mới cả key không còn ai đọc | Key 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:
- 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.
- 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ũ.
- 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ớ
- Cache-aside là mặc định: đọc miss thì app nạp; ghi thì ghi database rồi xoá key.
- Write-back nhanh nhất nhưng có thể mất dữ liệu; write-through ghi chậm hơn.
- TTL có jitter cho mọi key; đổi định dạng thì đổi version trong tên key.