Replication là giữ nhiều bản copy của cùng một dữ liệu trên nhiều máy. Mỗi máy giữ một bản copy gọi là một replica. Trang này nói phần chung cho mọi database; cách MySQL làm cụ thể (binlog, GTID, semi-sync, Group Replication) nằm ở MySQL chương 9.
Không mất dữ liệu, không ngừng phục vụ. Một máy hỏng ổ cứng hay mất điện thì vẫn còn bản copy ở máy khác, và hệ thống chuyển sang dùng máy đó.
Tăng throughput đọc. Có ba replica thì có thể chia lượng đọc ra ba máy.
Giảm latency theo vùng. Đặt replica ở gần người dùng (Singapore cho người dùng châu Á, Frankfurt cho châu Âu), nên mỗi lần đọc không phải đi nửa vòng trái đất.
Replication không tăng được throughput ghi tổng. Mỗi lệnh ghi cuối cùng vẫn phải được áp dụng trên mọi replica, nên mỗi máy vẫn phải làm toàn bộ lượng ghi. Muốn chia tải ghi thì cần partitioning: mỗi máy chỉ giữ một phần dữ liệu. Thực tế hai kỹ thuật đi cùng nhau, mỗi partition có vài replica.
Khi một replica nhận lệnh ghi, nó phải gửi lệnh đó sang các replica khác. Câu hỏi là client có phải chờ các replica kia nhận xong không.
Sync
Async
Client nhận OK khi
Mọi replica (hoặc các replica được chọn) đã nhận lệnh ghi
Replica đầu tiên đã ghi xong
Latency ghi
Cộng thêm thời gian gửi sang replica chậm nhất
Thấp
Một replica sập
Không ghi được nữa nếu chờ mọi replica
Không ảnh hưởng
Đọc từ replica khác
Thấy ngay dữ liệu mới
Có thể thấy dữ liệu cũ
Mất dữ liệu khi máy nhận ghi sập
Không
Có thể mất các lệnh ghi chưa kịp gửi đi
Sync tới mọi replica gần như không ai dùng, vì một replica sập hay chậm là cả hệ thống không ghi được. Cách hay gặp hơn là chờ ít nhất một replica (MySQL gọi là semi-sync), hoặc chờ đa số replica (consensus, xem Đồng thuận). PostgreSQL và MySQL mặc định đều dùng async (MySQL chương 9).
Với async replication, nếu ngừng ghi một lúc thì các replica rồi sẽ giống nhau, nhưng không có cam kết bao lâu. Tính chất này gọi là eventual consistency.
Follower nhớ vị trí cuối cùng mình đã áp dụng trong replication log (MySQL dùng GTID). Ví dụ follower áp dụng tới lệnh ghi thứ 50 thì mất điện. Lúc nó chạy lại, leader đã ghi tới 70, nên follower xin leader gửi tiếp từ 51 tới 70 rồi đuổi kịp. Đây là trường hợp dễ.
Phải chọn một follower lên làm leader mới, rồi chuyển client và các follower khác sang leader mới. Bước này gọi là failover, và có ba chỗ dễ sai:
Khó biết leader sập thật hay chỉ chậm. Mạng không cam kết thời gian gửi tin, nên chỉ có cách đoán bằng timeout. Timeout ngắn thì failover nhầm khi leader chỉ đang bận; timeout dài thì hệ thống không ghi được lâu hơn.
Mất lệnh ghi. Với async replication, leader cũ có thể đã nhận lệnh ghi 71 tới 80 mà follower mới chỉ có tới 70. Khi follower đó lên làm leader, các lệnh ghi 71 tới 80 biến mất dù client đã nhận OK.
Split brain. Leader cũ chạy lại mà vẫn tưởng mình là leader, nên có hai leader cùng nhận ghi. Phải chặn leader cũ, ví dụ bằng fencing token (Distributed lock) hoặc cắt hẳn nó khỏi mạng.
Muốn mọi node cùng đồng ý ai là leader thì cần consensus. Vì vậy các công cụ failover tự động thường dựa vào một hệ consensus: Patroni (PostgreSQL) dùng etcd, Consul hoặc ZooKeeper; MySQL Group Replication tự chạy một biến thể của Paxos.
Ưu điểm: đơn giản, không có write conflict, thêm follower là tăng được throughput đọc. Nhược điểm: throughput ghi giới hạn ở một máy; failover khó làm đúng.
Với async replication, follower luôn chậm hơn leader một chút. Khoảng chậm này gọi là replication lag, bình thường vài mili giây nhưng có lúc lên tới vài phút (nguyên nhân trên MySQL). Không bỏ được lag hoàn toàn, nhưng có ba kiểu đọc sai làm người dùng khó chịu nhất, và cả ba đều có cách chặn.
Kiểu đọc sai
Ví dụ
Cách chặn
Không đọc được thứ mình vừa ghi (cần read-your-writes)
Đổi trạng thái thành “đang hẹn hò”, tải lại trang vẫn thấy “độc thân” vì đọc từ follower chưa nhận lệnh ghi
Đọc từ leader trong vài giây sau khi ghi; hoặc nhớ vị trí log (GTID) của lệnh ghi và chỉ đọc follower đã áp dụng tới đó
Đọc lùi về quá khứ (cần monotonic reads)
Bấm làm mới lần đầu thấy 3 tin nhắn, lần sau thấy 2, vì lần sau đọc phải follower chậm hơn
Mỗi người dùng luôn đọc từ cùng một follower, ví dụ chọn theo hash(user_id) % số follower
Thấy câu trả lời trước câu hỏi (cần consistent prefix reads)
Thấy “Ừ, đi” trước “Trưa nay đi ăn không?” vì hai tin nằm ở hai partition, partition chứa câu trả lời replicate nhanh hơn
Ghi các dữ liệu phụ thuộc nhau (câu hỏi và câu trả lời) vào cùng một partition, để chúng đi qua cùng một replication log theo đúng thứ tự
Nhiều leader cùng nhận ghi. Mỗi leader gửi lệnh ghi của mình sang các leader khác, thường bằng async replication.
Khi nào dùng:
Nhiều data center. Mỗi data center có một leader, nên người dùng ghi vào data center gần mình, và một data center sập thì data center khác vẫn nhận ghi.
Ứng dụng chạy offline. Ứng dụng ghi chú hay lịch trên điện thoại cho sửa khi mất mạng; mỗi thiết bị thực chất là một leader, có mạng lại thì đồng bộ.
Sửa chung tài liệu theo thời gian thực: mỗi người sửa trên bản của mình rồi gộp lại.
Trong một data center thì gần như không đáng dùng multi leader: lợi ích nhỏ mà phải xử lý write conflict.
Mọi leader gửi về một node trung tâm, node này phát lại cho các node khác
Node trung tâm sập là tắc
All-to-all
Mọi leader gửi cho mọi leader
Lệnh ghi có thể đến sai thứ tự: một node nhận lệnh B (sửa dòng X) trước lệnh A (tạo dòng X)
Để một lệnh ghi không chạy vòng mãi giữa các leader, mỗi lệnh ghi mang danh sách các node đã nhận nó; node nào thấy tên mình trong danh sách thì không gửi tiếp.
Hai người cùng sửa một bản ghi trên hai leader khác nhau, mà không ai biết lệnh ghi của người kia. Hai lệnh ghi như vậy gọi là concurrent, dù thời điểm có thể lệch nhau vài giây. Khi hai leader gửi lệnh ghi cho nhau, phải chọn giá trị cuối cùng. Có bốn cách:
Tránh conflict. Mọi lệnh ghi cho cùng một bản ghi đi vào cùng một leader, ví dụ mọi thay đổi trên tài khoản của một user đi vào leader ở vùng của user đó. Đơn giản nhất, nhưng khi leader đó sập hay user đổi vùng thì vẫn có conflict.
Last write wins (LWW). Lệnh ghi có timestamp lớn hơn thắng. Dễ làm nhưng mất dữ liệu, vì lệnh ghi thua bị bỏ đi mà không ai biết. Timestamp lại không đáng tin:
Nếu dùng giờ của client thì client sửa được. Một người chỉnh giờ điện thoại sang năm 2100 là lệnh ghi của họ luôn thắng.
Nếu dùng giờ của server thì đồng hồ các máy lệch nhau (clock skew), vì thạch anh trong mỗi máy chạy nhanh chậm khác nhau. NTP định kỳ chỉnh lại giờ, nhưng một máy đang chạy nhanh bị NTP kéo lùi 1 giây thì lệnh ghi sau lại mang timestamp nhỏ hơn lệnh ghi trước, và lệnh ghi sau thua.
Giữ cả hai giá trị (siblings) rồi để ứng dụng hoặc người dùng chọn. Ví dụ hai thành viên cùng đổi tên nhóm chat thành “Team Backend” và “Team API” trên hai leader. Database giữ cả hai tên, lần đọc sau ứng dụng hỏi người dùng chọn tên nào rồi ghi lại một giá trị. Muốn biết hai lệnh ghi có concurrent hay không thì dùng version vector, ở mục dưới.
Mỗi replica giữ một vector, mỗi phần tử là số lệnh ghi nó từng nhận từ một replica. Ví dụ có hai leader L1, L2: vector [3, 5] nghĩa là đã nhận 3 lệnh ghi từ L1 và 5 lệnh ghi từ L2.
So hai vector A và B:
Mọi phần tử của A lớn hơn hoặc bằng phần tử tương ứng của B: A xảy ra sau B, A thay thế B được.
Có phần tử A lớn hơn và có phần tử B lớn hơn, ví dụ [2, 1] và [1, 2]: hai lệnh ghi concurrent, không ai biết người kia, nên phải xử lý conflict.
Ví dụ: [2, 1, 2] so với [1, 1, 1] thì mọi phần tử đều lớn hơn hoặc bằng, nên replica giữ [2, 1, 2] đã có đủ mọi thứ mà [1, 1, 1] mang tới và bỏ qua nó được. Còn [5, 2] so với [4, 3] thì bên đầu hơn ở L1 nhưng thua ở L2, nên đây là hai lệnh ghi concurrent.
Ví dụ counter lượt thích ở hai leader. L1 nhận 3 lượt thích, L2 nhận 5 lượt. Hai leader gửi giá trị cho nhau:
Cách lưu
L1
L2
Sau khi gộp
Đúng không
Một con số, gộp bằng max
3
5
5
Sai, mất 3 lượt của L1
Một con số, gộp bằng tổng
3
5
8, nhưng lần gửi sau lại cộng dồn thành 16
Sai
Vector, gộp bằng max từng ô
[3, 0]
[0, 5]
[3, 5], tổng 8
Đúng
Sau đó L1 nhận thêm 1 lượt thành [4, 5], tổng 9, rồi gửi cho L2. L2 gộp [4, 5] với [3, 5] bằng max từng ô, ra [4, 5], tổng 9. Không có chuyện cộng 9 với 8 thành 17, vì vector cho biết hai bên đã trùng nhau ở phần [3, 5]. Đây chính là G-Counter ở mục CRDT bên dưới.
Riak dùng version vector rồi trả về mọi sibling cho ứng dụng tự gộp. Ứng dụng giỏ hàng có thể gộp bằng cách lấy hợp các món hàng.
CRDT (conflict-free replicated data type) là các kiểu dữ liệu được thiết kế sao cho các replica luôn gộp được với nhau mà không có conflict, và cuối cùng ra cùng một kết quả.
Kiểu
Cách lưu
Gộp
G-Counter (counter chỉ tăng)
Mỗi replica một ô đếm số lần tăng của chính nó
Lấy max từng ô; giá trị là tổng các ô
PN-Counter (tăng và giảm)
Hai G-Counter: một cho tăng, một cho giảm
Gộp từng G-Counter; giá trị là tổng tăng trừ tổng giảm
2P-Set
Một tập phần tử đã thêm, một tập phần tử đã xoá
Hợp từng tập. Đã xoá thì không thêm lại được
OR-Set
Mỗi lần thêm gắn một tag ngẫu nhiên; xoá là xoá đúng các tag mình đã thấy
Thêm lại được sau khi xoá, và lệnh thêm concurrent với lệnh xoá sẽ thắng
Sequence CRDT
Danh sách có thứ tự, mỗi phần tử có định danh
Dùng cho sửa chung văn bản (thư viện Yjs, Automerge)
Ví dụ với danh sách đi chợ dùng chung. Một người thêm “trứng, sữa” trên L1, người kia thêm “bánh mì, cà phê” trên L2. Gộp hai set ra cả bốn món, ở replica nào cũng vậy.
Với 2P-Set, nếu ai đó xoá “sữa” thì “sữa” nằm mãi trong tập đã xoá, nên lần sau thêm “sữa” lại cũng không hiện.
Với OR-Set, lần thêm đầu là sữa#a1, xoá là xoá đúng sữa#a1. Lần thêm sau là sữa#b7, một tag mới chưa bị xoá, nên “sữa” hiện lại.
Có hai cách gửi CRDT giữa các replica:
Gửi thao tác (operation-based), ví dụ “tăng ô của L1 thêm 1”. Gọn, nhưng mỗi thao tác phải đến đúng một lần và đúng thứ tự nhân quả, nên cần hệ thống gửi tin tin cậy. Ví dụ L1 thêm “trứng” rồi xoá “trứng”. Nếu thao tác “thêm” bị mất trên đường tới L2 thì L2 nhận lệnh xoá một món không có. Nếu thao tác “thêm” đến hai lần thì có thể thành hai quả trứng.
Gửi toàn bộ trạng thái (state-based) rồi gộp. Hàm gộp phải giao hoán, kết hợp và idempotent (nhận lại cùng một trạng thái nhiều lần cũng không sai), nên tin đến trùng hay sai thứ tự đều không sao. Vì vậy nó hợp với gossip protocol: mỗi node gửi ngẫu nhiên cho vài node khác, lan dần ra cả cluster. Đổi lại, trạng thái lớn thì gửi tốn băng thông.
Redis Enterprise dùng CRDT cho chế độ Active-Active (nhiều vùng cùng nhận ghi). Riak có sẵn counter, set và map dạng CRDT.
Không có leader. Client gửi lệnh ghi tới nhiều replica cùng lúc, và đọc cũng từ nhiều replica cùng lúc. Mô hình này xuất phát từ bài báo Dynamo của Amazon (2007), và được Cassandra, ScyllaDB, Riak dùng. DynamoDB thì không: mỗi partition của DynamoDB có một leader bầu bằng Multi-Paxos, và chỉ leader nhận ghi.
Mỗi giá trị có một version. Khi đọc từ nhiều replica, client lấy giá trị có version lớn nhất. Replica nào trượt lệnh ghi (đang sập, mạng chập chờn) sẽ đuổi kịp bằng hai cơ chế:
Read repair. Lúc đọc, client thấy một replica trả version cũ thì ghi giá trị mới vào replica đó. Cách này chỉ sửa được dữ liệu có người đọc.
Anti-entropy. Một tiến trình chạy nền so dữ liệu giữa các replica rồi gửi phần khác nhau. Để không phải so từng dòng, mỗi replica dựng một Merkle tree: lá là hash của từng khoảng dữ liệu, mỗi node cha là hash của các node con. Hai replica so hash từ gốc xuống, nhánh nào hash bằng nhau thì bỏ qua, nên chỉ phải đi vào đúng nhánh khác nhau. Cassandra làm việc này khi chạy nodetool repair.
Anti-entropy không gửi lại cả replication log như single leader, vì hai replica thường đã có phần lớn lệnh ghi giống nhau. Ví dụ replica 1 đã nhận các lệnh ghi a, c, e, f, g, còn replica 2 có b, c, d, f, g. Replica 1 chỉ cần gửi a và e, replica 2 chỉ cần gửi b và d. Merkle tree giúp tìm ra đúng các phần thiếu đó mà không phải duyệt cả bảng.
Thử: Merkle tree tìm dòng khác nhau giữa hai replica
Bấm để sửa dòng ở replica B:
1hash phải so
0dòng cần gửi
8dòng nếu so từng dòng
hash khác nhau: đi xuống hai node conhash bằng nhau: bỏ qua cả nhánh bên dướikhông phải so
Mỗi lá là hash của một dòng, mỗi node cha là hash của hai node con. Hash ở đây chỉ có 3 chữ số cho dễ nhìn; hệ thống thật dùng hash dài hơn nhiều và mỗi lá thường là một khoảng nhiều dòng. Với 8 dòng thì chưa thấy lợi nhiều, nhưng bảng có hàng triệu dòng mà chỉ lệch vài dòng thì số hash phải so chỉ tăng theo log của số dòng.
Gọi N là số replica của một key. Client coi lệnh ghi thành công khi W replica xác nhận, và mỗi lần đọc hỏi R replica. Nếu W + R > N thì tập replica được đọc luôn có ít nhất một replica nằm trong tập vừa ghi, nên lần đọc thấy được giá trị mới nhất. Cấu hình hay gặp là N = 3, W = 2, R = 2. Cassandra gọi mức này là QUORUM.
Thử: quorum trong leaderless replication
W + R = 4 > N = 3. Tập node đọc luôn giao với tập node vừa ghi ít nhất 1 node.
node 1Av1
node 2Av1
node 3Av1
có giá trị mới nhất đã ghi thành cônggiá trị của lệnh ghi báo lỗivừa ghivừa đọc
Gợi ý: để N = 3, W = 2, R = 2, bấm “Ghi lỗi giữa chừng” rồi “Đọc 20 lần”, sẽ thấy lúc đọc ra giá trị mới, lúc ra giá trị cũ. Đổi R = 1 rồi ghi bình thường và đọc 20 lần để thấy đọc trượt khi W + R ≤ N. Mỗi lần ghi và đọc chọn node ngẫu nhiên.
W và R là chỗ đánh đổi. W nhỏ thì ghi nhanh nhưng đọc phải hỏi nhiều replica; W = N thì một replica sập là không ghi được.
Dù W + R > N, vẫn có những trường hợp hai client đọc cùng lúc mà thấy hai giá trị khác nhau:
Nhiều lệnh ghi concurrent đến các replica theo thứ tự khác nhau. Ví dụ ba client cùng ghi x = 1, x = 2, x = 3 vào cả ba replica. Replica 1 nhận theo thứ tự 1, 2, 3 nên giữ 3; replica 2 nhận 3, 1, 2 nên giữ 2; replica 3 nhận 2, 3, 1 nên giữ 1. Hai client cùng đọc quorum 2 replica sẽ thấy hai cặp giá trị khác nhau, và không replica nào biết giá trị nào mới nhất. Nếu dùng LWW để chọn thì hai trong ba lệnh ghi bị mất.
Lệnh ghi lỗi giữa chừng. Ghi thành công ở 1 replica, thiếu replica thứ 2 nên client nhận lỗi. Nhưng replica kia không rollback, nên có lần đọc thấy giá trị mới, có lần không. Read repair hay anti-entropy còn có thể lan giá trị đó ra cả cluster. Bấm “Ghi lỗi giữa chừng” trong mô phỏng để thấy.
Sloppy quorum. Khi không liên lạc được đủ W replica “chính chủ” của một key, Dynamo và Riak cho ghi tạm vào replica khác trong cluster để không từ chối lệnh ghi. Lúc đó tập ghi và tập đọc có thể không giao nhau. Khi replica chính chủ sống lại, replica tạm gửi trả dữ liệu, gọi là hinted handoff. Cassandra cũng có hinted handoff (mặc định giữ hint trong 3 giờ), nhưng hint không được tính vào W.
Một replica chứa giá trị mới bị hỏng và được khôi phục từ replica cũ hơn, nên số replica có giá trị mới tụt xuống dưới W.
Vì vậy quorum chỉ cho eventual consistency mạnh hơn một chút, không phải linearizable.
Linearizable nghĩa là hệ thống cư xử như thể dữ liệu chỉ có một bản: một khi có client đọc được giá trị mới, mọi lần đọc sau đó (của bất kỳ client nào) cũng phải thấy giá trị đó hoặc mới hơn. Distributed lock, bầu leader, kiểm tra trùng username đều cần tính chất này (Distributed lock).
Mô hình
Linearizable?
Single leader, đọc từ follower, async
Không, vì follower có thể chậm
Single leader, chỉ đọc từ leader
Gần như có, nhưng failover với async replication làm mất lệnh ghi, và leader cũ chưa biết mình bị thay vẫn có thể trả lời
Multi leader
Không, vì có write conflict
Leaderless với quorum
Không, vì các trường hợp ở mục trên
Consensus (Raft, Paxos)
Có
Hai ví dụ cụ thể:
Single leader, chỉ đọc từ leader. Leader nhận x = 5 rồi x = 10, nhưng mới gửi x = 5 sang follower. Client đọc leader thấy 5, đọc lần nữa thấy 10. Leader sập, follower lên thay, client đọc tiếp thấy 5. Giá trị đã lùi về quá khứ dù client chưa từng đọc từ follower.
Multi leader. L1 nhận x = 1, L2 nhận x = 5 sau đó. Trước khi hai leader kịp gửi cho nhau, một client đọc L1 thấy 1, đọc L2 thấy 5, đọc lại L1 lại thấy 1.
Muốn xếp mọi lệnh ghi theo một thứ tự chung thì có thể dùng Lamport clock. Mỗi node và mỗi client giữ một bộ đếm. Mỗi lần client ghi vào node, cả hai đặt bộ đếm bằng max của hai bên cộng 1, và lệnh ghi mang số đó kèm tên node. Hai lệnh ghi có cùng số thì so tên node. Ví dụ hai client C1, C2 và hai node A, B, mọi bộ đếm bắt đầu từ 0:
Bước
Lệnh ghi
Tính
Lamport timestamp
1
C1 ghi vào A
max(0, 0) + 1
(1, A)
2
C2 ghi vào B
max(0, 0) + 1
(1, B)
3
C2 ghi vào B
max(1, 1) + 1
(2, B)
4
C2 ghi vào A
max(C2 = 2, A = 1) + 1
(3, A)
5
C1 ghi vào A
max(C1 = 1, A = 3) + 1
(4, A)
Thứ tự toàn phần là (1, A), (1, B), (2, B), (3, A), (4, A). Ở bước 4, A nhảy từ 1 lên 3 vì C2 mang theo số 2 từ B, nhờ vậy lệnh ghi C2 làm sau luôn đứng sau lệnh ghi C2 làm trước. Lamport clock gọn hơn version vector vì chỉ gồm một số và một tên node, dù cluster có bao nhiêu node. Nhưng thứ tự này chỉ biết sau khi đã gom đủ mọi lệnh ghi, nên lúc đang ghi một node không biết có lệnh ghi nào khác “đứng trước” mình hay không. Vì vậy Lamport clock không đủ để làm lock hay kiểm tra trùng. Muốn mọi node thống nhất thứ tự ngay lúc ghi thì cần total order broadcast, tức là consensus (Đồng thuận).
Single leader, async replication, read replica để tăng đọc
Không được mất lệnh ghi đã báo OK
Single leader với semi-sync, hoặc cluster consensus (MySQL Group Replication, etcd)
Người dùng ở nhiều châu lục, cần ghi nhanh ở mọi nơi
Multi leader theo vùng, kèm cách xử lý conflict; hoặc partition theo vùng để mỗi user chỉ ghi vào một leader
Ghi rất nhiều, chấp nhận eventual consistency, không muốn failover
Leaderless (Cassandra, ScyllaDB)
Ứng dụng offline-first, sửa chung tài liệu
Multi leader với CRDT
Ghi nhớ
Replication tăng độ bền, throughput đọc và giảm latency theo vùng. Nó không chia được tải ghi; việc đó là của partitioning.
Async replication nhanh nhưng có replication lag, và failover có thể làm mất lệnh ghi đã báo OK. Ba kiểu đọc sai cần chặn: không đọc được thứ mình vừa ghi, đọc lùi về quá khứ, thấy câu trả lời trước câu hỏi.
Single leader không có write conflict, nhưng failover khó: khó biết leader sập thật, dễ mất lệnh ghi, dễ split brain.
Multi leader và leaderless phải xử lý write conflict. LWW làm mất dữ liệu; version vector phát hiện được lệnh ghi concurrent; CRDT gộp tự động.
Quorum W + R > N giúp đọc thấy giá trị mới trong đa số trường hợp, nhưng không linearizable. Muốn linearizable thì cần consensus.
Linearizability, Lamport clock, vì sao cần consensus
Những chỗ trang này không theo video:
Video nói replicate dạng statement hỏng vì hàm lấy thời gian hiện tại. Thực ra MySQL coi NOW() là an toàn khi replicate dạng statement; các hàm bị coi là không an toàn là UUID(), RAND(), SYSDATE(), USER()…, cùng UPDATE … LIMIT.
Video lấy ví dụ WAL không dùng được khi replicate MySQL sang PostgreSQL. Vấn đề thường gặp hơn là WAL buộc primary và replica phải cùng major version, nên khó nâng cấp từng máy.
Video nói multi leader chỉ đọc từ follower. Thực tế đọc từ leader cũng được.
Video tổng kết rằng multi leader và leaderless có throughput ghi cao hơn single leader. Mỗi lệnh ghi vẫn phải áp dụng trên mọi replica, nên replication không tăng throughput ghi tổng; cái tăng là latency ghi theo vùng và khả năng ghi khi một máy sập. Chia tải ghi là việc của partitioning.
Video mô tả sloppy quorum là chuyển lệnh ghi sang một cluster ở vùng khác. Trong Dynamo, sloppy quorum là ghi tạm vào node khác trong cùng cluster khi node chính chủ không liên lạc được.
Video nói sequence CRDT là nền tảng của Google Docs. Google Docs dùng operational transformation (OT), một kỹ thuật khác; CRDT có trong các thư viện như Yjs, Automerge.
Video định nghĩa linearizable là các lệnh ghi có thứ tự và đọc không lùi về quá khứ. Định nghĩa đó gần với monotonic reads; linearizable mạnh hơn, đòi hỏi mọi client thấy cùng một bản dữ liệu mới nhất.
Mỗi partition có leader bầu bằng Multi-Paxos, chỉ leader nhận ghi
Từ kiến thức chung, chưa đối chiếu lần này:
Martin Kleppmann, Designing Data-Intensive Applications (O’Reilly, 2017), chương 5 (replication) và chương 9 (linearizability, Lamport clock, total order broadcast). Playlist đi theo thứ tự của hai chương này.
Cassandra không tính hint vào consistency level (trừ mức ANY); tài liệu Hints chỉ gợi ý chứ không nói thẳng.
Patroni dùng etcd, Consul hoặc ZooKeeper; MySQL Group Replication dùng một biến thể Paxos (chương 9 đã nhắc).
Redis Enterprise Active-Active dùng CRDT; Riak có kiểu dữ liệu CRDT; Google Docs dùng OT.
OR-Set: lệnh thêm concurrent với lệnh xoá thì thêm thắng.