Cache Handbook: Từ Monitoring đến Scaling - Vận hành hệ thống Cache “triệu request”
Bài viết được tổng hợp và biên soạn lại từ cuốn “A Cache Handbook for Software Engineers” của Quang Hoang (Software Engineer tại Google). Đây là phần 4/4 trong series.
Ở các phần trước, chúng ta đã tìm hiểu nền tảng caching, bài toán consistency và các cái bẫy thường gặp. Bài viết cuối cùng này sẽ tập trung vào hai khía cạnh vận hành quan trọng nhất: Monitoring (giám sát) và Scaling (mở rộng) hệ thống Cache.
Phần 1: Monitoring Cache
Để vận hành Cache hiệu quả, bạn cần theo dõi các chỉ số (metric) quan trọng sau.
1.1. Hit Rate
Nhắc lại công thức:
Hit Rate = Cache Hit / (Cache Hit + Cache Miss) × 100Chỉ số này thường được đo theo nhóm chức năng hoặc API, ví dụ Hit Rate của GetProduct API.
Không có con số tuyệt đối, nhưng với các hệ thống thông thường:
- 80% - 95% được coi là lý tưởng.
- < 50%: Cache đang làm việc không hiệu quả. Có thể do dung lượng bộ nhớ hoặc TTL quá nhỏ, eviction policy không phù hợp, hoặc dữ liệu thay đổi quá liên tục.
- = 100%: Hãy cẩn thận — rất có thể cơ chế cập nhật dữ liệu đang bị lỗi.
1.2. Latency
Lý do tồn tại quan trọng nhất của Cache là tốc độ, nên cần monitor:
- Average Latency: Đối với remote cache (Redis, Memcached), thường dưới 1-2ms.
- Tail Latency (p90, p99): Phát hiện các trường hợp bất thường.
1.3. Throughput
Throughput được đo bằng Ops/sec (Operations per second — số lượng lệnh thực thi trên mỗi giây). Monitor chỉ số này giúp bạn xác định giới hạn của hệ thống và đưa ra quyết định Scale-out.
1.4. Memory
- Memory Utilization: Tỷ lệ sử dụng bộ nhớ. Đặt alert khi vượt ngưỡng 60-70%.
- Eviction Rate: Số key bị ép xóa mỗi giây. Nếu Eviction Rate tăng cao + Hit Rate giảm mạnh → có thể đang gặp Cache Thrashing.
- Fragmentation Ratio:
Fragmentation Ratio = Memory Allocated by OS / Memory Used by DataHai mốc quan trọng:
- > 1.5: Cache đang bị phân mảnh nặng.
- < 1.0: Cache đang thiếu RAM và phải dùng swap (chậm hơn rất nhiều).
1.5. Connection Churn Rate
Để phát hiện Connection Churn, không nhìn vào số kết nối đang mở (Connected Clients), mà phải nhìn vào tốc độ tạo kết nối mới. Chỉ số này được tính bằng đạo hàm bậc 1 của số lượng kết nối. Ví dụ trong Prometheus:
rate(connection_totals)Phần 2: Scaling Cache
Khi lưu lượng truy cập và kích thước dữ liệu quá lớn, một Cache server thường không đủ. Việc mở rộng theo chiều dọc (Vertical Scaling — tăng RAM, CPU) thường gặp rào cản chi phí và bottleneck I/O mạng. Giải pháp tối ưu là Horizontal Scaling.
Câu hỏi quan trọng: Làm thế nào để phân phối các key lên N server một cách đồng đều và ổn định?
2.1. Modulo Hashing
Cách tiếp cận cơ bản nhất: mỗi key được hash ra một số nguyên, sau đó chia lấy dư cho tổng số node (N):
Node Index = hash(key) mod NVấn đề Rebalancing: Khi N thay đổi (thêm/bớt node), phép chia thay đổi mẫu số → gần như toàn bộ key bị tính sai vị trí. Tỷ lệ key phải di chuyển xấp xỉ N/(N+1).
Ví dụ: Cụm 10 nodes, thêm 1 node mới → khoảng 90% dữ liệu bị Cache Miss diện rộng (Cache Avalanche), đẩy toàn bộ tải xuống Database.
2.2. Consistent Hashing
Được giới thiệu năm 1997 bởi David Karger, Daniel Lewin, Thomson Leighton (cofounders của Akamai Technologies).
Cơ chế hoạt động:
- Hash Ring: Sử dụng hàm hash (MD5, MurmurHash) có dải giá trị từ 0 đến 2³² − 1, biểu diễn thành vòng tròn khép kín.
- Ánh xạ Server: IP/ID của Cache Server được hash và đặt lên vòng tròn.
- Routing Key: Key được hash để tìm vị trí trên vòng tròn. Quét theo chiều kim đồng hồ, node đầu tiên gặp được là Server lưu trữ key đó.
Ưu điểm: Khi thêm/bớt node, số key bị rebalance chỉ là K/N (K = tổng số key), thay vì 90% như Modulo.
Vấn đề phân phối không đều: Nếu N nhỏ, khoảng cách giữa các node trên vòng tròn rất lớn → một node phải ôm dữ liệu nhiều hơn. Giải pháp: Virtual Nodes — một node vật lý được ánh xạ thành nhiều virtual nodes bằng cách hash IP kết hợp hậu tố (NodeA#1, NodeA#2).
Triển khai Consistent Hashing
Tùy kiến trúc, Consistent Hashing được lưu ở một trong ba vị trí:
- Thick Client (Client-side Routing): Lưu trực tiếp trong bộ nhớ App Server. Các thư viện như
spymemcached(Java),gomemcache(Golang) tự duy trì cấu trúc này. - Proxy (Middleware Routing): Lưu trên Proxy server trung gian (Twemproxy, Envoy). App Server gửi request đến Proxy, Proxy điều hướng đến đúng node.
- Cluster (Server-side Routing): Mỗi node trong cụm tự lưu trữ bản sao Consistent Hashing (như Cassandra, DynamoDB).
Xử lý khi node bị down
Passive Eviction: Client tính toán ra Node A, gửi request, bị timeout. Khi lỗi chạm ngưỡng (max_failures = 3), Client tự loại Node A ra khỏi Consistent Hashing. Request tiếp theo rơi vào node kế tiếp trên Ring.
Nhược điểm: Dễ gây “Split-brain” — Client 1 thấy Node A chết nhưng Client 2 vẫn thấy sống. Hai client có Consistent Hashing khác nhau, phá vỡ tính nhất quán.
Active Eviction: Sử dụng Service Discovery (Consul):
- Node Cache khởi động → đăng ký IP/Port lên Service Discovery.
- Node liên tục gửi Heartbeat → “I am alive!”
- Nếu Node A sập, Service Discovery ghi nhận sự kiện.
- Service Discovery đẩy sự kiện tới Proxy/App Server qua API hoặc HTTP Long-polling.
- Tất cả đồng loạt cập nhật Consistent Hashing.
2.3. Hash Slot (Redis Cluster)
Mặc dù Consistent Hashing giải quyết tốt bài toán phân tán, nó đặt gánh nặng tính toán lên Client/Proxy. Redis Cluster (từ phiên bản 3.0) tiếp cận theo hướng khác: Hash Slot.
Cơ chế hoạt động:
Redis Cluster chia key space thành 16384 slot (2¹⁴). Con số này cố định, không phụ thuộc số node. Khi key được ghi:
Hash Slot = CRC16(key) mod 16384Mỗi node quản lý một dải Hash Slot. Ví dụ với 3 node:
- Node A: Slot 0 → 5460
- Node B: Slot 5461 → 10922
- Node C: Slot 10923 → 16383
Nếu Node A có RAM gấp đôi Node B, bạn có thể chủ động gán số lượng Slot cho Node A nhiều gấp đôi — thay vì phụ thuộc vào xác suất ngẫu nhiên của Virtual Nodes trong Consistent Hashing.
Tại sao là 16384? Mỗi gói tin heartbeat cần đính kèm bitmap thông tin các slot đang quản lý. Với 16384 slot: bitmap chỉ chiếm 16384 / 8 = 2048 bytes (2KB) — tối ưu cho network.
Hash Tag
Redis hỗ trợ thao tác trên nhiều key cùng lúc (MGET, MSET). Nếu key có chứa cặp ngoặc {}, hàm CRC16 chỉ tính trên giá trị bên trong:
user:{100}:profileuser:{100}:orders
Chuỗi trong {} đều là 100 → cả hai key rơi vào cùng một Hash Slot → cùng một Node → Client chỉ cần gửi request đến một Node.
Routing Mechanism
Khi Client muốn đọc/ghi key:
- Client tính Hash Slot, tra bảng mapping nội bộ → gửi request tới Node tương ứng.
- Nếu bảng mapping outdated, request bị lạc sang Node sai. Node trả về lỗi
MOVED <slot_id> <ip_đích>:<port>. - Client nhận
MOVED→ gửi lại request đến IP đích + cập nhật bảng mapping để lần sau đi đúng ngay từ đầu.
Gossip Protocol
Để các node trong Redis Cluster biết chính xác Hash Slot nào thuộc Node nào mà không cần server trung tâm (ZooKeeper, Consul), chúng sử dụng Gossip Protocol:
- Mỗi giây, một Node ngẫu nhiên chọn vài Node khác để gửi
PING. - Đầu nhận đáp
PONG. - Payload chứa: Node ID, Slot Bitmap, Flags, Gossip Session (thông tin trạng thái các node khác).
Nhờ trao đổi chéo liên tục, thông tin cấu hình “lây lan” ra toàn cụm chỉ trong vài giây.
Nhược điểm: Rào cản quy mô: tối đa 1000 node. Khi số node tăng, lượng PING/PONG tăng theo cấp số nhân. Ước tính: cluster 1000 node gửi 133,200 API gossip mỗi giây, tiêu tốn lượng lớn CPU và băng thông. Ở quy mô hyperscale, người ta ưu tiên dùng server trung tâm để quản lý trạng thái Cluster.
Zero Downtime Resharding
Khi cần thêm/bớt Node, các Hash Slot + dữ liệu phải được dời đi mà không gây gián đoạn dịch vụ.
Ví dụ: Slot 8 đang chuyển từ Node A sang Node B:
- Node A đánh dấu Slot 8 là
MIGRATING. Node B đánh dấuIMPORTING. - Background thread chuyển dần các key thuộc Slot 8 sang Node B.
- Nếu Client yêu cầu key thuộc Slot 8 và gửi đến Node A:
- Key chưa chuyển đi: Trả về kết quả bình thường.
- Key đã chuyển đi: Node A trả về lỗi
ASK 8 <ip_Node_B>:<port>. Client mở kết nối tạm thời tới Node B để lấy dữ liệu (không cập nhật mapping config).
Lời kết
Qua 4 bài viết trong series này, hy vọng bạn đã gạt bỏ được ảo tưởng phổ biến: “Hệ thống chậm ư? Cứ gắn thêm Redis vào là xong.”
Sự thật là, thêm một lớp Cache không bao giờ làm hệ thống đơn giản hơn. Nó chỉ chuyển độ phức tạp từ nơi này sang nơi khác. Hy vọng series nhỏ này có thể giúp bạn nhìn thấu sự phức tạp đó để tự tin làm chủ hệ thống của mình.
Credit: Toàn bộ series được tổng hợp từ cuốn “A Cache Handbook for Software Engineers” của Quang Hoang — Software Engineer tại Google, trước đó từng làm việc tại Shopee và Rakuten. Bạn có thể kết nối với tác giả qua LinkedIn hoặc Blog .
Series: Cache Handbook
- Nền tảng cốt lõi của Caching
- Giải mã bài toán Cache Consistency
- 6 “cái bẫy” kinh điển khi dùng Cache
- Từ Monitoring đến Scaling ← Bạn đang ở đây