Hệ thống Dữ liệu Phân tán/Consistent hashing & rebalancing — thêm/bớt node ít xáo trộn
16/38
Bài 16 / 38~12 phútPhân mảnh (partitioning)Miễn phí lượt xem

Consistent hashing & rebalancing — thêm/bớt node ít xáo trộn

Hash mod N làm thêm/bớt node phải xáo lại gần hết dữ liệu. Consistent hashing chỉ di chuyển phần nhỏ; virtual node cân bằng tải. Chiến lược rebalancing.

TL;DR: Hash partition (bài trước) phân bố đều, nhưng cách hash ngây thơ hash(key) mod N (N = số node) có một tật nghiêm trọng: đổi N là gần như mọi khoá đổi partition — thêm một node phải di chuyển gần hết dữ liệu, làm nghẽn cả cụm. Consistent hashing sửa điều đó: đặt cả node lẫn khoá lên một "vòng" hash, mỗi khoá thuộc về node kế tiếp trên vòng; thêm/bớt một node chỉ ảnh hưởng phần khoá kề node đó, không đụng phần còn lại. Virtual node (mỗi node vật lý = nhiều điểm trên vòng) làm tải cân hơn và cho node mạnh/yếu gánh khác nhau. Rebalancing là quá trình di chuyển partition khi cụm co giãn — làm sao vừa cân tải vừa ít di chuyển vừa không chặn dịch vụ.

Chọn hash partition xong, câu hỏi vận hành lớn nhất là: cụm sẽ co giãn (thêm/bớt node), lúc đó dữ liệu xáo trộn thế nào? Nếu mỗi lần thêm một máy phải chép lại gần hết dữ liệu qua mạng, việc mở rộng trở thành ác mộng. Bài này giải đúng bài toán đó.

1. Analogy — chia khách theo vị trí trên một vòng tròn

Tưởng tượng các node đứng rải trên một vòng tròn, và mỗi khách (khoá) cũng được đặt vào một điểm trên vòng theo hash. Quy tắc: mỗi khách thuộc về node đầu tiên gặp khi đi theo chiều kim đồng hồ từ vị trí của khách.

Giờ một node rời đi: chỉ những khách ngay trước nó trên vòng cần chuyển sang node kế tiếp — mọi khách khác không đổi node. Một node mới vào giữa hai node cũ: nó chỉ nhận phần khách nằm trong cung nó vừa chen vào — phần còn lại yên vị. Đây là điểm khác biệt cốt lõi so với "chia theo số dư": ở đó, thêm/bớt một node đổi số dư của mọi khách.

Vòng trònConsistent hashing
Node đứng rải trên vòngNode hash lên vòng
Khách đặt theo hashKhoá hash lên vòng
Khách thuộc node kế tiếp chiều kim đồng hồKhoá thuộc node kế trên vòng
Node rời → chỉ khách ngay trước chuyểnBớt node → chỉ khoá kề di chuyển
Một node = nhiều điểm trên vòngVirtual node
💡 Cách nhớ

mod N = thêm một node, cả lớp đổi chỗ. Consistent hashing = thêm một node, chỉ hàng ghế kề nó đổi chỗ. Đó là toàn bộ giá trị của nó.

flowchart LR
  subgraph MOD["hash mod N — them 1 node"]
    M1["N: 4 -> 5"] --> M2["GAN MOI khoa doi partition<br/>(di chuyen khong lo)"]
  end
  subgraph CH["Consistent hashing — them 1 node"]
    C1["Chen 1 diem tren vong"] --> C2["Chi khoa cung KE di chuyen<br/>(~1/N)"]
  end

2. Vì sao hash(key) mod N hỏng khi đổi N?

Cách hash ngây thơ: partition = hash(key) mod N. Khi N cố định, nó phân bố đều — ổn. Nhưng thêm một node (N → N+1) đổi số chia, nên hash(key) mod Nhash(key) mod (N+1) cho kết quả khác nhau với gần như mọi khoá.

Thử đoán trước

Có N=4 node, hàng triệu khoá chia bằng hash(key) mod 4. Bạn thêm 1 node (N=5). Đừng đoán chung chung "nhiều hay ít" — thử tính ra con số chính xác: bao nhiêu phần trăm khoá sẽ đổi node? Gợi ý: so hash(key) mod 4 với hash(key) mod 5 cho từng giá trị hash liên tiếp, xem khi nào hai kết quả trùng nhau.

hash(key)=116:
  mod 4 = 0  -> mod 5 = 1   <- DOI node!
hash(key)=117:
  mod 4 = 1  -> mod 5 = 2   <- DOI node!
hash(key)=118:
  mod 4 = 2  -> mod 5 = 3   <- DOI node!
hash(key)=119:
  mod 4 = 3  -> mod 5 = 4   <- DOI node!
hash(key)=120:
  mod 4 = 0  (node 0)
  mod 5 = 0  (node 0)  -> may man khong doi
=> 4/5 khoa (80%) roi vao truong hop doi -> di chuyen GAN HET du lieu.
Tong quat: mot khoa GIU nguyen bucket khi (h mod 4) == (h mod 5), va dieu
nay chi dung khi h ≡ 0,1,2,3 (mod 20, voi 20 = lcm(4,5)) -> 4/20 = 20% giu,
con lai 80% phai doi node.

Kết quả: thêm một node đáng lẽ chỉ cần "san bớt" một phần dữ liệu, nhưng mod N bắt di chuyển gần hết — băng thông mạng bùng nổ, dịch vụ chậm suốt quá trình. Đây là lý do không hệ phân tán thật nào dùng mod N trần.

3. Consistent hashing — chỉ di chuyển phần kề

Nhớ lại Consistent hashing

Bạn đã học cơ chế consistent hashing ở track thuật toán: hash cả khoá lẫn node lên cùng một vòng, mỗi khoá thuộc node kế tiếp; thêm/bớt node chỉ di chuyển O(số khoá / số node) thay vì O(tất cả). Bài này là ứng dụng hệ thống của nó — áp vào việc phân partition dữ liệu.

Recall cho bạn kết luận; mục này dẫn lại cơ chế ngay tại chỗ vì đó chính là objective của bài — bài phải tự đứng được, không bắt người học nhảy sang track khác mới hiểu.

Đặt cả node lẫn khoá lên một vòng. Lấy một hàm hash có miền giá trị lớn (ví dụ 0 tới 2^32-1) rồi "uốn" miền đó thành vòng tròn (giá trị lớn nhất nối lại 0). Hash mỗi node (theo id/địa chỉ) ra một điểm trên vòng; hash mỗi khoá ra một điểm trên vòng. Quy tắc sở hữu: mỗi khoá thuộc node đầu tiên gặp khi đi theo chiều kim đồng hồ từ điểm của khoá — tức mỗi node "sở hữu" cung chạy từ node liền trước (ngược chiều kim) tới chính nó.

Vong hash (mien 0..2^32-1 uon thanh vong tron):
   ...A........B............C......D...   (roi quay lai A)
   khoa k1 roi trong cung (A..B]  -> thuoc B (node ke chieu kim dong ho)
   khoa k2 roi trong cung (B..C]  -> thuoc C
=> B so huu cung (A..B]; C so huu cung (B..C]; ...

Vì sao thêm một node chỉ di chuyển ~1/N khoá? Vị trí một node trên vòng chỉ phụ thuộc hash(node), không phụ thuộc N. Thêm node X = hash X ra một điểm mới rồi chèn vào giữa hai node cũ đang kề nhau, ví dụ giữa B và C. X chỉ "cướp" phần cung nằm giữa B và X (vốn thuộc C): chỉ các khoá rơi vào đoạn đó chuyển từ C sang X. Mọi cung khác — cung (A..B], phần (X..C] còn lại, cung (C..D]... — không đổi chủ. Với N node rải tương đối đều, mỗi node giữ trung bình ~1/N vòng, nên node mới cắt đúng một cung → trung bình chỉ ~1/N lượng khoá phải di chuyển, và chỉ một node cũ phải nhường. Bớt node đối xứng: xoá điểm của nó, cung của nó dồn sang node kế theo chiều kim.

Đây chính là cơ chế các store kiểu Dynamo/Cassandra dùng để chọn node giữ mỗi khoá (và cả các bản nhân của nó — nhớ replication + partition đi cùng nhau: R bản của một khoá thường là R node kế tiếp trên vòng, với R = replication factor, khác N = tổng số node cụm).

4. Virtual node — cân tải và xử lý node không đều

Thử đoán trước

Trong consistent hashing thuần (chưa có virtual node), một node đột ngột chết. Toàn bộ khoá nó đang giữ dồn đi đâu — chia đều cho mọi node còn lại, hay dồn hết vào một node? Lần theo quy tắc "khoá thuộc node kế theo chiều kim đồng hồ" ở mục 3 để đoán trước khi đọc tiếp.

Consistent hashing thuần có hai vấn đề: (1) với ít node, các cung trên vòng không đều → tải lệch; (2) khi một node chết, toàn bộ cung của nó dồn sang một node kế — node đó đột ngột gánh gấp đôi.

Virtual node (vnode) sửa cả hai: mỗi node vật lý được đặt lên vòng ở nhiều điểm (ví dụ mỗi node = 100–200 vnode). Nhờ nhiều điểm rải khắp vòng, cung của mỗi node vật lý là tổng nhiều mảnh nhỏ rải đều → tải cân hơn; và khi một node chết, các mảnh của nó phân sang nhiều node kế khác nhau, không dồn một chỗ.

Khong vnode: node B chet -> toan bo cung B -> node C (C gap doi tai)
Co vnode   : B = {b1,b2,...,b100} rai khap vong
             B chet -> b1->C, b2->A, b3->D, ... -> tan deu, khong ai gap doi

Vnode còn cho node không đều: máy mạnh gấp đôi thì cấp gấp đôi số vnode → nhận gấp đôi tải, tự nhiên.

5. Rebalancing — di chuyển partition khi cụm co giãn

Rebalancing là quá trình chuyển partition giữa các node khi thêm/bớt. Mục tiêu: (a) tải cân sau khi xong, (b) di chuyển ít nhất có thể, (c) không chặn dịch vụ trong lúc chuyển. Một cách hay bị hiểu nhầm là "chia lại động theo mod N" — sai như mục 2.

Chiến lược phổ biến (và đơn giản đáng ngạc nhiên): cố định SỐ partition, chọn nhiều hơn số node hẳn (ví dụ 1000 partition cho 10 node = 100 partition/node). Mỗi khoá thuộc một partition cố định (partition không đổi khi thêm node); rebalancing chỉ là gán lại vài partition nguyên khối cho node mới. Thêm node thứ 11 → lấy bớt vài partition từ mỗi node cũ giao cho node mới, di chuyển đúng phần đó. Vì số partition không đổi, ánh xạ khoá→partition ổn định; chỉ ánh xạ partition→node thay đổi.

Đây là một chiến lược độc lập, song song với consistent hashing ở mục 3 — không phải cách "hiện thực hoá" consistent hashing, mà là một lối tiếp cận khác nhắm cùng mục tiêu ít-di-chuyển (hash trực tiếp node+khoá lên vòng, so với hash khoá vào B partition cố định rồi gán partition→node).

6. Pitfall — rebalancing tự động, toàn bộ, đúng lúc tải cao

Pitfall — để rebalancing chạy ồ ạt và tự động không kiểm soát

Hai cạm bẫy vận hành:

❌ Dung hash(key) mod N cho phan partition -> them 1 node -> di chuyen gan het
   -> mang nghen, dich vu cham hang gio

❌ Rebalancing hoan toan tu dong + tuc thi khi phat hien node "cham"
   -> node chi tam cham (GC/mang) bi tuong chet -> tu dong chuyen du lieu di
   -> cham hon -> lai tuong chet node khac -> hieu ung domino

Hệ quả: hoặc di chuyển khổng lồ không cần thiết, hoặc rebalancing tự phát dây chuyền làm sập cụm đang khoẻ.

Hướng đúng: dùng cơ chế ít-di-chuyển (consistent hashing / số partition cố định), giới hạn băng thông rebalancing để không bóp nghẹt lưu lượng thật, và giữ một mức người-trong-vòng-lặp cho quyết định thêm/bớt node (rebalancing tự động hoàn toàn dễ nhầm "chậm" với "chết" — đúng bài toán bạn sẽ gặp lại ở Module 5). Co giãn nên là thao tác có kiểm soát, không phải phản xạ tức thì.

7. 📚 Deep Dive

📚 Deep Dive — Rebalancing partitions
  • Designing Data-Intensive Applications (Kleppmann) — Chương 6, mục "Rebalancing Partitions" — vì sao hash mod N sai, chiến lược "fixed number of partitions", dynamic partitioning, và vận hành rebalancing (tự động vs thủ công).
  • David Karger et al. (1997), "Consistent Hashing and Random Trees" — paper gốc của consistent hashing; ý cân tải bằng cách đặt mỗi node lên nhiều điểm trên vòng đã có trong chính paper này. Thuật ngữ "virtual node" được phổ biến và hiện thực rộng rãi trong thiết kế Dynamo (DeCandia et al., 2007).

Ghi chú: DDIA Ch6 nhấn: nhiều hệ dùng "số partition cố định, nhiều hơn số node" (mục 5) như một chiến lược rebalancing độc lập — hash khoá vào B partition cố định rồi gán partition→node, tách biệt với consistent hashing (vốn hash trực tiếp cả node lẫn khoá lên cùng một vòng). Cả hai đều nhắm mục tiêu ít-di-chuyển, nhưng là hai cơ chế song song, không phải cái này là "hiện thực" của cái kia.

8. Liên hệ các bài khác

  • Bài 01 — Range vs hash: consistent hashing là cách hiện thực hash partition sao cho co giãn rẻ — nối thẳng từ đánh đổi bài trước.
  • Module 2 — Leaderless: Dynamo dùng consistent hashing để chọn nhóm node giữ mỗi khoá (partition) + quorum để nhân bản — partition và replication gắn ở đây.
  • Bài 04 — Request routing: khi partition di chuyển lúc rebalancing, client phải biết mapping mới — bài routing.
  • Module 5 — Partial failure: rebalancing tự động dễ nhầm "chậm" với "chết" — vì sao co giãn cần kiểm soát.

9. Tóm tắt

  • hash(key) mod N phân bố đều nhưng đổi N là gần mọi khoá đổi node → di chuyển khổng lồ; không dùng được cho cụm co giãn.
  • Consistent hashing: node + khoá trên một vòng, khoá thuộc node kế; thêm/bớt node chỉ di chuyển phần kề (~1/N), không đụng phần còn lại.
  • Virtual node: mỗi node = nhiều điểm trên vòng → tải cân hơn, node chết tán đều sang nhiều node, và node mạnh/yếu nhận tải khác nhau.
  • Rebalancing: cân tải + ít di chuyển + không chặn dịch vụ; chiến lược thực dụng = cố định số partition (nhiều hơn số node), gán lại partition nguyên khối cho node mới.
  • Anti-pattern: mod N (di chuyển khổng lồ) hoặc rebalancing tự động tức thì (nhầm chậm với chết → domino). Co giãn nên có kiểm soát.

10. Tự kiểm tra

Tự kiểm tra
Q1
Vì sao `hash(key) mod N` gây di chuyển dữ liệu khổng lồ khi thêm một node? Ước lượng phần khoá phải đổi node.

Vì partition được tính từ số chia N: thêm một node đổi N thành N+1, mà hash(key) mod Nhash(key) mod (N+1) cho kết quả khác nhau với gần như mọi khoá (chỉ số ít may mắn trùng). Vị trí partition của một khoá phụ thuộc N, nên đổi N là "xáo lại cả bàn".

Phần khoá phải đổi node xấp xỉ 1 - 1/(N+1) — gần như toàn bộ (với N lớn, gần 100%), thay vì lý tưởng chỉ ~1/(N+1) (phần "san" cho node mới). Đó là lý do không dùng mod N trần cho hệ co giãn.

Q2
Consistent hashing giảm di chuyển thế nào khi thêm/bớt node? Vì sao chỉ 'phần kề' bị ảnh hưởng?

Node và khoá đều hash lên một vòng; mỗi khoá thuộc node kế tiếp theo một chiều. Vị trí một node trên vòng không phụ thuộc N — nó cố định theo hash của node đó.

Nên thêm một node = chen một điểm mới vào vòng: nó chỉ "giành" phần khoá nằm trong cung ngay trước nó (vốn thuộc node kế tiếp), mọi khoá ở cung khác không đổi chủ. Bớt node = xoá một điểm: chỉ cung của nó chuyển sang node kế. Trung bình chỉ ~1/N khoá di chuyển, không phải gần hết — vì quyền sở hữu là *cục bộ theo cung*, không tính lại toàn cục theo N.

Q3
Virtual node giải quyết hai vấn đề gì của consistent hashing thuần? Giải thích cơ chế.

(1) Tải lệch: với ít node, các cung trên vòng không đều → node ôm cung to gánh nhiều. (2) Node chết dồn một chỗ: toàn bộ cung của node chết chuyển sang một node kế → node đó đột ngột gấp đôi tải.

Virtual node đặt mỗi node vật lý lên vòng ở nhiều điểm (100–200 vnode). Cung của một node giờ là tổng nhiều mảnh nhỏ rải khắp vòng → cân hơn (luật số lớn). Khi node chết, các mảnh của nó phân sang nhiều node kế khác nhau → không ai gấp đôi. Bonus: cấp nhiều vnode hơn cho máy mạnh → nó nhận nhiều tải hơn, tự nhiên xử lý node không đều.

Q4
Chiến lược 'cố định số partition (nhiều hơn số node)' hoạt động thế nào khi thêm node, và vì sao nó ổn định hơn rehash động?

Chọn số partition cố định lớn hơn số node hẳn (vd 1000 partition / 10 node). Mỗi khoá thuộc một partition cố định — ánh xạ khoá→partition không đổi khi cụm co giãn. Chỉ ánh xạ partition→node thay đổi: thêm node thứ 11 → lấy vài partition nguyên khối từ mỗi node cũ giao cho node mới.

Ổn định hơn rehash động vì: (a) khoá không bao giờ đổi partition → chỉ di chuyển đúng các partition được gán lại, lượng dự đoán được; (b) không phụ thuộc N trong công thức hash → không có hiệu ứng "đổi N xáo tất cả"; (c) rebalancing là thao tác rời rạc, rõ ràng (chuyển partition X từ node A sang B), dễ kiểm soát và giới hạn băng thông.

Q5
Vì sao rebalancing hoàn toàn tự động và tức thì có thể gây hiệu ứng domino làm sập cụm đang khoẻ?

Vì hệ không phân biệt được node "tạm chậm" (GC pause, mạng nghẽn nhất thời) với node "đã chết" (đúng bài toán Module 5). Rebalancing tự động tức thì thấy một node chậm → kết luận nó chết → bắt đầu chuyển dữ liệu của nó đi.

Việc chuyển dữ liệu tốn băng thông và CPU, làm các node liên quan chậm thêm → hệ lại tưởng node khác chết → chuyển tiếp → càng chậm. Vòng xoáy này (domino) có thể quật ngã cả cụm vốn đang khoẻ, chỉ vì một trục trặc tạm thời bị hiểu là chết. Đó là lý do nên giữ người trong vòng lặp cho quyết định thêm/bớt node và giới hạn tốc độ rebalancing.

Bài tiếp theo: Secondary index phân mảnh — local vs global

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên