Hệ thống Dữ liệu Phân tán/Từ ACID single-node tới giao dịch phân tán
21/38
Bài 21 / 38~12 phútTransaction phân tánMiễn phí lượt xem

Từ ACID single-node tới giao dịch phân tán

Ôn ACID và isolation ở một node rồi hỏi: điều gì vỡ khi giao dịch trải trên nhiều node? Cầu nối từ khoá SQL sang bài toán phân tán.

TL;DR: Transaction cho hai đảm bảo chính: atomicity (mọi thao tác cùng commit hoặc cùng huỷ, không nửa vời) và isolation (giao dịch đồng thời không giẫm chân nhau, như chạy tuần tự). Trên một node, một write-ahead log và một lock/MVCC manager lo cả hai. Khi giao dịch trải trên nhiều node, cả hai vỡ: atomicity cần mọi node đồng ý commit dù mạng có thể chết giữa chừng — sinh ra 2PC; isolation cần ngăn anomaly xuyên node mà không có lock manager trung tâm. Bài này ôn ACID và chỉ rõ điều gì vỡ khi lên nhiều node, đặt nền cho các bài sau.

Bạn đã thấy transaction "chạy đẹp" trên một database. Câu hỏi của module: khi tài khoản A ở node 1 và tài khoản B ở node 2, làm sao "trừ A, cộng B" vẫn hoặc-cả-hai-hoặc-không-cái-nào — khi node 2 có thể chết đúng lúc node 1 vừa trừ xong? Trước khi giải, phải hiểu rõ cái gì trở nên khó.

Nhớ lại ACID

Ở khoá SQL, bạn học ACID: Atomicity (all-or-nothing), Consistency (giữ ràng buộc), Isolation (giao dịch đồng thời không phá nhau), Durability (đã commit thì bền). Trên một node, atomicity và durability dựa vào write-ahead log; isolation dựa vào lock/MVCC. Bài này hỏi: hai trụ đó đứng thế nào khi dữ liệu trải nhiều node.

1. Analogy — hai két sắt ở hai thành phố

Trên một node, transaction như một két sắt có người gác: mọi thay đổi ghi vào sổ nháp, tới lúc "commit" thì đóng dấu một phát — hoặc xé bỏ toàn bộ nếu huỷ. Một người gác, một quyết định, không ai chen.

Giờ tiền của bạn nằm trong hai két ở hai thành phố (hai node), và bạn muốn chuyển từ két A sang két B. Không còn một người gác chung. Bạn nhắn hai nơi "trừ 100 ở A, cộng 100 ở B". Nếu A trừ xong mà B mất điện trước khi cộng — tiền bốc hơi. Nếu bạn bảo cả hai "chuẩn bị" rồi mới "chốt", nhưng người báo chốt lăn ra ngất giữa chừng — A không biết nên chốt hay huỷ, ngồi treo giữ tiền. Đây chính là hai cái khó: cùng quyết (atomicity) và không biết đối phương chết hay chậm.

Két sắtTransaction
Một két, một người gácSingle-node: một WAL, một lock manager
Đóng dấu một phát / xé bỏCommit / abort atomic
Hai két hai thành phốDữ liệu trên nhiều node
Trừ A xong, B mất điệnAtomicity vỡ (nửa vời)
Người báo chốt ngất giữa chừngCoordinator chết → blocking
💡 Cách nhớ

Một node = một người gác, dễ. Nhiều node = nhiều két không người gác chung — phải bịa ra "nghi thức cùng chốt", và nghi thức đó chính là 2PC (và điểm yếu của nó).

2. Ôn nhanh — vì sao một node đủ để đảm bảo cả hai

Trên một node, cả atomicity (all-or-nothing) lẫn isolation (đồng thời như tuần tự) đứng được nhờ một điểm điều phối trung tâm: một write-ahead log lo atomicity (ghi ý định trước; commit = đóng một dấu "committed"; crash trước dấu đó thì lúc khởi động lại rollback phần dở), và một lock/MVCC manager duy nhất lo isolation (điều phối khoá hoặc ảnh chụp nhất quán cho mọi giao dịch).

Single-node transaction (chuyen tien trong 1 DB):
  BEGIN
    UPDATE acc SET bal = bal - 100 WHERE id = A   -- ghi WAL
    UPDATE acc SET bal = bal + 100 WHERE id = B   -- ghi WAL
  COMMIT                                           -- 1 dau "committed" -> atomic
  => WAL + lock manager cua CUNG mot node lo atomicity + isolation.

Mấu chốt mang sang phần sau: cả hai đảm bảo cùng dựa vào MỘT thứ đó. Distributed transaction khó chính vì thứ đó không còn tồn tại một cách tự nhiên khi dữ liệu trải nhiều node — và phần dưới sẽ lần theo đúng cơ chế WAL để thấy nó vỡ ở đâu.

3. Điều gì vỡ khi lên nhiều node?

Đặt hai tài khoản ở hai node và thử "gọi hai update trong một khối code" như thể vẫn là một giao dịch. Trước khi xem nó vỡ ở đâu, hãy tự lần theo cơ chế WAL vừa ôn.

Thử đoán trước

Node 1 vừa commit xong A -= 100 (dấu "committed" đã ghi bền vào WAL của riêng node 1). Đúng lúc đó node 2 chết trước khi kịp commit B += 100. Trong hệ có "thứ" nào tự động cuộn lại phần node 1 đã làm để giữ all-or-nothing không? Lần theo cơ chế WAL từng-node ở mục 2 rồi đoán — trước khi đọc tiếp.

Chuyen tien A (node 1) -> B (node 2), goi hai update trong 1 khoi code:

t0  Node1: BEGIN; A -= 100; ghi WAL rieng
t1  Node1: COMMIT -> WAL node1 dong dau "committed"
        -> A giam 100 that su, da BEN (durable)
t2  Node2: BEGIN; B += 100; ghi WAL rieng (chua commit)
t3  Node2: CHET (mat dien) TRUOC khi COMMIT
t4  Node2 song lai: WAL node2 KHONG co dau "committed"
        -> rollback phan do cua RIENG no -> B khong doi

Ket qua: A: -100 (da tru, ben) | B: +0 (khong cong)
        -> mot NUA giao dich da hieu luc -> mat 100, vi pham "tong khong doi"

Lần theo cơ chế: ở t4, WAL của node 2 làm đúng việc của nó — thấy không có dấu "committed" nên rollback phần dở của chính node 2. Nhưng đó là phục hồi cục bộ. Không có một WAL chung hay một lock manager chung nào bắc qua cả hai node để thấy bức tranh "node 1 đã commit, node 2 fail → phải cuộn lại node 1". Còn node 1, từ góc nhìn của nó, giao dịch đã thành công và bền — nó không có lý do gì tự revert. Không ai giữ bức tranh toàn cục để kích hoạt rollback — chính là "điểm điều phối trung tâm" đã biến mất. Đây là cơ chế gốc của mọi thứ còn lại trong module: để cuộn lại được, các node phải thoả thuận trước khi bất kỳ ai commit (2PC, bài 03), hoặc bù trừ sau khi đã commit (saga, bài 04).

Hai node hai WAL rieng va cho trong noi le ra phai co quyet dinh chung

Từ mini-trace đó, hai đảm bảo vỡ theo hai hướng:

Atomicity vỡ vì mỗi node commit độc lập. Node 1 có WAL của nó, node 2 có WAL của nó. Node 1 có thể commit thành công trong khi node 2 fail — và không có "một dấu committed chung" để cả hai cùng nhìn. Cần một nghi thức để mọi node đồng ý cùng commit hoặc cùng abort, chịu được việc một node/mạng chết giữa chừng. Đó là two-phase commit (bài 03) — và nó phải cử một coordinator đóng vai "người báo chốt", kéo theo điểm yếu blocking.

Isolation vỡ vì không có lock manager chung. Hai giao dịch đụng dữ liệu ở các node khác nhau; không manager đơn lẻ nào thấy toàn cảnh để ngăn chúng giẫm chân. Bài 02 ôn lại các anomaly (lost update, write skew) ở mức single-node làm nền — vì khi phân tán, đảm bảo "serializable" (như thể tuần tự) còn khó hơn nhiều, và đó không phải thứ 2PC (bài 03) giải được (2PC chỉ lo atomicity, không lo isolation xuyên node).

⚠️ Consistency (chữ C) không phải việc của database phân tán

Trong ACID, Consistency nghĩa là "giao dịch giữ các ràng buộc nghiệp vụ đúng" (số dư không âm, tổng tiền không đổi) — nhưng đó là trách nhiệm của ứng dụng (viết đúng logic), không phải thứ database tự bảo đảm. Database cấp atomicity + isolation làm công cụ để ứng dụng đạt consistency. (Đừng nhầm chữ C này với "consistency" trong CAP hay trong replication — ba thứ tên giống nhau nhưng khác nghĩa; Module 6 sẽ tách bạch.)

4. Pitfall — bê nguyên tư duy single-node transaction lên nhiều node

Pitfall — 'cứ BEGIN...COMMIT là atomic' khi dữ liệu ở nhiều node

Sai lầm: giả định cứ mở một transaction bao quanh vài thao tác ở các service/DB khác nhau là chúng atomic — như trên một node.

❌ Chuyen tien: node1.trUpdate(A -= 100); node2.trUpdate(B += 100);
   xem nhu "atomic" vi co goi trong 1 khoi code
   -> node1 commit xong, node2 mat mang -> A mat 100, B khong nhan -> tien bay
   -> khong co WAL chung -> khong co all-or-nothing that su

❌ Tuong isolation tu dong dung xuyen service vi "moi service co transaction"
   -> hai giao dich o hai node van co the tao anomaly (bai 02)

Hệ quả: mất tính atomic im lặng — code trông như giao dịch nhưng thực chất là hai thao tác độc lập có thể chỉ một cái thành công.

Hướng đúng: nhận ra atomicity/isolation xuyên node không miễn phí — nó cần một cơ chế tường minh (2PC) đắt và mong manh, hoặc một thiết kế tránh nó (saga, bài 04). Trước khi viết "transaction xuyên node", hỏi: thao tác này có thật cần atomic xuyên node, hay có thể tách thành các bước bù trừ được? Rất thường là được — và rẻ hơn nhiều.

5. 📚 Deep Dive

📚 Deep Dive — Transactions
  • Designing Data-Intensive Applications (Kleppmann) — Chương 7 "Transactions" — ACID chính xác nghĩa gì, vì sao "Consistency" là trách nhiệm ứng dụng, và vì sao atomicity/isolation khó khi phân tán.
  • Khoá SQL của OLHub (module Transactions & consistency) là nền single-node: ACID, isolation levels, MVCC — bài này mở rộng đúng các khái niệm đó ra nhiều node.

Ghi chú: DDIA Ch7 nhấn: nhiều "đảm bảo" của transaction là công cụ giảm tải suy luận cho lập trình viên; khi phân tán, cái giá của các đảm bảo đó tăng vọt — nên phải cân nhắc có thật cần không.

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

7. Tóm tắt

  • Cả hai dựa vào một điểm điều phối trung tâm — thứ biến mất khi dữ liệu trải nhiều node.
  • Atomicity xuyên node: mỗi node commit độc lập → cần nghi thức "cùng quyết" chịu lỗi giữa chừng → 2PC (bài 03).
  • Isolation xuyên node: không có lock manager chung → anomaly xuyên node, serializable khó hơn (bài 02).
  • Consistency (chữ C) là trách nhiệm ứng dụng; database cấp atomicity + isolation làm công cụ.

8. Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Phân biệt atomicity và isolation. Trên một node, cơ chế nào đảm bảo mỗi cái?
  2. Q2
    Vì sao atomicity 'vỡ' khi giao dịch trải trên nhiều node? Điều gì thiếu so với single-node?
  3. Q3
    Trong chuyển tiền A→B với A ở node 1, B ở node 2: kịch bản nào làm mất tính atomic nếu chỉ 'gọi hai update trong một khối code'?
  4. Q4
    Vì sao chữ 'C' (Consistency) trong ACID không phải thứ database tự đảm bảo, và nó liên quan thế nào tới atomicity + isolation?
  5. Q5
    Trước khi viết một 'transaction xuyên node', câu hỏi nào nên hỏi, và vì sao?

Bài tiếp theo: Anomaly & serializability — lost update, write skew, SSI

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

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

Bài tiếp theo

Anomaly & serializability — lost update, write skew, SSI