Hệ thống Dữ liệu Phân tán/Replication lag — sống chung với độ trễ giữa các bản
9/38
Bài 9 / 38~12 phútNhân bản (replication)Miễn phí lượt xem

Replication lag — sống chung với độ trễ giữa các bản

Async replication làm follower trễ hơn leader một nhịp. Eventual consistency và ba đảm bảo sống chung với lag: read-your-writes, monotonic reads, consistent prefix reads.

TL;DR: Với async replication, follower luôn trễ hơn leader một nhịp — khoảng trễ đó là replication lag. Đọc từ một follower đang lag có thể trả về dữ liệu cũ (stale). Nếu lag luôn cuối cùng bắt kịp (không có ghi mới), các bản hội tụ — gọi là eventual consistency (nhất quán rốt cuộc). Vấn đề là "rốt cuộc" có thể là vài giây, và trong khoảng đó người dùng gặp ba loại nghịch lý cần đảm bảo riêng để chặn: read-your-writes (đọc phải thấy chính thứ mình vừa ghi), monotonic reads (không thấy thời gian đi lùi), và consistent prefix reads (không thấy hệ quả trước nguyên nhân). Ba đảm bảo này không xoá lag — chúng là cách sống chung với nó mà không làm người dùng bối rối.

Bài trước dựng single-leader và chỉ ra async replication nhanh nhưng tạo khoảng trễ. Bài này đào đúng khoảng trễ đó: nó gây ra những nghịch lý gì cho người dùng, và ta chặn từng nghịch lý thế nào. Đây là kiến thức bạn dùng lại ở mọi hệ có bản sao async — kể cả leaderless ở bài sau.

1. Analogy — tin nhắn nhóm tới không đều

Trong một nhóm chat, tin nhắn tới điện thoại mỗi người trễ khác nhau tuỳ mạng. Bạn gửi "Đặt bàn 7h nhé", rồi vài giây sau mở một thiết bị khác chưa nhận kịp — và không thấy tin mình vừa gửi. Khó chịu: bạn biết mình đã gửi mà màn hình như chưa có (thiếu read-your-writes).

Tệ hơn: bạn F5 trang hai lần, lần đầu (trúng bản mới) thấy tin của An, lần sau (trúng bản cũ hơn) tin An biến mất — như thời gian đi lùi (thiếu monotonic reads). Hoặc bạn thấy câu trả lời "Ừ đồng ý" hiện trước câu hỏi "Đi ăn không?" — hệ quả trước nguyên nhân (thiếu consistent prefix reads).

Nhóm chatReplication
Tin tới mỗi máy trễ khác nhauReplication lag khác nhau giữa follower
Không thấy tin mình vừa gửiVi phạm read-your-writes
F5 lại, tin cũ biến mấtVi phạm monotonic reads
Thấy trả lời trước câu hỏiVi phạm consistent prefix reads
Cuối cùng mọi máy đồng bộEventual consistency
💡 Cách nhớ

Lag không tránh được với async. Ba đảm bảo là ba lời hứa nhỏ giữ người dùng khỏi bối rối: thấy chính mình (read-your-writes), không lùi (monotonic), không đảo nhân quả (consistent prefix).

2. Replication lag là gì, và vì sao "eventual" là đảm bảo yếu?

Với async, leader không chờ follower — nên tại một thời điểm, follower đã áp tới một điểm cũ hơn trong log so với leader.

flowchart LR
  L["Leader<br/>da ghi X=5"] -->|"replicate (tre)"| F["Follower<br/>con X=4 (dang lag)"]
  C["Client doc tu follower"] --> F
  F --> S["Tra ve X=4 (STALE)<br/>-> se dung khi follower bat kip"]

Khoảng cách đó là replication lag, đo bằng thời gian hoặc số thao tác. Lag bình thường vài mili-giây, nhưng khi tải cao hoặc mạng trục trặc có thể vọt lên vài giây, đôi khi phút.

Đọc từ follower đang lag trả về stale read (dữ liệu cũ). Nếu hệ ngừng nhận ghi, mọi follower cuối cùng áp hết log và hội tụ về cùng trạng thái — tính chất này gọi là eventual consistency. Nhưng chữ "eventual" (rốt cuộc) cố tình mơ hồ: không có đảm bảo về việc "rốt cuộc" là bao lâu. Với hệ tải liên tục, follower có thể luôn trễ. Eventual consistency là một đảm bảo yếu — đủ cho nhiều trường hợp, nhưng để giao diện không làm người dùng bối rối, ta cần thêm ba đảm bảo dưới đây.

3. Read-your-writes — thấy chính mình vừa ghi

Thử đoán trước

Người dùng cập nhật ảnh đại diện (ghi qua leader), rồi trang tự tải lại và đọc từ một follower đang lag. Bạn đoán họ thấy ảnh mới hay ảnh cũ? Và đảm bảo nào cần để họ luôn thấy ảnh mới của chính mình, dù follower còn lag với ghi của người khác?

Lần theo cơ chế: follower vừa bị đọc còn đang lag — nó chưa áp ghi đổi ảnh (ghi mới chỉ vào tới leader), nên trả về ảnh . Người dùng thấy như thao tác lưu của mình vừa bốc hơi, dù dữ liệu thật vẫn nằm an toàn ở leader. Đảm bảo chặn đúng cảnh này là read-your-writes (đọc-thấy-ghi-của-mình): sau khi một người dùng ghi gì đó, mọi lần họ đọc lại sẽ thấy đúng thứ đó — dù có thể chưa thấy ghi mới của người khác. Nó chỉ nói về dữ liệu của chính người ghi, không phải toàn hệ — nên nó lo được ảnh của chính bạn kể cả khi follower vẫn lag với ghi của người khác.

Cách hiện thực (agnostic): với dữ liệu mà người dùng có thể tự sửa, đọc từ leader (hoặc từ follower đủ mới) trong một khoảng sau khi họ ghi; hoặc theo dõi thời điểm ghi gần nhất của người dùng và chỉ đọc từ follower đã áp qua điểm đó. Không có read-your-writes, người dùng "lưu" xong lại thấy như chưa lưu — cảm giác hệ mất dữ liệu.

4. Monotonic reads — không đi lùi thời gian

Monotonic reads đảm bảo: nếu một người dùng đọc nhiều lần, mỗi lần sau không lùi về trạng thái cũ hơn lần trước. Không cấm thấy dữ liệu cũ — chỉ cấm thấy mới rồi lại thấy cũ hơn.

Vi phạm xảy ra khi hai lần đọc trúng hai follower có lag khác nhau: lần đầu trúng follower gần bắt kịp (thấy tin mới), lần sau trúng follower lag nặng (tin mới biến mất) — như du hành ngược thời gian. Cách chặn đơn giản: cho mỗi người dùng luôn đọc từ cùng một follower (chọn theo hash user-id), để họ chỉ tiến chứ không lùi. Monotonic reads yếu hơn "luôn thấy mới nhất" nhưng đủ để trải nghiệm không phi lý.

5. Consistent prefix reads — không đảo nhân quả

Consistent prefix reads đảm bảo: nếu chuỗi ghi có quan hệ nhân quả (A trước B), thì ai đọc cũng thấy A trước B, không bao giờ thấy B mà chưa thấy A. Vi phạm cho ra cảnh "thấy câu trả lời trước câu hỏi".

Thử đoán trước

Một diễn đàn lưu câu hỏicâu trả lời ở hai phần dữ liệu (partition) khác nhau, mỗi phần replicate độc lập với lag riêng. An đăng câu hỏi (vào partition P1); thấy nó, Bình đăng câu trả lời (vào partition P2). Một người đọc thứ ba tình cờ trúng lúc P2 đã bắt kịp nhưng P1 còn lag. Thử đoán họ thấy gì trên màn hình, và đảm bảo nào bị vi phạm — trước khi đọc tiếp.

Lần theo cơ chế: bản sao của P2 (chứa câu trả lời) đã tới người đọc, còn bản sao của P1 (chứa câu hỏi) vẫn đang lag — nên họ thấy câu trả lời hiện ra trước khi câu hỏi xuất hiện, đúng cảnh đảo nhân quả mà consistent prefix reads cấm. Nghịch lý này đặc biệt hay xuất hiện khi dữ liệu bị phân mảnh (partition — Module 3): các partition replicate độc lập với lag khác nhau, nên thứ tự nhân quả xuyên partition có thể bị đảo khi đọc. Chặn nó cần đảm bảo các ghi có quan hệ nhân quả đi qua đường giữ được thứ tự, hoặc theo dõi quan hệ nhân quả tường minh — chính là chỗ vector clock (bạn học ở track thuật toán) vào cuộc, và ta sẽ dùng lại nó ở bài multi-leader.

Nhớ lại Quorum (R + W > N)

Ba đảm bảo trên là cách sống chung với lag ở single-leader. Ở kiến trúc leaderless (bài 04), người ta còn một cách khác để giảm stale read: đọc/ghi qua quorum — yêu cầu đủ số bản đồng ý. Bạn đã học cơ chế R+W>N đó; bài 04 sẽ áp nó vào replication.

6. Pitfall — mặc định eventual consistency cho dữ liệu người dùng tự sửa

Pitfall — để user tự sửa dữ liệu rồi đọc từ follower lag

Sai lầm phổ biến: mọi đọc đều đẩy sang follower để giảm tải leader — kể cả đọc ngay sau khi người dùng vừa ghi.

❌ User sua ho so -> ghi qua leader -> trang reload -> doc tu follower dang lag
   -> thay ho so CU -> user tuong sua that bai -> sua lai -> ghi trung/loan

❌ Feed doc luan phien nhieu follower lag khac nhau
   -> F5 thay post moi roi lai mat -> tuong bug

Hệ quả: người dùng mất niềm tin ("lưu không ăn"), thao tác lặp gây dữ liệu bẩn, và bug "lúc có lúc không" cực khó tái hiện (phụ thuộc trúng follower nào).

Hướng đúng: phân loại đọc. Đọc dữ liệu người dùng vừa có thể sửa → cần read-your-writes (đọc từ leader/follower đủ mới). Đọc lặp trong một phiên → monotonic reads (ghim một follower). Dữ liệu có nhân quả xuyên phần → consistent prefix. Còn lại (dữ liệu ít đổi, không của riêng ai) mới để eventual consistency thoải mái. Đừng áp một mức nhất quán cho mọi loại đọc.

7. 📚 Deep Dive

📚 Deep Dive — Replication lag
  • Designing Data-Intensive Applications (Kleppmann) — Chương 5, mục "Problems with Replication Lag" — eventual consistency, và ba đảm bảo read-your-writes (read-after-write), monotonic reads, consistent prefix reads, kèm cách hiện thực từng cái.
  • Thuật ngữ eventual consistency phổ biến hoá từ hệ Dynamo (Amazon); ý "rốt cuộc hội tụ nếu ngừng ghi" là đảm bảo liveness yếu, không nói gì về thời gian — nền cho các đảm bảo mạnh hơn ở Module 6 (linearizability).

Ghi chú: DDIA Ch5 nhấn: ba đảm bảo này là session-level (theo người dùng/phiên), rẻ hơn nhiều so với nhất quán mạnh toàn cục — chọn đúng mức cho từng loại đọc là kỹ năng cốt lõi.

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

  • Bài 01 — Single-leader: async replication (bài 01) chính là nguồn của lag; bài này xử lý hệ quả của nó.
  • Bài 03 — Multi-leader: nhiều leader làm lag và thứ tự phức tạp hơn — consistent prefix + vector clock trở thành trung tâm.
  • Bài 04 — Leaderless: quorum R+W>N là một cách giảm stale read thay vì chỉ sống chung với nó.
  • Module 6 — Consistency & consensus: các đảm bảo yếu ở đây đối lập với linearizability (nhất quán mạnh) — hiểu cái yếu để thấy giá của cái mạnh.

9. Tóm tắt

  • Replication lag: follower async trễ hơn leader; đọc từ follower đang lag → stale read (dữ liệu cũ).
  • Eventual consistency: ngừng ghi thì hội tụ, nhưng "rốt cuộc" không có mốc thời gian — đảm bảo yếu.
  • Read-your-writes: người dùng luôn thấy chính thứ mình vừa ghi (đọc từ leader/follower đủ mới).
  • Monotonic reads: đọc lần sau không lùi trước lần trước (ghim một follower theo user).
  • Consistent prefix reads: không thấy hệ quả trước nguyên nhân; hay vỡ khi dữ liệu phân mảnh — cần theo dõi nhân quả (vector clock).
  • Anti-pattern: đẩy mọi đọc sang follower lag kể cả ngay sau khi user ghi → "lưu không ăn", dữ liệu loạn. Phân loại đọc theo mức nhất quán cần.

10. Tự kiểm tra

Tự kiểm tra
Q1
Replication lag là gì, và vì sao 'eventual consistency' là một đảm bảo yếu dù nghe có vẻ ổn ('cuối cùng cũng đúng')?

Replication lag là khoảng follower async trễ hơn leader (đo bằng thời gian hoặc số thao tác); đọc từ follower đang lag trả về dữ liệu cũ. Eventual consistency nói: nếu ngừng ghi, mọi bản cuối cùng hội tụ.

Yếu vì chữ "eventual" (rốt cuộc) không kèm mốc thời gian: có thể vài mili-giây, nhưng khi tải cao/mạng lỗi thì vài giây tới phút, và với hệ ghi liên tục follower có thể luôn trễ. Nó không hứa gì về "bao lâu" hay về trải nghiệm trong lúc chờ — nên cần thêm các đảm bảo session-level để giao diện không phi lý.

Q2
Phân biệt read-your-writes và monotonic reads. Cho tình huống mỗi cái chặn được.

Read-your-writes: sau khi bạn ghi, mọi lần bạn đọc lại thấy đúng thứ đó (nói về dữ liệu của chính người ghi). Chặn cảnh: sửa hồ sơ xong reload thấy hồ sơ cũ, tưởng lưu thất bại.

Monotonic reads: các lần đọc của một người không lùi về trạng thái cũ hơn lần trước (nói về thứ tự thời gian giữa các lần đọc). Chặn cảnh: F5 lần đầu thấy bình luận mới của người khác, F5 lần sau (trúng follower lag hơn) bình luận đó biến mất — như đi lùi thời gian.

Khác nhau: read-your-writes về ghi của chính mình; monotonic reads về không lùi giữa các lần đọc (kể cả dữ liệu người khác).

Q3
Cách hiện thực read-your-writes và monotonic reads (agnostic) khác nhau thế nào, và vì sao cả hai không cần nhất quán mạnh toàn cục?

Read-your-writes: đọc dữ liệu mà người dùng có thể tự sửa từ leader (hoặc follower đã áp qua thời điểm ghi gần nhất của họ) trong một khoảng sau khi ghi. Monotonic reads: cho mỗi người dùng luôn đọc từ cùng một follower (chọn theo hash user-id) để họ chỉ tiến, không lùi.

Cả hai là đảm bảo theo phiên/người dùng (session-level), không phải toàn hệ: chỉ cần lo trải nghiệm nhất quán của một người, không cần mọi người thấy cùng trạng thái tức thì. Vì vậy chúng rẻ hơn nhiều so với nhất quán mạnh (linearizability) mà vẫn đủ chống các nghịch lý người dùng thấy.

Q4
Consistent prefix reads chống nghịch lý gì, và vì sao nó đặc biệt hay vỡ khi dữ liệu bị phân mảnh (partition)?

Nó chống cảnh thấy hệ quả trước nguyên nhân: nếu A xảy ra trước B theo nhân quả (câu hỏi trước câu trả lời), không ai được thấy B mà chưa thấy A. Đảm bảo là "ai đọc cũng thấy đúng tiền tố của chuỗi ghi có thứ tự".

Hay vỡ khi phân mảnh vì mỗi partition replicate độc lập với lag khác nhau; hai ghi có quan hệ nhân quả nằm ở hai partition khác nhau có thể tới người đọc theo thứ tự đảo (partition chứa B nhanh hơn partition chứa A). Chặn nó cần giữ thứ tự các ghi nhân quả hoặc theo dõi nhân quả tường minh (vector clock) — không tự nhiên có khi các partition độc lập.

Q5
Vì sao 'đẩy mọi đọc sang follower để giảm tải leader' là chính sách nguy hiểm? Nên thay bằng cách nghĩ nào?

Nguy hiểm vì nó áp một mức nhất quán (eventual) cho mọi loại đọc, kể cả đọc ngay sau khi người dùng vừa ghi. Hệ quả: người dùng sửa xong đọc thấy dữ liệu cũ (vi phạm read-your-writes) → tưởng lưu hỏng → sửa lại → dữ liệu loạn; và bug "lúc có lúc không" tuỳ trúng follower nào, cực khó tái hiện.

Thay bằng: phân loại đọc theo mức nhất quán cần. Dữ liệu người dùng vừa có thể sửa → read-your-writes (leader/follower đủ mới). Đọc lặp trong phiên → monotonic (ghim follower). Nhân quả xuyên phần → consistent prefix. Chỉ dữ liệu ít đổi, không của riêng ai mới để follower lag thoải mái.

Bài tiếp theo: Multi-leader — ghi ở nhiều nơi và bài toán xung đột

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