Lập trình 22/09/2026 9 phút

Cache-aside: mẫu cache phổ biến nhất, đo thật hit ratio leo từ 37,6% lên 95%

Cache-aside là mẫu cache gần như mọi hệ thống đều dùng: đọc cache trước, miss thì lấy từ database rồi điền vào. Bài này dựng nó bằng Go + Redis + PostgreSQL và đo thật: 20.000 request trên tập 1.000 sản phẩm, hit ratio leo từ 37,6% (mốc 1.000 request) lên 95%, và số truy vấn database đứng yên ở 1.000 — tiết kiệm 95% lượt chạm DB. Nói thẳng: query có index nên MISS chỉ chậm hơn HIT khoảng 4 lần, cái thắng lớn không nằm ở độ trễ một request. Kèm đo lỗi xuyên thủng cache: 200 id không tồn tại khiến 5.000/5.000 request đều chạm database.

Lập trình 22/09/2026 9 phút

TTL và hết hạn: đo thật độ cũ của cache bằng TTL/2, và cơn bão miss do TTL đồng loạt

TTL không phải là 'cache bao lâu' — nó là lời hứa về mức độ cũ tối đa của dữ liệu bạn phục vụ. Bài này đo thật trên Go + Redis + PostgreSQL: cho giá đổi trong DB mỗi 200ms rồi đọc qua cache với bốn TTL khác nhau. Kết quả: TTL 10s cho độ lệch trung bình 5,0 giây dữ liệu và chỉ 1 lần chạm DB; TTL 100ms cho 0,2 giây lệch nhưng 97 lần chạm DB — tăng TTL 100 lần đổi được 97 lần ít tải DB bằng đúng 25 lần cũ hơn. Và đo thêm cái bẫy TTL đồng loạt: 1.000 key nạp cùng lúc hết hạn dồn vào đúng một giây, thêm nhiễu ±20% thì đỉnh giảm còn 266.

Lập trình 22/09/2026 9 phút

Cache stampede: đo thật 500 request cùng miss một key, cả 500 lao thẳng xuống database

Khi một key nóng biến mất — hết TTL hoặc bị đá — mọi request đang hỏi nó đều miss cùng lúc và đều đi tiếp xuống database. Đo thật trên Go + Redis + PostgreSQL: truy vấn gốc chạy một mình hết 6,2ms; cho 500 request cùng xuất phát thì cả 500 đều chạm database và request xấu nhất mất 1119,1ms — chậm hơn 180 lần, dù pool database chỉ có 50 kết nối. Kèm luật R × D tính trước quy mô stampede, và một cái bẫy khi đo khiến phép đo đầu tiên che mất gần một nửa hiện tượng.

Lập trình 22/09/2026 8 phút

Cache invalidation: đo thật đảo thứ tự hai dòng code làm tỉ lệ đọc sai nhảy từ 7,26% lên 66,82%

Cache-aside chỉ dựa vào TTL thì dữ liệu cũ tới cả phút. Xoá cache khi ghi là cách chữa — nhưng xoá vào lúc nào thì quan trọng hơn người ta tưởng. Đo thật trên Go + Redis + PostgreSQL với TTL 300s để mọi tính mới phải đến từ invalidation: ghi DB rồi xoá cache cho 7,26% lượt đọc sai và cache luôn khớp lại sau đó; đảo lại thành xoá cache rồi ghi DB cho 66,82% và cache KẸT ở giá trị sai cho tới khi TTL hết. Hai con số đó còn là hai loại lỗi khác hẳn nhau.

Lập trình 22/09/2026 8 phút

Write-through vs write-back: đo thật write-back nhanh gấp 3, ghi DB ít đi 5 lần, và mất 19,4% dữ liệu khi chết

Bảy phần trước đều để cache đứng ngoài đường ghi. Bài này đưa nó vào thẳng đường ghi và đo trên Go + Redis + PostgreSQL với 20.000 lượt tăng bộ đếm: write-through tốn 0,125ms mỗi lượt và vẫn 20.000 lệnh ghi xuống database, nhưng đổi lại 100% hit khi đọc ngay sau đó (write-around chỉ 80,2%). Write-back gộp lô cho 0,038ms và chỉ 3.926 lệnh ghi — nhanh gấp 3, ít đi 5 lần. Cái giá đo được: cho tiến trình chết trước lúc dồn nốt thì 3.882 lượt mất trắng, 19,41%.

Lập trình 22/09/2026 9 phút

Tổng kết Caching: gom 11 phép đo thành một hàm, DB giảm 19 lần và Redis giảm 60 lần

Phần cuối loạt Caching gom mọi thứ đã đo thành một hàm đọc duy nhất — L1 trong tiến trình, singleflight, khoá SET NX, dấu vắng mặt, TTL có nhiễu — rồi đo nó cạnh cache-aside ngây thơ dưới cùng một lưu lượng: 32 người đọc song song, Zipf trên 5000 key, 5% là bot quét id không tồn tại. Kết quả: truy vấn database từ 101.739 xuống 5.300 (đúng sàn lý thuyết), lưu lượng Redis từ 100% xuống 0,1%, p99 từ 0,283ms xuống 0,001ms — đổi lại đuôi khởi động nguội xấu hơn, 388,7ms so với 21,5ms. Kèm khung quyết định và checklist rà soát trước khi lên production.