Consistent Hashing: Scale-out Redis Cache và Database Sharding không mất dữ liệu
Hướng dẫn Consistent Hashing cho Redis cache và Database Sharding: hash ring, virtual nodes, replication, Redis Cluster hash slots và quy trình dual-write/backfill/cutover không mất dữ liệu.
ShanverseCập nhật 14 phút đọc— lượt xemĐang tải lượt xem
Distributed Systems
Redis
Caching
Bài toán của phép chia modulo
Cách phân phối key đơn giản nhất là:
Với 3 node, user:42 có thể nằm ở node 1. Khi tăng từ 3 lên 4 node, mẫu số thay đổi và phần lớn key có thể trỏ sang node khác. Hậu quả:
Cache miss hàng loạt và cache stampede.
Network, CPU và disk bị bão hòa vì rehydrate/migration.
Router mới đọc shard mới trong khi dữ liệu vẫn ở shard cũ.
Có thể đọc thiếu hoặc ghi sai nơi nếu topology không được version hóa.
Scale-out trở thành một sự kiện rủi ro cao.
Mục tiêu của consistent hashing là giữ phần lớn key ở nguyên vị trí khi membership thay đổi.
Consistent Hashing hoạt động thế nào?
Bình luận
Hash cả node và key vào cùng một không gian số dạng vòng tròn.
Mermaid
Loading diagram…
Quy tắc cơ bản:
Hash ID của mỗi node thành một điểm trên vòng.
Hash key thành một điểm trên cùng vòng.
Từ key đi theo chiều kim đồng hồ.
Node đầu tiên gặp chịu trách nhiệm cho key.
Khi thêm node, chỉ đoạn vòng mà node mới chiếm cần di chuyển.
Khi xóa node, chỉ key của node đó chuyển sang successor.
Nếu có N node cân bằng và thêm một node, kỳ vọng khoảng 1/(N+1) key chuyển sang node mới thay vì phần lớn key như modulo hashing.
Virtual Nodes: vì sao một node vật lý cần nhiều điểm?
Chỉ đặt mỗi server một lần trên ring dễ tạo các đoạn có kích thước rất khác nhau. Một node có thể nhận quá nhiều key dù hash function tốt.
Virtual nodes, hay vnodes, đặt mỗi node vật lý tại nhiều vị trí:
Lợi ích:
Phân phối key đều hơn.
Khi node thay đổi, dữ liệu được chia cho nhiều node kế nhiệm.
Có thể gán nhiều vnode hơn cho server mạnh hơn.
Giảm biến động tải giữa các node.
Không có một số vnode chuẩn cho mọi hệ thống. Hãy benchmark phân phối key, memory metadata và thời gian lookup bằng workload thật.
TypeScript: xây dựng hash ring có virtual nodes
Production code cần thêm:
Hash function ổn định giữa mọi ngôn ngữ và phiên bản.
Xử lý collision.
Topology version và config distribution atomic.
Health state không flap theo từng request.
Failure domain theo rack/AZ cho replica.
Metrics kiểm tra skew và hot key.
Math.random() hoặc hash phụ thuộc process không được dùng để route dữ liệu lâu dài.
Complexity
Với N node và V vnode mỗi node:
Metadata: O(N × V).
Lookup bằng binary search: O(log(N × V)).
Thêm node: thêm V token rồi sort/update cấu trúc.
Key movement: tỷ lệ với các khoảng token chuyển owner.
Ring chỉ lưu metadata placement, không cần lưu mọi key.
Scale-out distributed cache an toàn
Cache phải là bản sao có thể tái tạo từ nguồn dữ liệu chính. Nếu Redis là nơi duy nhất giữ dữ liệu quan trọng, đó không còn là cache thuần túy.
Khi thêm cache node
Một rollout an toàn:
Tạo ring version v2 có node mới.
Đảm bảo mọi client/router nhận cùng topology.
Với key đổi owner, đọc node mới trước.
Nếu miss, fallback node cũ hoặc database.
Ghi kết quả vào node mới với TTL và jitter.
Giới hạn tốc độ warm-up để tránh làm quá tải database.
Theo dõi hit ratio, fallback, latency và backend QPS.
Ngừng fallback sau khi đủ thời gian/coverage.
Pseudo-code:
singleFlight/request coalescing ngăn hàng nghìn request cùng rebuild một hot key.
Cache replication
getReplicas(key, 2) có thể chọn node đầu tiên và node vật lý kế tiếp trên ring. Khi thiết kế replica:
Không đặt hai replica cùng host/rack/AZ.
Xác định write policy: sync, async hay best effort.
Read từ primary trước hay fastest replica.
Version value để tránh replica cũ ghi đè dữ liệu mới.
Cache replication vẫn không thay thế database durability.
Redis Cluster không dùng consistent-hash ring
Điểm quan trọng: Redis Cluster dùng 16.384 hash slots, không dùng consistent hashing ring kiểu Karger.
Mỗi primary sở hữu một tập slot. Scale-out diễn ra bằng cách chuyển slot giữa các node. Client cluster-aware xử lý redirect MOVED/ASK trong lúc topology thay đổi.
Hash tags
Các key có phần trong {...} giống nhau được ép vào cùng slot:
Điều này hữu ích cho multi-key operation nhưng có thể tạo hot slot nếu gom quá nhiều dữ liệu vào một tag.
Reshard Redis đúng cách
Thêm primary mới và replica phù hợp.
Kiểm tra cluster health, replication lag và coverage đủ 16.384 slot.
Chuyển slot dần bằng công cụ cluster-aware.
Theo dõi p99 latency, network, CPU và memory.
Không remove node khi node vẫn sở hữu slot.
Kiểm tra client hỗ trợ redirect và refresh topology.
Có backup nếu Redis giữ state bền vững, không chỉ cache.
Consistent hashing là mô hình hữu ích để hiểu minimal remapping; Redis Cluster hiện thực scale-out bằng fixed slot map và explicit resharding.
Database Sharding: placement không đồng nghĩa durability
Database khác cache ở chỗ miss không thể đơn giản rebuild nếu source record đã bị mất. Consistent hashing chỉ trả lời:
Key này nên thuộc shard nào trong topology hiện tại?
Nó không tự giải quyết:
Copy row an toàn.
Write đang diễn ra trong lúc backfill.
Transaction và unique constraint xuyên shard.
Replica, backup và point-in-time recovery.
Cutover giữa router versions.
Rollback khi target shard sai dữ liệu.
Vì vậy, câu “không mất dữ liệu” là thuộc tính của migration protocol, không phải riêng hash function.
Chọn shard key
Shard key tốt thường:
Có cardinality cao.
Phân phối tải tương đối đều.
Xuất hiện trong phần lớn query quan trọng.
Ổn định, gần như không đổi.
Giữ dữ liệu cần transaction cùng shard.
Ví dụ thường gặp: tenant_id, user_id, account_id.
Không nên chọn:
Timestamp tăng dần nếu gây write hotspot.
Country/status có cardinality thấp.
Field thường xuyên thay đổi.
Key khiến một khách hàng lớn chiếm cả shard mà không có chiến lược split.
Consistent hashing phân phối nhiều key đều hơn, nhưng không chữa được hot key. Một tenant cực lớn vẫn có thể cần sub-sharding hoặc dedicated shard.
Quy trình online resharding không mất write
Mermaid
Loading diagram…
1. Chuẩn bị và bất biến
Chốt shard key và token ranges cần chuyển.
Kiểm tra schema/index/constraint ở target.
Bật backup/PITR và thử restore.
Đặt migration ID, topology version và owner state rõ ràng.
Ghi lại log position hoặc snapshot boundary.
2. Capture write đang diễn ra
Có ba mẫu phổ biến:
Dual-write: app ghi cả source và target.
CDC: đọc binlog/WAL rồi apply sang target.
Outbox: transaction ghi dữ liệu và event cùng source, worker relay sang target.
Dual-write đơn giản về ý tưởng nhưng có partial failure. Không nên:
Thay vào đó cần idempotency key, version, retry queue và reconciliation. CDC/outbox thường dễ audit hơn cho migration dài.
3. Backfill idempotent
Đọc theo primary key/range nhỏ.
Ghi bằng upsert có version hoặc compare-and-set.
Throttle để không ảnh hưởng production.
Lưu checkpoint bền vững.
Retry chunk an toàn.
Không để snapshot cũ ghi đè event mới hơn.
Ví dụ điều kiện:
4. Catch up và validation
Chờ replication/CDC lag về ngưỡng chấp nhận.
So sánh row count theo token range.
Checksum theo chunk, không khóa toàn bảng.
So sánh aggregate nghiệp vụ.
Shadow read một tỷ lệ traffic và diff kết quả.
Theo dõi missing, divergent và stale records.
Count bằng nhau không chứng minh dữ liệu giống nhau; checksum và invariant nghiệp vụ quan trọng hơn.
5. Cutover theo range/token
Không đổi toàn hệ thống trong một lần. Chuyển từng token range hoặc tenant:
Router ghi target là authoritative.
Read target trước, source fallback trong cửa sổ ngắn.
Read-repair nếu target thiếu và source có record mới hơn.
Quan sát error, latency và divergence.
Mở rộng dần phần traffic.
Topology config phải có version và cập nhật atomic. Nếu router cũ và mới cùng hoạt động, write ownership phải được định nghĩa rõ để tránh split-brain.
6. Rollback và cleanup
Giữ source read-only hoặc tiếp tục replicate trong rollback window.
Rollback bằng topology version, không sửa tay từng service.
Chỉ xóa source sau khi backup, validation và thời gian an toàn hoàn tất.
Gỡ dual-write/CDC có chủ đích; tránh để migration code tồn tại vĩnh viễn.
Routing record trong giai đoạn chuyển shard
Một state machine rõ ràng tốt hơn boolean migrated:
Consistent Hashing, Hash Slots và Directory-based Sharding
Database relational thường cần explicit ranges, vindex hoặc directory để hỗ trợ resharding, query planning và vận hành tốt hơn một ring thuần túy.
Rendezvous Hashing: lựa chọn thay thế
Rendezvous/Highest Random Weight Hashing tính score cho mỗi cặp (key, node) và chọn node có score cao nhất:
Ưu điểm:
Không cần lưu/sort ring.
Dễ lấy top-K replica.
Minimal disruption khi membership đổi.
Weighted variant hỗ trợ node khác capacity.
Nhược điểm cơ bản là lookup O(N) hash operations, dù có các kỹ thuật tối ưu. Với số node vừa phải, Rendezvous Hashing thường đơn giản và hấp dẫn.
Failure modes cần thiết kế trước
Node chết đột ngột
Ring remap key nhưng dữ liệu trên node chết không tự xuất hiện ở node mới. Cache có thể rebuild; database cần replica/failover và quorum policy.
Topology split-brain
Hai client có danh sách node khác nhau sẽ route cùng key đến hai nơi. Cần config service có version, consensus/authoritative source và rollout có kiểm soát.
Hot key
Hash phân phối key, không phân phối traffic của một key. Giải pháp:
Local cache.
Request coalescing.
Read replicas.
Key replication.
Salting chỉ khi có thể merge kết quả.
Admission control và rate limiting.
Node capacity không đồng đều
Dùng weighted vnode hoặc weighted rendezvous hashing. Weight sai có thể overload node yếu hơn.
Scale-in quá nhanh
Churn liên tục gây migration liên tục. Dùng stabilization window, health hysteresis và giới hạn tốc độ rebalance.
Cross-shard operation
Join, transaction, pagination và unique constraint toàn cục trở nên khó hơn. Thiết kế data model để operation quan trọng cùng shard; dùng saga/outbox hoặc global service khi cần.
Test bằng key thật trước production
Đo ít nhất:
Tỷ lệ key trên mỗi node.
Standard deviation và max/mean.
Tỷ lệ key di chuyển khi add/remove node.
Tỷ lệ hot traffic, không chỉ số lượng key.
Lookup throughput và p99.
Replica nằm cùng failure domain.
Kết quả giống nhau giữa Java, Go, Node.js và các version.
Observability khi reshard
Cache
Hit/miss/fallback ratio theo ring version.
Backend QPS do cache miss.
Keys/bytes và eviction theo node.
Hot key và skew.
Migration/warm-up throughput.
p50/p95/p99 latency.
Database
Backfill progress và records/second.
CDC lag và retry/dead-letter count.
Source-target checksum mismatch.
Shadow-read divergence.
Dual-write partial failure.
Query/error/lock/replication lag trên target.
Cutover percentage theo range/tenant.
Không dùng shard key thô làm metric label high-cardinality; đưa chi tiết vào log có kiểm soát.
Anti-patterns
Nói consistent hashing tự ngăn mất dữ liệu.
Dùng Redis/cache như nguồn dữ liệu duy nhất mà không có durability plan.
Đổi hash(key) % N trực tiếp trong production khi thêm node.
Dùng hash function khác nhau giữa client.
Một physical node chỉ có một điểm ring rồi kỳ vọng cân bằng.
Auto-remove node chỉ vì health check chập chờn.
Backfill mà không capture concurrent writes.
Dual-write không idempotency, retry và reconciliation.
Cutover tất cả shard cùng lúc.
Xóa source ngay sau cutover.
Chỉ so row count rồi kết luận dữ liệu chính xác.
Bỏ qua hot tenant, cross-shard transaction và global uniqueness.
Cho rằng Redis Cluster dùng consistent-hash ring.
Production checklist
Phân biệt cache disposable và durable state.
Chọn shard key dựa trên query, transaction và distribution.
Hash function/canonical key format ổn định giữa mọi client.
Ring có đủ vnode hoặc dùng weighted rendezvous hashing.
Topology có version và được rollout atomic.
Replica tách failure domain.
Cache migration có old-ring fallback, warm-up và stampede control.
Database migration capture mọi concurrent write.
Backfill idempotent, có checkpoint và throttle.
Có checksum, invariant và shadow reads.
Cutover theo range/tenant, có canary.
Source được giữ trong rollback window.
Backup/PITR đã restore thử.
Redis Cluster reshard bằng hash slots và cluster-aware client.
Dashboard theo dõi skew, lag, divergence và p99.
Kết luận
Consistent Hashing giải quyết bài toán minimal remapping: khi cluster thay đổi, chỉ một phần key đổi owner. Virtual nodes cải thiện cân bằng; Rendezvous Hashing là lựa chọn đơn giản khác; Redis Cluster dùng fixed hash slots để reshard có kiểm soát.
Để thực sự “không mất dữ liệu”:
Với cache, giữ database là source of truth, fallback ring cũ, warm-up có kiểm soát và chống stampede.
Với database, dùng replication/CDC hoặc outbox, backfill idempotent, validate, cutover theo range và giữ rollback window.
Hashing quyết định dữ liệu nên đi đâu. Migration protocol mới quyết định dữ liệu có đến đó đầy đủ và an toàn hay không.
Giải thích kiến trúc Event Sourcing và CQRS trong core banking, từ immutable ledger, double-entry, balance projection, idempotency đến replay, reconciliation và eventual consistency.
Tìm hiểu Consensus Algorithms qua sơ đồ Mermaid: cách Raft và Paxos dùng majority quorum, leader election và replicated log để đảm bảo dữ liệu nhất quán.
Hướng dẫn thiết kế In-Memory Cache với LRU/LFU, Doubly Linked List và Hash Map: TypeScript implementation O(1), eviction policy, TTL, memory sizing và vận hành production.