Cache là gì và khi nào nên dùng
Bắt đầu từ câu hỏi "dữ liệu này cũ bao lâu thì chấp nhận được?"
Đây là bài đầu tiên của khóa, nên ta chưa viết code vội. Mục tiêu hẹp hơn: hiểu cache là gì và ra được quyết định "endpoint này có nên cache hay không". Khi đã trả lời được câu hỏi đó, việc dựng luồng cache-aside ở bài sau sẽ chỉ còn là ráp các bước lại với nhau.
Sau bài này bạn sẽ:
- Giải thích cache bằng một câu, không cần thuật ngữ.
- Chỉ ra được cache hit và cache miss trong một luồng request.
- Trả lời ba câu hỏi quyết định cho một endpoint cụ thể.
- Kể được vài trường hợp rõ ràng là không nên cache.
Cache là gì, gói trong một câu
Cache là _____.
Điểm dễ hiểu sai nhất: cache không làm dữ liệu nhanh hơn. Nó tránh việc phải tính lại. Mỗi lần lấy được từ cache, bạn bỏ qua được một lần query DB, một lần gọi API bên ngoài, một lần render template. Cái giá phải trả là dữ liệu có thể cũ hơn sự thật ở nguồn gốc.
Nhìn cache bằng chiếc tủ lạnh
Bạn đang nấu ăn. Đi chợ (query DB) mất 30 phút, mở tủ lạnh (cache) mất 5 giây. Vì vậy bạn mua một lần rồi trữ trong tủ.
Ba điều rút ra từ cái tủ lạnh:
- Đồ trong tủ có hạn dùng. Không ai để thịt trong đó ba tuần rồi vẫn tin là tươi. Hạn dùng đó chính là TTL.
- Tủ có dung tích hữu hạn. Bạn không nhét cả quầy thịt vào. Cache cũng có giới hạn bộ nhớ.
- Nếu bạn chỉ nấu món đó một lần mỗi tháng, cái tủ chẳng giúp gì, chỉ tốn điện.
flowchart TD
A["🙋<br/>Yêu cầu dữ liệu"] --> B{"🔍<br/>Có trong cache?"}
B -->|Có| C["⚡<br/>Trả ngay"]
B -->|Không| D["🐢<br/>Tính lại từ nguồn"]
D --> E["📦<br/>Ghi vào cache"]
E --> C
Nhánh "Có" là _____. Nhánh "Không" là _____, và mỗi lần miss là một lần bạn trả đủ giá của nguồn chậm.
Ba câu hỏi trước khi bật cache
Đây là phần tôi luôn vẽ ra bảng trắng trước khi sửa code. Với đúng một endpoint, hỏi ba câu:
- Đọc nhiều không? Cache chỉ có lợi khi tỉ lệ đọc lớn hơn ghi rất nhiều, ví dụ 100 đọc cho 1 ghi. Nếu endpoint đọc vài lần mỗi giờ, bạn thêm một tầng phải bảo trì mà gần như không nhận lại gì.
- Dữ liệu cũ bao lâu thì chấp nhận được? Đây là câu hỏi với sản phẩm, không phải câu hỏi kỹ thuật. Giá sản phẩm cũ 5 phút thường không sao. Số dư ví cũ 5 phút là sự cố.
- Tính lại đắt không? Đắt ở đây đo bằng thời gian, bằng tiền, hoặc bằng rate limit của bên thứ ba. Một query join 4 bảng mất 800ms thì mỗi lần miss là 800ms người dùng phải chờ.
Ba câu này chính là _____. Cả ba cùng nghiêng về "có" thì cache đáng làm. Chỉ cần một câu trả lời "không" mạnh, hãy khoan.
Ba ví dụ thật
| Endpoint | Đọc / ghi | Cũ bao lâu còn ổn | Chi phí tính lại | Kết luận |
|---|---|---|---|---|
GET /products/123 |
Rất nhiều đọc, ghi vài lần mỗi ngày | 5 phút là ổn | Join 3 bảng, ~600ms | Nên cache |
GET /me/wallet |
Đọc nhiều nhưng ghi mỗi giao dịch | Gần như 0 | Một query nhanh | Không cache ở tầng này |
GET /exchange-rates |
Đọc cực nhiều, đọc chung cho mọi người | 60 giây là ổn | API ngoài chậm, có rate limit | Nên cache, TTL ngắn |
GET /search?q=... |
Đọc nhiều nhưng mỗi từ khóa một khác | Tùy | Query nặng | Khó, vì gần như luôn miss |
Đọc bảng theo hàng:
GET /products/123: đọc nhiều, dữ liệu cũ vài phút không ai phàn nàn, tính lại đắt. Ba trên ba. Cache với TTL 5 phút là quyết định rõ ràng.
GET /me/wallet: đọc cũng nhiều, nhưng dòng "cũ bao lâu còn ổn" trả lời là gần như không. Người dùng vừa chuyển tiền mà thấy số dư cũ sẽ gọi support ngay. Ở đây ta ưu tiên đúng, không ưu tiên nhanh.
GET /exchange-rates: đây là kiểu cache đẹp nhất, vì dữ liệu dùng chung cho mọi người. Một lần gọi API ngoài phục vụ được hàng nghìn request. Tỉ giá cũ 60 giây là chuyện bình thường.
GET /search?q=...: nghe có vẻ nên cache nhưng số lượng từ khóa gần như vô hạn, nên tỉ lệ trúng rất thấp. Trừ khi bạn chỉ cache vài chục truy vấn phổ biến nhất.
Những chỗ nên tránh cache
- Dữ liệu mà tính đúng quan trọng hơn tốc độ: số dư, tồn kho dùng để quyết định cho mua hay không.
- Endpoint ghi như
POST,PUT,DELETE. Không có gì để đọc lại ở đây. - Dữ liệu riêng của từng user mà mỗi người chỉ đọc một lần. Bạn sẽ giữ trong bộ nhớ hàng triệu bản sao không ai dùng lại.
- Dữ liệu đổi mỗi giây trong khi lượng đọc rất thấp. Bạn vừa ghi vào đã phải xóa ra.
- Trường hợp bạn chưa đo được nguồn chậm nằm ở đâu. Cache đắp lên một chỗ chậm chưa xác định chỉ làm hệ thống khó hiểu hơn.
Mẹo và lỗi thường gặp khi quyết định cache
- Đo trước, cache sau. Một dòng log thời gian query cho bạn biết có đáng cache hay không. Cache theo cảm giác là cách nhanh nhất để thêm một tầng phức tạp mà không ai dám xóa.
- Chọn TTL ngay lúc thiết kế. Nếu bạn không trả lời được "dữ liệu này cũ bao lâu thì chấp nhận được", bạn chưa sẵn sàng cache.
- Đừng dùng cache để che một query tệ. Query vẫn chạy 800ms mỗi lần miss và dữ liệu vẫn cũ. Sửa index rẻ hơn nhiều.
- Hỏi người làm sản phẩm về độ tươi, đừng tự đoán. Kỹ sư thường chọn TTL ngắn hơn mức cần thiết vì sợ, và mất đi phần lớn lợi ích.
- Nhớ rằng mọi cache đều có lúc sai. Câu hỏi không phải "làm sao để không bao giờ cũ", mà là "cũ trong bao lâu thì chấp nhận được, và ai chịu trách nhiệm dọn nó".
Chốt lại và bài tiếp theo
- Cache là bản sao dữ liệu ở nơi đọc nhanh hơn, đổi lấy việc chấp nhận dữ liệu có thể cũ.
- Quyết định cache dựa trên ba yếu tố: tần suất đọc, độ tươi cần thiết, chi phí tính lại.
- Cache hit là trúng, cache miss là phải trả đủ giá của nguồn chậm.
- Dữ liệu cần đúng tức thời thì đừng cache, đừng vì "thấy ai cũng cache".
Bài sau ta sẽ dựng chính cái sơ đồ ở trên thành luồng cache-aside hoàn chỉnh: đọc đi đường nào, ghi thì xóa cache lúc nào, và thứ tự nào thì sai.
Front: Cache là gì, trong một câu? Back: Bản sao dữ liệu đặt ở nơi đọc nhanh hơn, chấp nhận có thể cũ hơn nguồn gốc.
Front: Ba câu hỏi quyết định một endpoint có nên cache hay không? Back: Tần suất đọc, độ tươi cần thiết của dữ liệu, chi phí tính lại.
Front: Cache thực chất giúp gì cho hệ thống? Back: Tránh phải tính lại dữ liệu, chứ không làm nguồn dữ liệu nhanh hơn.
Front: Cache hit là gì? Back: Yêu cầu tìm thấy dữ liệu trong cache và trả về ngay, không chạm nguồn chậm.
Front: Cache miss là gì? Back: Không có trong cache, phải đọc từ nguồn chậm rồi ghi lại vào cache.
Front: Endpoint đọc rất ít thì cache có đáng không? Back: Không, gần như không có lợi mà vẫn thêm một tầng phải bảo trì.
Front: Tỉ lệ đọc/ghi thế nào thì cache mới có lợi? Back: Khi đọc nhiều hơn ghi rất nhiều, ví dụ khoảng 100 đọc cho 1 ghi.
Front: Số dư ví của người dùng có nên cache 5 phút không? Back: Không, dữ liệu này cần đúng gần như tức thời, tốc độ không quan trọng bằng tính đúng.
Front: Vì sao endpoint có tham số gần như vô hạn như tìm kiếm tự do lại khó cache? Back: Số khóa quá lớn nên tỉ lệ trúng rất thấp, trừ khi chỉ cache vài truy vấn phổ biến.
Front: Tỉ giá hối đoái là kiểu cache đẹp vì sao? Back: Dữ liệu dùng chung cho mọi người, một lần gọi nguồn phục vụ được rất nhiều request.
Front: Câu hỏi về độ tươi của dữ liệu nên hỏi ai? Back: Hỏi người làm sản phẩm, vì đó là quyết định về sản phẩm chứ không phải kỹ thuật.
Front: Vì sao không nên dùng cache để che một query chậm? Back: Query vẫn chậm mỗi lần miss và dữ liệu vẫn cũ, sửa nguyên nhân thường rẻ hơn.
Front: Ngoài bản thân dữ liệu, một cache còn cần thông tin gì? Back: Thời điểm hết hạn (TTL), để biết khi nào phải bỏ bản sao đó đi.
Vài chỗ để tự kiểm tra: cache là _____, và mỗi lần không tìm thấy trong cache được gọi là _____.