Chuyển tới nội dung chính

Cách Caching trong Cơ sở dữ liệu Hoạt động

Caching là gì?

Caching (Lưu nhanh) lưu trữ dữ liệu được truy cập thường xuyên (Frequently Accessed Data) vào một lớp nhanh hơn và gần hơn (Faster, Closer Layer) để các yêu cầu sau bỏ qua nguồn chậm hơn. Đối với cơ sở dữ liệu, điều này có nghĩa là tránh I/O đĩa thừa (Redundant Disk I/O) và không cần thực hiện lại các truy vấn tốn kém (Expensive Queries).

Tại sao cần Caching?

Vấn đềCaching giúp ích như thế nào
Đọc đĩa chậm (Slow Disk Reads)Dữ liệu trong RAM nhanh hơn SSD/HDD nhiều bậc
Truy vấn lặp lại (Repeated Queries)Các truy vấn giống nhau trả về kết quả đã tính toán sẵn mà không cần thực hiện lại
Tải DB cao (High DB Load)Ít truy vấn đến cơ sở dữ liệu → dư dung lượng cho các thao tác phức tạp
Đọc nhạy cảm độ trễ (Latency-sensitive Reads)Thời gian phản hồi giảm từ mili-giây xuống micro-giây

Thuật ngữ quan trọng (Key Terminology)

  • Cache Hit (Trúng Cache) — dữ liệu được tìm thấy trong Cache (đường dẫn nhanh)
  • Cache Miss (Trượt Cache) — dữ liệu không có trong Cache; phải lấy từ nguồn (đường dẫn chậm)
  • Hit Ratio (Tỷ lệ Trúng)hits / (hits + misses) — chỉ số sức khỏe Cache chính yếu
  • TTL (Time-to-Live - Thời gian sống) — bao lâu một mục được coi là hợp lệ trước khi làm mới
  • Eviction Policy (Chính sách Loại bỏ) — quy tắc quyết định dữ liệu nào bị xóa khi Cache đầy (LRU, LFU, TTL)
  • Cache Invalidation (Vô hiệu hóa Cache) — xóa hoặc cập nhật dữ liệu cũ (Stale Data) trong Cache

Luồng Hit/Miss

Quan sát cách các yêu cầu đi qua lớp Cache. Hit trả về dữ liệu ngay lập tức; Miss truy vấn cơ sở dữ liệu và lưu kết quả cho lần sau:

0 / 18
Client
Cache
Database
Response

Các mức Caching trong Cơ sở dữ liệu

Caching diễn ra ở nhiều lớp (Layers) giữa Client và đĩa:

Client Request

[1] Application Cache (Redis, Memcached, In-process)
↓ miss
[2] Query Cache (tích hợp trong một số RDBMS — hiếm trong hệ thống hiện đại)
↓ miss
[3] Buffer Pool (bộ nhớ DB Engine — luôn có)
↓ miss
[4] Disk / SSD (tệp dữ liệu thực tế)

1. Buffer Pool / Page Cache (Bộ đệm Trang)

Buffer Pool là bộ nhớ đệm nội bộ của Database Engine. Nó lưu trữ các Data Pages (Trang dữ liệu) — các khối kích thước cố định, thường 8–16 KB — vừa được đọc từ hoặc ghi lên đĩa.

  • Mọi thao tác SELECT, INSERT, UPDATE, DELETE đều hoạt động trên các Page trong Buffer Pool
  • Dirty Pages (Trang bẩn) — đã sửa đổi nhưng chưa ghi lên đĩa — được ghi lại bởi quá trình Checkpoint
  • Đây là lớp Caching cơ bản nhất — có mặt trong mọi RDBMS chính

Tỷ lệ Hit của Buffer Pool — mục tiêu Production: > 95%

-- PostgreSQL
SELECT sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) AS ratio
FROM pg_statio_user_tables;

-- MySQL
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';

2. Query Cache (Bộ nhớ nhanh Truy vấn)

Một số cơ sở dữ liệu ánh xạ chuỗi SQL cố định (Literal SQL String) với tập kết quả (Result Set) của nó. Nếu cùng truy vấn thực hiện lại và không có dữ liệu thay đổi, kết quả đã Cache được trả về trực tiếp.

Cơ sở dữ liệuHỗ trợ Query Cache
MySQLKhông còn dùng (Deprecated) từ 5.7, đã loại bỏ (Removed) trong 8.0 — Mutex toàn cục gây tranh chấp (Contention) khi ghi
PostgreSQLKhông có Query Cache tích hợp; phụ thuộc vào Caching ở mức ứng dụng
SQL ServerPlan Cache (lưu Execution Plan, không phải kết quả)
Tại sao MySQL loại bỏ nó

Mỗi lần ghi vào bất kỳ bảng nào đều vô hiệu hóa tất cả truy vấn đã Cache cho bảng đó dưới một Mutex toàn cục. Chi phí vượt quá lợi ích trong hầu hết workload thực tế.

3. Cache ở mức Ứng dụng (Application-level Cache)

Một Cache bên ngoài nằm giữa ứng dụng và cơ sở dữ liệu. Ứng dụng kiểm tra Cache trước; chỉ khi Miss mới truy vấn DB và điền vào Cache (Populate Cache).

CachePhù hợp nhất cho
RedisCấu trúc dữ liệu phong phú (Rich Data Structures), Persistence, Pub/Sub, hỗ trợ TTL
MemcachedKey-value đơn giản, Đa luồng (Multithreaded), rất nhanh cho tra cứu cơ bản
In-process (IMemoryCache)Không qua mạng (No Network Hop), nhanh nhất, chỉ cho từng Instance
// Cache-aside Read Pattern
async Task<User> GetUser(int id)
{
var key = $"user:{id}";

var cached = await cache.GetStringAsync(key);
if (cached is not null)
return Deserialize<User>(cached); // Hit

var user = await db.Users.FindAsync(id); // Miss → query DB

await cache.SetStringAsync(key, Serialize(user), new() // Populate Cache
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30)
});

return user;
}

So sánh các Chiến lược Cache (Cache Strategies)

Chuyển đổi giữa năm chiến lược — quan sát khi nào Client nhận được ACK và khi nào cơ sở dữ liệu thực sự được truy cập:

The most common pattern. Application checks the cache first; on a miss, it queries the database and stores the result. Next read is a cache hit.

0 / 0
Client
Cache
Database

Chiến lược Đọc (Read Strategies)

Cache-Aside (Lazy Loading - Tải chậm)

Ứng dụng tự quản lý Cache (Application manages the cache explicitly). Khi đọc, kiểm tra Cache trước; nếu Miss, truy vấn nguồn và tự điền vào Cache.

  • Ưu điểm: Đơn giản, kiểm soát đầy đủ, hoạt động với bất kỳ Cache Store nào
  • Nhược điểm: Logic Cache rò rỉ vào mã ứng dụng; khởi động nguội (Cold Start) chậm
  • Phù hợp nhất cho: Mục đích chung (General-purpose), workload hỗn hợp Đọc/Ghi

Read-Through

Cache Provider nằm giữa ứng dụng và cơ sở dữ liệu. Khi Miss, Cache Provider tự động tải dữ liệu từ cơ sở dữ liệu — ứng dụng chỉ giao tiếp với lớp Cache.

  • Ưu điểm: Mã ứng dụng sạch hơn (không có logic Cache), hành vi Cache nhất quán giữa các dịch vụ
  • Nhược điểm: Gắn chặt với khả năng của Cache Provider, ít kiểm soát logic tải
  • Phù hợp nhất cho: Workload đọc nhiều (Read-heavy), muốn tách biệt trách nhiệm rõ ràng
  • Ví dụ: Spring Cache (@Cacheable), Hibernate Second-level Cache, Redis với Lua Scripts

Chiến lược Ghi (Write Strategies)

Write-Through

Mọi thao tác ghi đều đến cả Cache và cơ sở dữ liệu đồng bộ trước khi xác nhận Client.

  • Ưu điểm: Tính nhất quán mạnh (Strong Consistency) — Cache và DB luôn đồng bộ; đọc luôn nhanh
  • Nhược điểm: Độ trễ ghi cao (hai lần ghi đồng bộ)
  • Phù hợp nhất cho: Workload đọc nhiều, tính nhất quán quan trọng hơn tốc độ ghi

Write-Around

Ghi trực tiếp đến cơ sở dữ liệu, bỏ qua Cache hoàn toàn. Cache chỉ được điền khi dữ liệu được đọc (Miss → truy vấn DB → lưu vào Cache).

  • Ưu điểm: Cache không bị ô nhiễm bởi dữ liệu chỉ ghi; không khuếch đại ghi (No Write Amplification)
  • Nhược điểm: Dữ liệu vừa ghi không có trong Cache cho đến khi đọc lần đầu; trượt hàng loạt (Burst of Misses) sau khi ghi
  • Phù hợp nhất cho: Workload ghi nhiều, dữ liệu không được đọc lại ngay (Log, Analytics, Bulk Import)

Write-Behind (Write-Back - Ghi ngược)

Ghi chỉ đến Cache; Client được xác nhận ngay lập tức. Cơ sở dữ liệu được cập nhật bất đồng bộ theo lô (Batch).

  • Ưu điểm: Ghi rất nhanh; ghi lô giảm tải DB
  • Nhược điểm: Nguy cơ mất dữ liệu nếu Cache Crash trước khi Flush; tính nhất quán cuối cùng (Eventual Consistency)
  • Phù hợp nhất cho: Workload ghi nhiều, chấp nhận mất dữ liệu (Session State, Telemetry Counters)

Bảng so sánh chiến lược

Khía cạnhCache-AsideRead-ThroughWrite-ThroughWrite-AroundWrite-Behind
Luồng ghi (Write Path)Ứng dụng cập nhật DB, rồi vô hiệu hóa CacheGiống Cache-AsideCache + DB đồng bộTrực tiếp đến DB, bỏ qua CacheChỉ Cache; DB bất đồng bộ
Luồng đọc (Read Path)Kiểm tra Cache → Miss → truy vấn DB → lưuCache Provider tự tải từ DB khi MissCache luôn được điền sau khi ghiMiss → DB → lưu (giống Cache-Aside)Cache luôn được điền sau khi ghi
Tính nhất quán (Consistency)Cuối cùng (Eventual) — cũ cho đến khi vô hiệu hóaCuối cùng (Eventual) — giống Cache-AsideMạnh (Strong) — luôn đồng bộCuối cùng (Eventual) — Cache điền lười biếngCuối cùng (Eventual) — DB trễ hơn Cache
Độ trễ ghi (Write Latency)Bình thườngKhông áp dụng (chiến lược chỉ đọc)Cao (hai lần ghi đồng bộ)Bình thường (một lần ghi DB)Rất nhanh (chỉ ghi vào Cache)
Nguy cơ mất dữ liệuKhôngKhôngKhôngKhông — Crash Cache mất các ghi chưa Flush
Phù hợp nhất choMục đích chung, workload hỗn hợpĐọc nhiều, mã ứng dụng sạchĐọc nhiều, tính nhất quán mạnhGhi nhiều, dữ liệu không đọc lại ngayGhi nhiều, chấp nhận Eventual Consistency

Kết hợp chiến lược (Combining Strategies)

Chiến lược Đọc và Ghi có thể ghép cặp:

Đọc + GhiHiệu quả
Cache-Aside + Write-AroundĐơn giản, Cache chỉ điền khi đọc
Cache-Aside + Write-ThroughNhất quán, nhưng ứng dụng quản lý Cache
Read-Through + Write-ThroughMã ứng dụng sạch + tính nhất quán mạnh
Read-Through + Write-BehindMã ứng dụng sạch + ghi nhanh (Eventual Consistency)

Chiến lược Vô hiệu hóa Cache (Cache Invalidation Strategies)

Biết khi nào dữ liệu Cache đã cũ (Stale) là bài toán khó nhất trong Caching.

Hết hạn dựa trên TTL (TTL-based Expiration)

Mỗi mục có một Time-to-Live. Khi TTL hết hạn, mục sẽ tự động bị loại bỏ (Evicted).

  • Ưu điểm: Đơn giản, không cần phối hợp (Coordination)
  • Nhược điểm: Dữ liệu cũ trong khoảng TTL; TTL ngắn giảm độ cũ nhưng tăng Miss
  • Phù hợp nhất cho: Dữ liệu ít thay đổi, chấp nhận hơi cũ

Vô hiệu hóa rõ ràng (Explicit Invalidation)

Ứng dụng hoặc Trigger của cơ sở dữ liệu xóa/cập nhật các mục Cache khi dữ liệu thay đổi.

  • Ưu điểm: Kiểm soát chính xác
  • Nhược điểm: Cần biết khi nào dữ liệu thay đổi
  • Kỹ thuật phổ biến: PostgreSQL LISTEN/NOTIFY, MongoDB Change Streams, SQL Server Change Tracking

Chính sách Loại bỏ (Eviction Policies)

Khi Cache đầy, một thứ gì đó phải bị xóa:

Chính sáchLoại bỏPhù hợp nhất cho
LRU (Ít dùng gần đây nhất - Least Recently Used)Mục không dùng lâu nhấtMục đích chung — giả định truy cập gần đây dự đoán truy cập tương lai
LFU (Ít dùng nhất - Least Frequently Used)Mục được truy cập ít nhấtMẫu truy cập ổn định, một số dữ liệu thực sự phổ biến hơn
FIFO (Vào trước Ra trước - First In, First Out)Mục cũ nhấtTriển khai đơn giản; hiếm khi tối ưu
Dựa trên TTLCác mục hết hạn bất kể dung lượngDữ liệu nhạy cảm thời gian (Session, phản hồi API)

Các lỗi Thường gặp (Common Pitfalls)

Cache Stampede / Thundering Herd (Đốt bộ nhớ nhanh)

Một mục phổ biến hết hạn và nhiều yêu cầu đồng thời cùng Miss Cache cùng lúc, đánh sập cơ sở dữ liệu.

Cách khắc phục: Khóa phân tán/Mutex (Distributed Lock) để chỉ một yêu cầu xây dựng lại; hoặc làm mới trước khi hết hạn (ví dụ: ở 80% TTL với Jitter).

Cache Penetration (Xuyên bộ nhớ nhanh)

Truy vấn cho dữ liệu không tồn tại không bao giờ được Cache (không có mục để lưu), nên mọi yêu cầu đều đến DB.

Cách khắc phục: Cache kết quả Null với TTL ngắn; hoặc sử dụng Bloom Filter để kiểm tra sự tồn tại trước.

Cache Breakdown (Phá vỡ bộ nhớ nhanh)

Một Hot Key duy nhất hết hạn, gây ra đột biến bất ngờ.

Cách khắc phục: Không bao giờ để Hot Key hết hạn — làm mới chúng bất đồng bộ ở nền (Background).

Over-caching (Lạm dụng Cache)

Không nên Cache: dữ liệu thay đổi thường xuyên, dữ liệu thời gian thực (số dư tài khoản), hoặc Blob có kích thước quá lớn.

Nguyên tắc: Cache dữ liệu được đọc thường xuyên (Read Frequently)ít thay đổi (Changes Rarely).

Caching trong Thực tế

Cơ sở dữ liệuCấu hình Buffer PoolQuery CacheCache Ứng dụng
PostgreSQLshared_buffers (~25% RAM)Không cóRedis / mức ứng dụng
MySQLinnodb_buffer_pool_sizeĐã loại bỏ trong 8.0Redis / mức ứng dụng
SQL ServerBuffer Pool (tự động quản lý)Plan Cache (Execution Plan)Redis / mức ứng dụng
-- PostgreSQL: tải trước bảng vào Buffer Pool
SELECT pg_prewarm('users');

-- SQL Server: kiểm tra Buffer Pool usage
SELECT COUNT(*) * 8 / 1024 AS total_mb
FROM sys.dm_os_buffer_descriptors
WHERE database_id = DB_ID();
# Redis: các thao tác Cache cơ bản
SET user:42 '{"name":"Alice"}' EX 1800 # Đặt với TTL 30 phút
GET user:42 # Lấy
DEL user:42 # Vô hiệu hóa

Khi nào dùng Chiến lược nào

Tình huốngCách tiếp cận được khuyến nghị
Đọc nhiều, dữ liệu ít thay đổiCache-aside hoặc Read-through với TTL dài
Đọc nhiều, dữ liệu thỉnh thoảng thay đổiCache-aside + Vô hiệu hóa rõ ràng khi ghi
Đọc nhiều, muốn mã ứng dụng sạchRead-through + Write-through
Ghi nhiều, dữ liệu không đọc lại ngayWrite-around (Cache chỉ điền khi đọc)
Ghi nhiều, chấp nhận Eventual ConsistencyWrite-behind Cache
Yêu cầu tính nhất quán mạnh (Strong Consistency)Write-through Cache hoặc không dùng Cache
Dữ liệu thời gian thực (số dư, tồn kho)Không Cache — luôn truy vấn cơ sở dữ liệu
Dữ liệu Session, giới hạn tốc độ (Rate Limiting)Redis với TTL

Các chủ đề Liên quan

  • Indexing — giảm chi phí truy vấn trước khi cần Cache
  • Redis — Cache Engine phổ biến nhất
  • System Design: Caching — Caching trong Hệ thống phân tán (Distributed Systems)