Quorum — nhất quán đọc/ghi với R+W>N
Đọc R bản, ghi W bản; nếu R+W>N thì tập đọc và tập ghi luôn giao nhau nên đọc thấy ghi mới nhất. Tunable consistency và đánh đổi C vs A theo CAP.
TL;DR: Trong hệ thống replicate dữ liệu lên N node, đọc chờ R bản phản hồi và ghi chờ W bản xác nhận. Nếu R + W > N, tập đọc và tập ghi luôn giao nhau ít nhất một node — node đó chắc chắn có phiên bản mới nhất, đảm bảo đọc không bao giờ thấy dữ liệu cũ. Đây gọi là quorum. Điều chỉnh R và W theo yêu cầu: W=1 cho ghi nhanh, R=N cho đọc luôn mới nhất, hoặc W=R=⌊N/2⌋+1 cho cân bằng. Khi có partition (mạng bị chia), buộc phải chọn giữa consistency và availability — đây là bản chất CAP theorem.
Hệ thống lưu trữ hồ sơ bệnh nhân của bạn replicate mỗi bản ghi lên 3 node (N=3). Bác sĩ cập nhật liều thuốc của bệnh nhân (write). Hai giây sau, y tá đọc hồ sơ trên máy khác. Nếu hệ thống không có cơ chế quorum, y tá có thể đọc liều cũ từ node chưa nhận được update — hậu quả nghiêm trọng. Bài này giải thích cơ chế toán học đảm bảo điều đó không xảy ra.
1. Replication và bài toán consistency
Replication là việc lưu cùng một dữ liệu trên nhiều node để tăng availability và throughput đọc. Với N=3 replica:
- Khi write "liều = 500mg": gửi tới cả 3 node.
- Nếu network chậm, node3 nhận update muộn hơn 2 giây so với node1 và node2.
- Trong khoảng 2 giây đó, ai đọc từ node3 thấy "liều = 300mg" (giá trị cũ).
Đây gọi là eventual consistency — cuối cùng tất cả node đồng thuận, nhưng trong thời gian trung gian có sự không nhất quán. Với dữ liệu không quan trọng (like count trên mạng xã hội), acceptable. Với dữ liệu y tế, không chấp nhận được.
Câu hỏi: có cách nào đảm bảo đọc thấy write mới nhất mà không cần chờ tất cả N node xác nhận (vì như vậy quá chậm và không fault-tolerant)?
2. Quorum — giao điểm đọc/ghi
Định nghĩa: với N replica, write được coi là thành công khi W node xác nhận; read trả về kết quả khi R node phản hồi.
Điều kiện quorum: nếu R + W > N, tập W node được ghi và tập R node được đọc phải giao nhau ít nhất 1 node. Node đó chứa write mới nhất → read sẽ thấy giá trị đó (sau khi so sánh version).
Chứng minh ngắn: giả sử ngược lại — tập W và tập R không giao nhau. Vậy |W| + |R| ≤ N, tức R + W ≤ N — mâu thuẫn với giả thiết R + W > N. Vậy tập giao không rỗng.
flowchart TB
subgraph Write["Ghi (W=2 trên N=3)"]
WC["Client ghi"] --> WN1["Node 1 ✓"]
WC --> WN2["Node 2 ✓"]
WC -.->|"chưa đến"| WN3["Node 3 ✗"]
end
subgraph Read["Đọc (R=2 trên N=3)"]
RC["Client đọc"] --> RN2["Node 2 ✓"]
RC --> RN3["Node 3 ✓"]
RC -.->|"bỏ qua"| RN1["Node 1 ✗"]
end
WN2 -->|"Node 2 là giao điểm"| RN2Trong sơ đồ trên: Write đến Node 1 và Node 2 (W=2). Read hỏi Node 2 và Node 3 (R=2). Node 2 là giao điểm — nó có write mới nhất, đảm bảo read thấy giá trị đúng.
3. Pseudocode — read/write với quorum
-- Ghi với quorum (W acks cần thiết)
function quorumWrite(key, value, version, nodes, W):
acks <- 0
for each node in nodes:
gửi WriteRequest(key, value, version) tới node không đồng bộ
-- chờ đủ W ack
while acks < W:
response <- chờ ack tiếp theo (có timeout)
if response.ok:
acks <- acks + 1
if timeout hoặc tất cả node đã phản hồi mà acks < W:
return ERROR_WRITE_QUORUM_NOT_MET
return SUCCESS
// Time: O(W) ack chờ đợi Space: O(N) buffer responses
-- Đọc với quorum (R responses cần thiết)
function quorumRead(key, nodes, R):
responses <- []
for each node in nodes:
gửi ReadRequest(key) tới node không đồng bộ
-- chờ đủ R phản hồi
while responses.length < R:
response <- chờ response tiếp theo (có timeout)
if response.ok:
responses.append(response)
if timeout hoặc tất cả node đã phản hồi mà responses.length < R:
return ERROR_READ_QUORUM_NOT_MET
-- chọn phiên bản mới nhất trong R responses
return max(responses, by = version)
// Time: O(R) responses Space: O(R) buffer
Dòng max(responses, by = version) là then chốt: trong R responses, mỗi node trả về (value, version). Node trong giao điểm đọc/ghi có version cao nhất → client chọn đúng giá trị mới nhất. Version thường là timestamp (Lamport clock) hoặc vector clock (bài 04).
3.1 Ví dụ cụ thể N=3, R=2, W=2
| Thời điểm | Sự kiện | Node 1 | Node 2 | Node 3 |
|---|---|---|---|---|
| t=0 | Ghi "liều=500mg" (v=2) | v=2 ✓ | v=2 ✓ | v=1 (chưa nhận) |
| t=1 | Đọc, hỏi Node 2 + Node 3 | — | v=2 | v=1 |
| t=1 | Client nhận 2 response: max(v=2, v=1) = v=2 | 500mg ✓ |
R=2, W=2, N=3: R+W=4 > N=3 → quorum đảm bảo. Node 2 có trong cả tập ghi lẫn tập đọc.
4. Tunable consistency — đánh đổi theo yêu cầu
Điều chỉnh R và W cho các use case khác nhau:
| Cấu hình | R | W | Đặc tính | Dùng khi |
|---|---|---|---|---|
| Read-heavy | 1 | N | Đọc nhanh nhất, ghi chậm (chờ tất cả) | Analytics read-heavy, ghi hiếm |
| Write-heavy | N | 1 | Ghi nhanh nhất, đọc chậm (chờ tất cả) | Log ingestion, event streaming |
| Cân bằng (quorum) | ⌊N/2⌋+1 | ⌊N/2⌋+1 | Cân bằng, tolerate ⌊N/2⌋ failure | Dữ liệu quan trọng (y tế, tài chính) |
| Eventual | 1 | 1 | Nhanh nhất, không đảm bảo | Social feeds, like count |
Với N=3: R=W=2 (⌊3/2⌋+1=2). Tolerate 1 node failure: ngay cả khi 1 node chết, 2 node còn lại đủ để đạt quorum.
Cassandra gọi đây là CONSISTENCY LEVEL. Bạn set riêng cho từng query: CONSISTENCY QUORUM (R=W=⌊N/2⌋+1), CONSISTENCY ONE (R hoặc W=1), CONSISTENCY ALL (R hoặc W=N). Mỗi query có thể có consistency level khác nhau — ví dụ ghi critical dùng QUORUM, đọc dashboard dùng ONE.
5. Quorum và CAP theorem
CAP theorem phát biểu: một hệ thống phân tán không thể đồng thời đảm bảo cả ba tính chất — Consistency (đọc thấy write mới nhất), Availability (mọi request đều nhận response), Partition tolerance (hệ thống tiếp tục hoạt động khi network bị chia).
Trong thực tế, network partition là điều không thể tránh (P phải giữ). Vậy phải chọn giữa C và A:
flowchart LR
P["Network Partition\n(bắt buộc xảy ra)"] --> CHOICE{"Chọn C hay A?"}
CHOICE -->|"Chọn C"| CP["CP System\nTừ chối serve\nkhi không đạt quorum\n(Zookeeper, etcd, HBase)"]
CHOICE -->|"Chọn A"| AP["AP System\nVẫn serve dù stale\ndata (Cassandra,\nDynamo, CouchDB)"]Khi partition xảy ra và quorum không đạt được:
- CP (chọn C): trả lỗi, từ chối request. Đúng data hoặc không có gì.
- AP (chọn A): vẫn trả response từ node available, dù có thể stale. Sai data hơn là không có gì.
Cassandra mặc định AP: với CONSISTENCY ONE, vẫn serve kể cả khi partition. Với CONSISTENCY QUORUM, nó có thể fail nếu partition chia đôi cluster — hành xử như CP.
CAP không nói "chọn hai trong ba" — P không thể bỏ được trong hệ thống phân tán thực. CAP thực sự nói: khi partition xảy ra, chọn C hay A. Hầu hết hệ thống modern chọn AP với "tunable consistency" — cho phép từng use case quyết định mức độ consistency chấp nhận được.
6. Vấn đề version — cần vector clock
Quorum dùng version để chọn giá trị mới nhất trong R responses. Nhưng với write đồng thời từ nhiều client, timestamp đơn giản có thể sai: hai client ghi cùng lúc trên đồng hồ server khác nhau vài millisecond → thứ tự thực sự không được bảo toàn.
Vector clock (bài 04) giải quyết vấn đề này: thay vì một số timestamp, mỗi event mang một vector (một số/node), cho phép xác định quan hệ nhân quả chính xác — "write A xảy ra trước write B", "A và B đồng thời" (conflict cần resolve). Đây là bước tự nhiên tiếp theo sau quorum.
7. Liên hệ các bài khác
- Consistent hashing: quyết định key nào vào node nào (routing). Quorum quyết định cần bao nhiêu node xác nhận để đảm bảo consistency. Hai kỹ thuật cùng nhau tạo nền tảng Dynamo-style storage.
- Vector clock: cơ chế version chính xác hơn timestamp đơn giản — cần thiết khi có write đồng thời từ nhiều client. Quorum phát hiện có conflict, vector clock giúp resolve conflict đó.
- Gossip protocol: trong AP systems, sau khi quorum write thành công (W acks), các node còn lại nhận update qua gossip — đây là cơ chế anti-entropy đảm bảo eventual consistency.
- Case study Cassandra & etcd: Cassandra (AP, tunable) và etcd (CP, Raft-based) là hai cực của design space — xem cách chúng triển khai quorum/consensus trong production.
📚 Deep Dive
Bài báo gốc:
- DeCandia et al. (2007) — "Dynamo: Amazon's Highly Available Key-value Store", SOSP. Section 4 "System Interface" và Section 6 "Implementation" mô tả chi tiết quorum với tunable N/R/W và version bằng vector clock. Đây là tài liệu tham khảo thiết kế của Cassandra.
CAP theorem:
- Brewer (2000) — "Towards Robust Distributed Systems", PODC Keynote. Phát biểu gốc là conjecture; Gilbert & Lynch (2002) chứng minh formal trong "Brewer's Conjecture and the Feasibility of Consistent Available Partition-Tolerant Web Services", ACM SIGACT News.
PACELC extension:
- Abadi (2012) — "Consistency Tradeoffs in Modern Distributed Database System Design", IEEE Computer — mở rộng CAP thành PACELC: ngay cả khi không có partition, vẫn có đánh đổi giữa latency (L) và consistency (C).
Sách:
- Designing Data-Intensive Applications (Kleppmann), Chapter 5 "Replication" và Chapter 9 "Consistency and Consensus" — giải thích quorum, linearizability, và Raft trong cùng một khung nhìn mạch lạc.
Tóm tắt
- Replication lên N node tăng availability nhưng tạo ra inconsistency window khi node nhận update chậm hơn.
- Quorum: write chờ W ack, read chờ R response. Nếu R+W > N, tập đọc và tập ghi luôn giao nhau ≥1 node.
- Node giao điểm có write mới nhất → client chọn max(version) trong R responses để đọc đúng.
- Tunable: W=1 (ghi nhanh), R=N (đọc luôn mới nhất), R=W=⌊N/2⌋+1 (cân bằng, tolerate ⌊N/2⌋ failure).
- CAP theorem: khi partition xảy ra, chọn C (từ chối request, CP) hoặc A (serve stale data, AP).
- Version bằng timestamp đơn giản không đủ khi có write đồng thời — cần vector clock (bài 04).
Tự kiểm tra
Q1Với N=5, R=2, W=3: điều kiện quorum có thoả không? Tolerate được bao nhiêu node failure?▸
R+W = 2+3 = 5 = N. Điều kiện quorum yêu cầu R+W > N (strictly greater) — 5 không lớn hơn 5, vậy quorum không thoả. Có thể tồn tại write set và read set không giao nhau (ví dụ write vào Node 1,2,3 và read từ Node 4,5 — không giao).
Để quorum thoả với N=5, cần R+W ≥ 6, ví dụ R=3, W=3 (quorum chuẩn cho N=5). Cấu hình này tolerate 2 node failure: dù 2 node chết, còn 3 node đủ cho cả read lẫn write.
Q2Tại sao dòng `max(responses, by = version)` trong pseudocode read lại đủ để đảm bảo đọc đúng giá trị mới nhất? Giả thiết gì cần thoả?▸
Giả thiết: mỗi write tăng version (monotonically increasing). Khi R responses trả về, ít nhất một response đến từ node nằm trong giao điểm của tập đọc và tập ghi mới nhất — node đó có version cao nhất. max(responses, by = version) chọn đúng response đó.
Giả thiết quan trọng cần thoả: (1) version tăng đơn điệu — không có hai write khác nhau có cùng version; (2) mỗi write thực sự đến được W node (không bị drop silently). Nếu version dùng timestamp và clock drift lớn, hoặc nếu có write đồng thời không có vector clock, max(version) có thể chọn sai — đây là lý do Dynamo dùng vector clock thay vì timestamp đơn giản.
Q3Một startup dùng Cassandra để lưu số lượng like trên post mạng xã hội. Họ nên chọn CONSISTENCY ONE hay QUORUM? Lập luận.▸
CONSISTENCY ONE phù hợp hơn cho like count.
Lý do: like count là dữ liệu không quan trọng về tính chính xác tức thời — nếu user A like và user B thấy count cũ thêm 1 giây, không ai bị hại. Đổi lại, QUORUM tăng write latency (chờ ⌊N/2⌋+1 acks) và giảm availability (nếu một node chết, write vẫn phải đạt quorum). Với like count có thể lên đến hàng triệu/giây, throughput và availability quan trọng hơn consistency chặt chẽ. QUORUM nên dùng cho dữ liệu critical như số dư tài khoản, đơn hàng, hồ sơ y tế.
Q4Trong quorumWrite pseudocode, nếu W=2 nhưng sau khi gửi request tới 3 node, node1 ack, node2 timeout, node3 ack — hàm trả về gì? Data có consistent không?▸
Hàm trả về SUCCESS (đã đạt W=2 acks từ node1 và node3). Write được coi là thành công.
Node2 sẽ nhận update sau khi mạng phục hồi (qua gossip/anti-entropy, bài 05). Trong thời gian node2 chưa nhận update: nếu read hỏi {node2, node3} (R=2), node3 có version mới, node2 có version cũ → max(version) vẫn trả đúng (node3 là giao điểm). Nếu read hỏi chỉ node2 (R=1, CONSISTENCY ONE), có thể thấy stale data. Quorum (R+W > N) đảm bảo, nhưng CONSISTENCY ONE thì không.
Q5Tại sao CAP theorem nói không thể giữ cả C, A, P — nhưng nhiều hệ thống nói là 'strongly consistent AND highly available'?▸
Vì từ ngữ trong marketing khác formal definition trong CAP. CAP định nghĩa chặt: A = mọi request (kể cả khi có partition) đều nhận response (không phải error/timeout); C = linearizability (đọc luôn thấy write mới nhất nhất quán trên mọi node).
Hệ thống "highly available" trong thực tế thường nghĩa là "99.99% uptime" — không phải available trong mọi partition scenario. "Strongly consistent" đôi khi nghĩa là "eventual consistency rất nhanh" hoặc "sequential consistency" (yếu hơn linearizability). Khi partition thực sự xảy ra và mạng bị chia đôi, mọi hệ thống phân tán phải chọn: phục vụ (AP) hay từ chối để giữ đúng (CP). Không có hệ thống thực tế nào thoát khỏi đánh đổi này.
Q6etcd (dùng Raft, majority quorum) với N=5 node tolerate bao nhiêu node failure? Tại sao không dùng N=4 cho cùng mức tolerance?▸
N=5, majority quorum = ⌊5/2⌋+1 = 3. Hệ thống hoạt động khi còn ít nhất 3 node → tolerate 2 node failure.
N=4, majority = 3. Tolerate 4-3=1 node failure — kém hơn N=5 dù có thêm 1 node. Lý do toán học: với N chẵn, majority vẫn là N/2+1 nhưng số failure tolerate được là N/2-1 thay vì (N-1)/2. N=5 tolerate 2, N=4 tolerate 1 — tăng thêm 1 node (N=4→5) doubles fault tolerance. Đây là lý do production etcd cluster thường dùng N=3 (tolerate 1) hoặc N=5 (tolerate 2), không dùng N=2 hay N=4.
Bài tiếp theo: Merkle tree — phát hiện khác biệt hiệu quả
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
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