Hệ thống Dữ liệu Phân tán/Partition theo key-range vs hash — và bài toán hot spot
15/38
Bài 15 / 38~12 phútPhân mảnh (partitioning)Miễn phí lượt xem

Partition theo key-range vs hash — và bài toán hot spot

Hai cách chia dữ liệu ra partition: theo dải khoá (range) giữ thứ tự nhưng dễ hot spot, hay theo hash phân bố đều nhưng mất range query. Đánh đổi và skew.

TL;DR: Có hai cách cơ bản chia dữ liệu ra partition. Key-range: mỗi partition giữ một dải khoá liền nhau (a–f, g–m, ...), giữ thứ tự nên range query nhanh — nhưng dễ hot spot khi ghi dồn vào một dải (ví dụ khoá là timestamp, mọi ghi mới rơi vào partition cuối). Hash: băm khoá rồi chia theo giá trị hash, phân bố đều nên chống hot spot — nhưng mất thứ tự, range query phải hỏi mọi partition. Không cách nào thắng tuyệt đối: range đổi phân bố đều lấy range query; hash đổi range query lấy phân bố đều. Và cả hai vẫn có thể skew nếu một khoá đơn lẻ quá nóng (một celebrity, một sản phẩm viral) — lúc đó phải xử riêng khoá đó.

Module trước giữ cùng dữ liệu trên nhiều bản. Giờ ta chia khác dữ liệu ra nhiều node — và quyết định đầu tiên, quan trọng nhất, là chia theo cái gì. Chọn sai làm một node gánh gần hết tải trong khi các node khác ngồi chơi (hot spot), phá tan lợi ích của việc chia.

Bài này so hai cách chia, chỉ ra đánh đổi cốt lõi (phân bố đều ↔ range query), và cảnh báo cái bẫy skew mà cả hai đều dính.

Nhớ lại Hash function

Bạn đã học hàm hash biến một khoá bất kỳ thành một số phân bố đều, cố tình phá vỡ mọi thứ tự để tránh cụm. Chính tính chất "phân bố đều, xoá thứ tự" đó là lý do hash partition chống hot spot nhưng mất range query — hai mặt của cùng một đồng xu.

1. Analogy — thư viện xếp sách theo vần vs theo số ngẫu nhiên

Một thư viện lớn chia sách ra nhiều phòng. Cách A: xếp theo vần tên (phòng A–F, G–M, N–Z). Tìm một dải ("mọi sách vần L–N") cực nhanh — đi thẳng vào đúng phòng. Nhưng nếu tự dưng ai cũng mượn sách vần "H" (một tác giả nổi bật), phòng G–M quá tải trong khi các phòng khác vắng.

Cách B: mỗi sách dán một số ngẫu nhiên, chia phòng theo số. Lượng khách vào mỗi phòng đều nhau (không phòng nào bị dồn). Nhưng muốn "mọi sách vần L–N" thì chịu — chúng rải khắp mọi phòng, phải đi hỏi tất cả.

Thư việnPartitioning
Xếp theo vần (A–F, G–M...)Key-range partition
Tìm một dải vần cực nhanhRange query nhanh
Phòng "H" quá tải khi vần H hotHot spot
Dán số ngẫu nhiên, chia theo sốHash partition
Khách vào mỗi phòng đều nhauPhân bố đều, chống hot spot
Tìm một dải vần = hỏi mọi phòngRange query = scatter/gather
💡 Cách nhớ

Range = xếp theo vần (tìm dải nhanh, dễ dồn một phòng). Hash = dán số ngẫu nhiên (đều tải, mất tìm-theo-dải). Chọn theo việc bạn hay hỏi kiểu nào.

flowchart TB
  subgraph RANGE["Range partition (khoa tang dan)"]
    direction LR
    RW["Ghi moi"] --> RC["Partition cuoi<br/>HOT SPOT"]
  end
  subgraph HASH["Hash partition"]
    direction LR
    HW["Ghi moi"] --> H1["P1"]
    HW --> H2["P2"]
    HW --> H3["P3 (tai deu)"]
  end

2. Key-range partition — giữ thứ tự, nhưng dễ hot spot

Chia không gian khoá thành các dải liền nhau, mỗi partition giữ một dải: partition 1 giữ khoá af, partition 2 giữ gm, ... Ranh giới dải chọn theo phân bố dữ liệu để mỗi partition cỡ nhau.

Điểm mạnh — range query. Vì khoá trong partition đã sắp xếp và các partition liền dải, một truy vấn WHERE key BETWEEN 'g' AND 'j' chỉ chạm đúng partition chứa dải đó, quét tuần tự. Đây là điều hash không làm được.

Điểm yếu — hot spot khi khoá không đều. Cạm bẫy kinh điển: khoá là timestamp (hoặc ID tăng dần). Mọi ghi mới đều có timestamp lớn nhất → rơi vào cùng một partition cuối. Kết quả: partition đó nhận toàn bộ tải ghi, còn các partition khác chỉ giữ dữ liệu cũ, hầu như không được ghi.

Khoa = timestamp, range partition:
  partition A: 00:00-08:00 (du lieu cu, it ghi)
  partition B: 08:00-16:00 (du lieu cu)
  partition C: 16:00-now   <- MOI ghi moi roi vao day -> HOT SPOT
=> chia ra nhieu node nhung chi 1 node nhan tai ghi.

Cách chữa thường là đổi khoá: ghép một tiền tố phân tán vào trước timestamp (ví dụ <sensor_id>-<timestamp>), để ghi rải theo sensor_id — nhưng lúc đó range query "mọi dữ liệu trong giờ qua" lại phải hỏi nhiều partition. Đánh đổi không biến mất, chỉ dời chỗ.

3. Hash partition — phân bố đều, nhưng mất range query

Áp một hàm hash lên khoá để ra một số phân bố đều, rồi chia partition theo dải giá trị hash (hoặc theo consistent hashing — bài sau). Vì hash rải khoá đều bất kể phân bố gốc, tải ghi/đọc phân bố đều — kể cả khi khoá gốc là timestamp tăng dần (hash của các timestamp liền nhau rơi rải rác).

Điểm mạnh — chống hot spot theo dải. Không còn "partition cuối" nhận mọi ghi mới; các khoá liền nhau về giá trị bị hash tán đi khắp nơi.

Điểm yếu — mất thứ tự. Chính vì hash phá thứ tự, hai khoá liền nhau (user1001, user1002) rơi vào hai partition cách xa. Một range query giờ không có "dải" để đi — phải hỏi mọi partition rồi gộp (scatter/gather), chậm và tốn.

Khoa = timestamp, hash partition:
  hash(16:00:01) -> partition A
  hash(16:00:02) -> partition C   <- ghi lien nhau tan ra -> DEU tai
  hash(16:00:03) -> partition B
  Nhung: "moi ban ghi 16:00-17:00" -> phai hoi CA A,B,C roi gop.

4. Skew — vì sao hash cũng không cứu được "celebrity key"?

Hash chia đều theo số lượng khoá, nhưng không đều theo tải trên một khoá. Nếu một khoá đơn lẻ cực nóng — tài khoản của một người nổi tiếng nhận hàng triệu tương tác, một sản phẩm viral — thì mọi thao tác lên khoá đó hash về cùng một partition. Partition đó nóng rực dù tổng thể phân bố "đều". Đây là skew ở mức khoá, và cả range lẫn hash đều dính.

⚠️ Hash không chia nhỏ được một khoá nóng

Hash phân bố đều giữa các khoá khác nhau, không trong một khoá. hash(celebrity_id) luôn ra cùng một partition — thêm node cũng vô ích cho khoá đó. Cách xử phải làm ở tầng ứng dụng: thêm hậu tố ngẫu nhiên vào khoá nóng (celebrity_id-00..celebrity_id-09) để tách nó ra 10 partition, đổi lại đọc phải gộp 10 mảnh. Hệ thường chỉ áp mẹo này cho một số ít khoá được phát hiện là nóng, không cho mọi khoá.

5. Pitfall — chọn cách chia theo thói quen, không theo pattern truy vấn

Pitfall — hash mọi thứ, rồi ngạc nhiên vì range query chậm

Vì hash "nghe an toàn" (chống hot spot), nó hay được chọn mặc định cho mọi bảng — kể cả bảng mà truy vấn chính là range.

❌ Bang su kien, truy van chinh: "moi su kien trong 24h qua"
   -> chon HASH partition (vi so hot spot)
   -> range query nay bien thanh scatter/gather MOI partition -> cham

❌ Bang ghi log theo thoi gian -> chon RANGE theo timestamp
   -> moi ghi don vao partition cuoi -> hot spot ghi

Cả hai đều sai vì chọn cách chia không khớp pattern truy vấn/ghi thật.

Hướng đúng: chọn theo pattern truy cập chi phối. Nếu truy vấn chính là range theo khoá (thời gian, thứ tự) → key-range, và xử hot spot bằng cách đổi khoá (thêm tiền tố phân tán) nếu cần. Nếu truy cập chủ yếu là điểm theo khoá (tra một user, một order) và sợ hot spot → hash. Với khoá nóng đơn lẻ, xử riêng bằng hậu tố. Không có "cách chia mặc định" đúng cho mọi bảng.

6. 📚 Deep Dive

📚 Deep Dive — Partitioning of key-value data

Ghi chú: DDIA Ch6 là nguồn agnostic chuẩn. Điểm cốt lõi cần mang theo: cách chia quyết định cả phân bố tải lẫn loại truy vấn nào rẻ — chọn nó là chọn trước cả hai.

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

  • Module 2 — Nhân bản: partition không sống một mình — mỗi partition thường lại được nhân bản; "node giữ partition P" là "nhóm bản của P".
  • Bài 02 — Consistent hashing: hash partition đặt ra câu hỏi "thêm/bớt node thì xáo dữ liệu thế nào" — consistent hashing trả lời.
  • Bài 04 — Request routing: chia xong thì client tìm đúng partition ở đâu — bài routing.
  • Module 1 — Scalability: partitioning chính là hình thức scale-out cho dữ liệu; hot spot là lý do "chia ra" không tự động "nhanh hơn".

8. Tóm tắt

  • Partitioning chia dữ liệu lớn ra nhiều node; hai cách cơ bản: key-range và hash.
  • Key-range: giữ thứ tự → range query nhanh; nhưng hot spot khi ghi dồn vào một dải (khoá timestamp/tăng dần).
  • Hash: phân bố đều → chống hot spot theo dải; nhưng mất thứ tự → range query thành scatter/gather.
  • Đánh đổi cốt lõi: phân bố đều (hash) ↔ range query (range) — chọn theo pattern truy vấn chi phối.
  • Skew: cả hai vẫn hỏng nếu một khoá đơn lẻ quá nóng; hash không chia nhỏ được một khoá — phải thêm hậu tố tách riêng.
  • Anti-pattern: hash mọi thứ (range query chậm) hoặc range theo timestamp (hot spot ghi) — chọn theo truy vấn thật.

9. Tự kiểm tra

Tự kiểm tra
Q1
Nêu đánh đổi cốt lõi giữa key-range và hash partition. Vì sao không thể có cả 'phân bố đều' lẫn 'range query nhanh' từ một cách chia đơn giản?

Key-range giữ khoá theo thứ tự nên range query (BETWEEN, theo dải) chỉ chạm đúng partition chứa dải — nhanh; nhưng thứ tự khiến khoá liền nhau dồn một chỗ → hot spot khi ghi tập trung (timestamp). Hash phá thứ tự để phân bố đều → chống hot spot; nhưng mất thứ tự nên range query phải hỏi mọi partition (scatter/gather).

Không thể có cả hai từ một cách chia đơn giản vì chúng mâu thuẫn ở gốc: "range query nhanh" cần giữ thứ tự, còn "phân bố đều" cần phá thứ tự. Một cách chia phải chọn giữ hoặc phá — nên phải chọn ưu tiên theo pattern truy vấn. (Compound key hash-một-phần là cách dung hoà, không phải phá vỡ đánh đổi.)

Q2
Vì sao dùng timestamp làm khoá với key-range partition gây hot spot? Cách chữa và cái giá của nó?

Vì range partition đặt các khoá liền nhau vào cùng partition, mà timestamp tăng dần → mọi ghi mới đều có giá trị lớn nhất → rơi vào cùng một partition cuối. Partition đó nhận toàn bộ tải ghi, các partition khác chỉ giữ dữ liệu cũ — chia ra nhiều node nhưng một node gánh hết.

Chữa bằng đổi khoá: ghép một tiền tố phân tán trước timestamp (vd sensor_id-timestamp), để ghi rải theo tiền tố. Cái giá: range query "mọi dữ liệu giờ qua" giờ phải hỏi nhiều partition (mỗi tiền tố một chỗ) rồi gộp — đánh đổi không biến mất, chỉ dời từ ghi sang đọc.

Q3
Range query trên hash partition tệ ở điểm nào, và vì sao 'scatter/gather' là hệ quả bắt buộc?

Hash cố tình phá thứ tự: hai khoá liền nhau về giá trị (user1001, user1002) hash ra hai partition cách xa. Nên một range query không có "dải liền" nào để đi thẳng vào — dữ liệu cần rải khắp mọi partition.

Scatter/gather là hệ quả bắt buộc: phải scatter (gửi truy vấn tới tất cả partition) rồi gather (gộp kết quả) — vì không partition đơn lẻ nào chứa đủ dải. Điều này chậm (chờ partition chậm nhất — nhớ tail latency) và tốn tài nguyên tỉ lệ số partition. Đó là lý do hash không hợp workload có nhiều range query.

Q4
Vì sao hash partition KHÔNG giải quyết được 'celebrity key' (một khoá cực nóng)? Cách xử lý là gì?

Hash phân bố đều giữa các khoá khác nhau, không trong một khoá. Mọi thao tác lên celebrity key đều dùng hash(celebrity_id) ra cùng một giá trị → cùng một partition. Nên partition đó nóng rực dù tổng thể "đều"; thêm node cũng vô ích cho khoá đó (vẫn hash về một chỗ).

Xử ở tầng ứng dụng: thêm hậu tố ngẫu nhiên vào khoá nóng (celebrity_id-00..celebrity_id-09) để tách nó ra nhiều partition, đổi lại mỗi lần đọc phải gộp các mảnh lại. Vì tốn kém, hệ thường chỉ áp cho một số ít khoá được phát hiện nóng, không cho mọi khoá.

Q5
Một bảng log sự kiện IoT: ghi rất nhiều (theo thời gian), và truy vấn chính là 'mọi sự kiện của thiết bị X trong khoảng thời gian T'. Chọn cách chia nào và vì sao?

Truy vấn có hai chiều: theo thiết bị (điểm) và theo thời gian (dải). Chọn compound key (device_id, timestamp): partition theo hash của device_id để ghi phân bố đều giữa các thiết bị (chống hot spot ghi mà timestamp-thuần gây ra), rồi trong mỗi partition sắp theo timestamp để range theo thời gian *của một thiết bị* vẫn nhanh cục bộ.

Vì sao không range-thuần theo timestamp: ghi dồn vào partition cuối → hot spot. Vì sao không hash-thuần cả khoá: mất luôn range theo thời gian. Compound key lấy phần lớn ưu điểm của cả hai — đúng tinh thần "chọn theo pattern truy vấn chi phối".

Bài tiếp theo: Consistent hashing & rebalancing

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