Hệ thống Dữ liệu Phân tán/Single-leader — một bản nhận ghi, các bản còn lại theo sau
8/38
Bài 8 / 38~12 phútNhân bản (replication)Miễn phí lượt xem

Single-leader — một bản nhận ghi, các bản còn lại theo sau

Kiến trúc replication phổ biến nhất: một leader nhận mọi ghi rồi phát log cho các follower. Sync vs async replication, dựng follower mới, và failover khi leader chết.

TL;DR: Single-leader (còn gọi master-slave, primary-replica) là kiến trúc replication phổ biến nhất: một node được chọn làm leader, mọi thao tác ghi phải đi qua nó; leader ghi vào bản của mình rồi phát một replication log cho các follower, mỗi follower áp cùng thứ tự thay đổi đó. Đọc thì được từ bất kỳ bản nào. Câu hỏi thiết kế then chốt là sync hay async: replication đồng bộ (leader chờ follower xác nhận) đảm bảo bản sao không mất nhưng chậm và mong manh nếu follower treo; replication bất đồng bộ (leader không chờ) nhanh và bền với follower chậm, nhưng có thể mất ghi vừa nhận nếu leader chết trước khi kịp phát. Khi leader chết, hệ phải failover — chọn follower mới lên làm leader — một thao tác đầy cạm bẫy (mất ghi, split-brain).

Đây là điểm khởi đầu tự nhiên cho replication: đơn giản, có sẵn trong hầu hết database quan hệ, và là nền để hiểu hai kiến trúc phức tạp hơn ở các bài sau. Giải thích được single-leader nghĩa là giải thích được replication logsync-vs-async — hai khái niệm bạn sẽ dùng lại suốt module.

Nhớ lại ACID & write-ahead log

Ở khoá SQL, bạn thấy database ghi mọi thay đổi vào một write-ahead log (WAL) nối tiếp trước khi sửa dữ liệu thật, để khôi phục sau crash. Replication log ở đây là cùng ý tưởng đó, mở rộng ra ngoài một máy: leader phát chính dòng thay đổi tuần tự đó cho follower áp lại.

1. Analogy — một người ghi sổ cái, nhiều người chép lại

Một cửa hàng có một sổ cái gốc do quản lý giữ: mọi giao dịch chỉ được ghi vào sổ này. Nhiều nhân viên giữ bản chép để tra cứu nhanh cho khách. Mỗi khi quản lý ghi một dòng mới, họ đọc dòng đó cho các nhân viên chép theo — cùng một thứ tự. Khách hỏi gì thì hỏi nhân viên gần nhất (đọc từ bản chép); còn muốn ghi giao dịch mới thì phải qua quản lý (leader).

Nếu quản lý đợi mọi nhân viên chép xong mới nhận khách tiếp (đồng bộ) thì chậm, và một nhân viên đi vệ sinh là tắc cả hàng. Nếu quản lý cứ ghi rồi đọc cho ai nghe kịp thì nghe (bất đồng bộ) thì nhanh, nhưng nếu quản lý ngất xỉu ngay sau khi ghi một dòng chưa ai kịp chép — dòng đó mất.

Cửa hàngSingle-leader replication
Sổ cái gốc, chỉ quản lý ghiLeader — nhận mọi ghi
Bản chép của nhân viênFollower — phục vụ đọc
Đọc cho nhân viên chép theoReplication log phát cho follower
Đợi mọi người chép xongSync replication
Ghi rồi ai nghe kịp thì ngheAsync replication
Quản lý ngất → chọn người thayFailover
💡 Cách nhớ

Ghi qua leader, đọc từ bất kỳ bản. Sync = chờ (an toàn, chậm), async = không chờ (nhanh, có thể mất). Failover là lúc căng nhất.

2. Cơ chế — leader, follower, và replication log

Luồng chuẩn:

  1. Client gửi ghi tới leader; leader ghi vào bản cục bộ của nó.
  2. Leader gửi thay đổi đó vào replication log phát cho mọi follower.
  3. Mỗi follower áp thay đổi theo đúng thứ tự leader gửi (thứ tự là chìa khoá — sai thứ tự thì các bản phân kỳ).
  4. Client gửi đọc tới leader hoặc bất kỳ follower.
flowchart TB
  C["Client (ghi)"] --> L["Leader"]
  L -->|"replication log"| F1["Follower 1"]
  L -->|"replication log"| F2["Follower 2"]
  R1["Client (doc)"] --> F1
  R2["Client (doc)"] --> L

Vì mọi ghi đi qua một điểm và được tuần tự hoá thành một log có thứ tự, các follower — nếu áp đúng thứ tự — hội tụ về cùng trạng thái. Đây là điểm mạnh của single-leader: không có xung đột ghi (chỉ một nơi quyết thứ tự), khác hẳn multi-leader ở bài sau.

3. Dựng follower mới và xử lý node chết

Thêm follower mới: không thể chép lúc dữ liệu đang đổi liên tục. Cách chuẩn: chụp một snapshot trạng thái leader tại một điểm trong log, nạp snapshot vào follower mới, rồi cho nó áp tiếp các thay đổi từ điểm đó trở đi — bắt kịp leader.

Follower chết: dễ. Follower biết nó đã áp tới đâu trong log (giữ vị trí); sống lại thì xin phần log còn thiếu từ leader và catch-up. Không ai ngoài chính nó bị ảnh hưởng.

Leader chết: khó — phải failover (mục 5).

4. Sync hay async — đánh đổi ở đâu?

Câu hỏi trung tâm của single-leader: leader có chờ follower xác nhận đã nhận ghi trước khi báo "thành công" cho client không?

  • Đồng bộ hoàn toàn (fully synchronous): leader chờ mọi follower xác nhận trước khi báo thành công. Đảm bảo ghi tồn tại trên toàn bộ các bản, nhưng chỉ cần một follower chậm hoặc treo là ghi bị chặn — cả hệ đứng chờ. Vì vậy hiếm khi dùng nguyên bản trong thực tế.
  • Bất đồng bộ (async): leader báo thành công ngay, phát log sau. Nhanh, và một follower chậm không kéo cả hệ. Nhưng nếu leader chết trước khi kịp phát ghi vừa nhận, ghi đó mất vĩnh viễn — dù client đã được báo "thành công".
Ghi X toi leader:
  SYNC : leader ghi X -> gui X cho follower -> CHO follower "ok" -> bao client
         => X chac chan co tren >=2 ban. Nhung follower treo = ghi treo.
  ASYNC: leader ghi X -> bao client NGAY -> gui X cho follower sau
         => nhanh. Nhung leader chet truoc khi gui X => X mat.

Vì cả hai cực đều có nhược điểm, một dung hoà phổ biến là semi-synchronous: giữ đúng một follower đồng bộ (đảm bảo luôn có ít nhất hai bản của mỗi ghi), phần còn lại bất đồng bộ (không kéo chậm). Nếu follower đồng bộ treo, một follower bất đồng bộ khác được nâng lên làm đồng bộ.

5. Failover — chỗ nguy hiểm nhất

Khi leader chết, hệ phải bầu một follower lên làm leader mới, và định tuyến lại ghi. Nghe đơn giản, nhưng đây là nơi sự cố sản xuất hay xảy ra:

  • Mất ghi (async): follower được chọn có thể chưa nhận những ghi cuối của leader cũ. Nếu leader cũ sống lại, các ghi đó thường bị bỏ — dữ liệu client tưởng đã lưu biến mất.
  • Split-brain: hai node cùng tin mình là leader và cùng nhận ghi → hai dòng lịch sử phân kỳ, dữ liệu hỏng. Chống bằng cơ chế đảm bảo chỉ một leader (fencing, lease) — bạn sẽ gặp lại ở bài consensus.
  • Chọn timeout khó: timeout phát hiện leader chết mà quá ngắn thì failover nhầm lúc leader chỉ chậm; quá dài thì hệ chết lâu mới chuyển.
⚠️ Failover không phải nút bấm

Việc "chọn leader mới" nghe như một bước, nhưng phải trả lời đúng: leader thật sự chết hay chỉ chậm/mất mạng? (nếu chỉ mất mạng, nâng follower lên = split-brain). Đây chính là bài toán đồng thuận (consensus) — làm sao nhiều node thống nhất ai là leader — mà Module 6 sẽ mổ. Single-leader đẩy được sự đơn giản của "một nơi ghi" nhưng dồn hết độ khó vào khoảnh khắc failover.

6. Pitfall — coi async replication là "an toàn như đã lưu"

Pitfall — tin 'ghi thành công' khi replication bất đồng bộ

Sai lầm: app báo người dùng "đã lưu" ngay khi leader xác nhận, trong khi replication là async và chưa follower nào có bản sao.

❌ Client -> leader: ghi X -> leader "ok" -> app bao user "da luu"
   (async: chua follower nao co X)
   -> leader chet ngay sau -> failover sang follower thieu X
   -> X BIEN MAT du user thay "da luu"

Hệ quả: mất dữ liệu im lặng — nguy hiểm vì người dùng và app đều tin nó đã bền. Với dữ liệu quan trọng (thanh toán, đơn hàng), "leader ack" async là chưa đủ.

Hướng đúng: với ghi không được phép mất, dùng sync (hoặc semi-sync) để đảm bảo ghi có trên ít nhất hai bản trước khi báo thành công — chấp nhận chậm hơn. Hiểu rõ hệ của bạn đang async hay sync, và "durable" nghĩa là gì trong cấu hình đó. Đừng suy ra độ bền từ chữ "thành công" trên màn hình.

7. 📚 Deep Dive

📚 Deep Dive — Single-leader replication
  • Designing Data-Intensive Applications (Kleppmann) — Chương 5, mục "Leaders and Followers" — replication log, sync vs async, dựng follower mới (snapshot + catch-up), và các cạm bẫy failover (mất ghi, split-brain).
  • Các cách hiện thực replication log: statement-based (phát câu lệnh — rủi ro với hàm không tất định), WAL shipping (phát log vật lý), logical (row-based) log (phát thay đổi mức hàng — phổ biến, tách khỏi định dạng lưu trữ). DDIA Ch5 mục "Implementation of Replication Logs".

Ghi chú: DDIA Ch5 là nguồn agnostic chuẩn cho replication. Single-leader là mặc định của hầu hết RDBMS truyền thống — sức mạnh nằm ở "một nơi quyết thứ tự ghi", giá nằm ở failover.

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

  • Module 1 — Reliability: replication chính là hiện thực redundancy cho dữ liệu — nhưng chỉ tăng tin cậy khi các bản độc lập và failover đúng.
  • Bài 02 — Replication lag: async replication tạo khoảng trễ giữa leader và follower; bài sau đào sâu hệ quả nhất quán của khoảng trễ đó.
  • Bài 05 — Chọn mô hình replication: single-leader hợp workload nào — so trực tiếp với multi-leader và leaderless.
  • Module 6 — Consensus: bài toán "ai là leader" khi node có thể chỉ chậm chứ không chết — nền của failover an toàn.

9. Tóm tắt

  • Single-leader: mọi ghi qua một leader; leader phát replication log có thứ tự; follower áp cùng thứ tự → hội tụ, không xung đột ghi. Đọc từ bản nào cũng được.
  • Sync: chờ follower ack → không mất bản sao, nhưng chậm + treo nếu follower treo. Async: nhanh + bền với follower chậm, nhưng có thể mất ghi khi leader chết.
  • Thực tế thường semi-sync: một follower đồng bộ (đảm bảo ≥2 bản), phần còn lại bất đồng bộ.
  • Follower chết = catch-up dễ; leader chết = failover khó: mất ghi async, split-brain, timeout khó chọn.
  • Failover thực chất là bài toán đồng thuận ("leader chết thật hay chỉ chậm") → Module 6.
  • Anti-pattern: coi "leader ack" async là đã bền → mất dữ liệu im lặng. Dữ liệu quan trọng cần sync/semi-sync.

10. Tự kiểm tra

Tự kiểm tra
Q1
Trong single-leader, vì sao đảm bảo 'không có xung đột ghi' — điều mà multi-leader không có? Điểm nào trong kiến trúc tạo ra tính chất đó?

mọi ghi đi qua một điểm duy nhất (leader), và leader tuần tự hoá chúng thành một replication log có thứ tự. Chỉ một nơi quyết định thứ tự các thay đổi, nên không có chuyện hai ghi mâu thuẫn được chấp nhận song song ở hai nơi.

Các follower chỉ *áp lại* log theo đúng thứ tự đó nên hội tụ về cùng trạng thái. Multi-leader (bài 03) cho ghi ở nhiều nơi cùng lúc → hai leader có thể chấp nhận hai ghi mâu thuẫn trước khi biết về nhau → sinh xung đột phải giải quyết. Single-leader đổi khả năng ghi phân tán lấy sự đơn giản "một nơi quyết thứ tự".

Q2
So sánh sync và async replication về mất dữ liệu và hiệu năng. Vì sao semi-synchronous là dung hoà phổ biến?
  • Sync: leader chờ follower ack → ghi chắc chắn có trên ≥2 bản, không mất khi leader chết; nhưng follower treo thì ghi bị chặn (hại availability + độ trễ).
  • Async: leader ack ngay → nhanh, follower chậm không kéo cả hệ; nhưng leader chết trước khi phát log thì ghi vừa nhận mất dù đã báo "thành công".

Semi-sync giữ một follower đồng bộ (đảm bảo mọi ghi có trên ít nhất hai bản → không mất khi một node chết) và phần còn lại bất đồng bộ (không để nhiều follower chậm kéo tắc). Nó lấy phần lớn độ an toàn của sync với phần lớn tốc độ của async.

Q3
Follower chết và leader chết — vì sao xử lý hai trường hợp khác nhau về độ khó?

Follower chết dễ: nó biết đã áp log tới vị trí nào; sống lại thì xin phần log còn thiếu từ leader và catch-up. Không ai ngoài chính nó bị ảnh hưởng, không cần quyết định gì ở mức hệ.

Leader chết khó vì cần failover: phải (1) xác định leader thật sự chết hay chỉ chậm/mất mạng, (2) chọn follower cập nhật nhất lên thay, (3) định tuyến lại ghi, (4) xử lý ghi async mà leader cũ chưa kịp phát. Mỗi bước có cạm bẫy (split-brain, mất ghi) — và bước (1) chính là bài toán đồng thuận.

Q4
Giải thích cơ chế mất ghi khi failover với async replication. Vì sao 'leader đã báo thành công' không đảm bảo dữ liệu còn sau failover?

Với async, leader báo "thành công" cho client ngay khi ghi vào bản của nó, rồi mới phát log cho follower. Nếu leader chết trong khoảng đó, ghi vừa nhận chưa tới follower nào.

Khi failover, một follower (thiếu ghi đó) lên làm leader mới. Ghi cũ chỉ tồn tại trên leader cũ đã chết; lúc nó sống lại, để tránh phân kỳ, ghi đó thường bị bỏ. Kết quả: dữ liệu client tưởng đã lưu (đã thấy "thành công") biến mất. Vì vậy độ bền phải suy từ *cấu hình replication* (sync/async), không từ chữ "thành công".

Q5
Split-brain là gì trong failover, vì sao nó nguy hiểm, và vì sao chống nó dẫn thẳng tới bài toán consensus?

Split-brain: hai node cùng tin mình là leader và cùng nhận ghi — thường xảy ra khi leader cũ chỉ *mất mạng* chứ chưa chết, còn hệ đã nâng một follower lên. Nguy hiểm vì hai dòng lịch sử ghi phân kỳ: cùng một khoá bị hai giá trị khác nhau, dữ liệu hỏng và rất khó hoà lại.

Chống nó đòi hỏi *đảm bảo chỉ một leader tại một thời điểm*, tức nhiều node phải thống nhất ai là leader ngay cả khi mạng chia cắt — đó đúng là định nghĩa của consensus (đồng thuận). Fencing token/lease là công cụ, nhưng gốc vấn đề là consensus, học ở Module 6.

Bài tiếp theo: Replication lag — sống chung với độ trễ đồng bộ

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