Giao diện
Data Consistency Models — Mô hình nhất quán dữ liệu
Một kỹ sư backend vừa deploy tính năng cập nhật avatar cho hệ thống có 12 triệu user. Mọi thứ hoạt động trơn tru trên staging — user đổi ảnh, refresh trang, ảnh mới hiển thị ngay. Production lên sóng, và support ticket đổ về: "Tôi đổi ảnh rồi mà vẫn thấy ảnh cũ." Nguyên nhân? Staging chạy single node, production chạy 3 replica với eventual consistency. Read request đánh vào replica chưa kịp sync.
Câu chuyện này lặp lại ở mọi quy mô. Một hệ thống ngân hàng cho phép chuyển khoản nhưng số dư hiển thị sai trong vài giây — khách hàng hoảng loạn vì tưởng "mất tiền". Một sàn thương mại điện tử oversell sản phẩm vì hai request đọc inventory từ hai replica khác nhau, cả hai đều thấy còn 1 sản phẩm.
Consistency model không phải khái niệm học thuật trừu tượng. Nó là quyết định kiến trúc ảnh hưởng trực tiếp đến trải nghiệm người dùng, tính đúng đắn của dữ liệu, và khả năng mở rộng của hệ thống. Chọn sai mô hình, bạn sẽ phải đánh đổi giữa dữ liệu sai và hệ thống chậm — cả hai đều không chấp nhận được trong production.
Bức tranh tư duy
Hãy hình dung một tập đoàn có trụ sở chính tại Đà Nẵng và hai chi nhánh tại Hà Nội, TP.HCM. Ban giám đốc ra thông báo tăng lương 10%.
Với strong consistency, thông báo chỉ được công bố khi cả ba văn phòng đã nhận, xác nhận, và dán lên bảng tin. Không ai biết trước ai — tất cả biết cùng lúc. Chi phí? Phải tổ chức video conference ba bên, chờ đường truyền chậm nhất.
Với eventual consistency, trụ sở gửi email cho hai chi nhánh. Hà Nội nhận sau 1 phút, TP.HCM nhận sau 5 phút. Trong khoảng thời gian giữa, nhân viên Hà Nội đã biết tin nhưng nhân viên TP.HCM thì chưa. Cuối cùng mọi người đều biết — nhưng không đồng thời.
Với causal consistency, nếu thông báo tăng lương dẫn đến thông báo điều chỉnh thuế, mọi chi nhánh đều thấy thông báo tăng lương trước thông báo thuế. Nhưng hai thông báo không liên quan (ví dụ tăng lương và đổi menu canteen) có thể đến theo thứ tự khác nhau tại mỗi chi nhánh.
Khi nào phép so sánh này không còn đúng? Máy tính có khả năng thiết lập causal ordering — theo dõi chính xác sự kiện nào gây ra sự kiện nào thông qua vector clock. Con người trong văn phòng không có cơ chế tương đương. Ngoài ra, máy tính có thể rollback và retry — nhân viên đã đọc thông báo thì không thể "unread".
Cốt lõi kỹ thuật
Strong Consistency (Linearizability)
Linearizability là mô hình nhất quán mạnh nhất. Nó tạo ra ảo giác single-copy — dù hệ thống có bao nhiêu replica, mọi client đều thấy như chỉ có một bản duy nhất. Mỗi operation dường như thực thi tại một thời điểm duy nhất nằm giữa lúc gọi và lúc nhận response.
Điều này có nghĩa: nếu client A ghi giá trị x = 5 và nhận confirmation, bất kỳ client B nào đọc x sau thời điểm đó phải nhận được 5 (hoặc giá trị mới hơn). Không có ngoại lệ.
Chi phí: Mỗi write phải đợi đa số replica xác nhận. Latency bị ràng buộc bởi replica chậm nhất trong quorum. Trong cross-datacenter deployment, latency có thể lên đến hàng trăm millisecond.
Implementation tiêu biểu: Raft, Multi-Paxos, ZAB (ZooKeeper). Các hệ thống như etcd, ZooKeeper, Google Spanner cung cấp linearizability.
Sequential Consistency
Sequential consistency yếu hơn linearizability ở một điểm quan trọng: nó đảm bảo mọi process thấy cùng một thứ tự các operation, nhưng thứ tự đó không nhất thiết khớp với real-time order.
Ví dụ: Client A ghi x = 1 lúc 10:00:00, Client B ghi x = 2 lúc 10:00:01. Với linearizability, mọi reader phải thấy 1 rồi 2. Với sequential consistency, hệ thống có thể chọn thứ tự 2 rồi 1 — miễn sao tất cả client đều thấy cùng thứ tự đó.
Use case: Ít gặp trong production hiện đại. Quan trọng hơn trong memory model của CPU (Java Memory Model đảm bảo sequential consistency cho volatile variables).
Causal Consistency
Causal consistency nắm bắt mối quan hệ nhân quả giữa các operation. Nếu operation A gây ra operation B (ví dụ: user đọc comment rồi reply), mọi process phải thấy A trước B. Nhưng hai operation không liên quan nhân quả (concurrent) có thể xuất hiện theo thứ tự khác nhau tại các replica.
Implementation: Vector clock, dependency tracking. COPS (Clusters of Order-Preserving Servers) là paper kinh điển.
Use case thực tế: Social media feed — reply phải hiển thị sau post gốc, nhưng hai post không liên quan có thể hiển thị theo thứ tự bất kỳ.
Read-your-writes Consistency
Đây là đảm bảo thực tế nhất đối với trải nghiệm người dùng: sau khi user ghi dữ liệu, chính user đó luôn đọc được giá trị mình vừa ghi. User khác có thể chưa thấy.
Quay lại ví dụ đầu bài — user đổi avatar rồi refresh trang. Với read-your-writes, user đó luôn thấy avatar mới. User khác có thể vẫn thấy avatar cũ trong vài giây.
Implementation phổ biến:
- Sticky session — route mọi request của cùng user về cùng replica
- Read from leader — sau write, đọc trực tiếp từ leader thay vì replica
- Version token — client gửi kèm version của write gần nhất, server đảm bảo trả về version ≥ token đó
Monotonic Reads
Monotonic reads đảm bảo: nếu một process đã đọc được giá trị v của key x, mọi lần đọc x tiếp theo sẽ trả về v hoặc giá trị mới hơn. Không bao giờ đi ngược thời gian.
Vi phạm monotonic reads trông như thế nào: User refresh trang lần 1 — thấy 10 comment. Refresh lần 2 — thấy 8 comment (đọc từ replica cũ hơn). Refresh lần 3 — lại thấy 10 comment. Trải nghiệm này gây hoang mang.
Kết hợp với read-your-writes: Hai đảm bảo này thường đi cùng nhau trong session-based consistency. Client duy trì session token chứa thông tin version, server sử dụng token để đảm bảo cả hai tính chất.
Eventual Consistency
Eventual consistency là mô hình yếu nhất nhưng có throughput và availability cao nhất. Đảm bảo duy nhất: nếu không có write mới, cuối cùng tất cả replica sẽ converge về cùng giá trị.
Chữ "cuối cùng" ở đây không có time bound. Có thể là 10ms, có thể là 10 phút. Hệ thống không cam kết thời gian cụ thể.
Tại sao vẫn hữu ích? Rất nhiều use case không cần strong consistency. DNS là ví dụ kinh điển — TTL của DNS record có thể lên đến 24 giờ, và internet vẫn hoạt động. View counter trên YouTube không cần chính xác tuyệt đối từng giây. Shopping cart recommendation không cần real-time.
Implementation tiêu biểu: Amazon DynamoDB (default), Apache Cassandra (với consistency level ONE), DNS, CDN cache.
Linearizability vs Serializability
Hai khái niệm này thường bị nhầm lẫn. Chúng giải quyết hai vấn đề hoàn toàn khác nhau.
| Tiêu chí | Linearizability | Serializability |
|---|---|---|
| Phạm vi | Single object/register | Toàn bộ transaction |
| Đảm bảo | Real-time ordering | Kết quả tương đương serial execution |
| Thuộc về | Distributed systems | Database isolation levels |
| Ví dụ | Read sau write phải thấy giá trị mới | Hai transfer đồng thời không corrupt balance |
| Kết hợp | Strict Serializability = Linearizability + Serializability |
Strict serializability (hay "one-copy serializability") là đảm bảo mạnh nhất — kết hợp cả hai. Google Spanner và CockroachDB cung cấp mức đảm bảo này thông qua synchronized clocks hoặc hybrid logical clocks.
Bảng so sánh tổng hợp
| Mô hình | Đảm bảo | Chi phí coordination | Availability | Use case điển hình |
|---|---|---|---|---|
| Linearizability | Real-time order, single-copy | Rất cao | Thấp (cần quorum) | Leader election, distributed lock |
| Sequential | Global total order | Cao | Thấp | Memory model, replicated state machine |
| Causal | Causal order preserved | Trung bình | Trung bình–Cao | Social feed, collaborative editing |
| Read-your-writes | User thấy write của mình | Thấp–Trung bình | Cao | User profile, settings |
| Monotonic reads | Không đi ngược thời gian | Thấp | Cao | Timeline, activity log |
| Eventual | Converge cuối cùng | Rất thấp | Rất cao | DNS, view counter, cache |
Thực chiến
Lab 1 — Chọn consistency level trong Cassandra
Cassandra cho phép cấu hình consistency level per-query. Ba thông số quan trọng:
- N — replication factor (số replica lưu trữ dữ liệu)
- W — số replica phải acknowledge write
- R — số replica phải respond cho read
Quy tắc vàng: Nếu R + W > N, hệ thống đảm bảo strong consistency cho operation đó. Vì ít nhất một replica trong read quorum sẽ chứa giá trị mới nhất.
Ví dụ với N = 3:
| Write CL | Read CL | R + W | Strong? | Nhận xét |
|----------|---------|-------|---------|-----------------------------------|
| ONE (1) | ONE (1) | 2 | ❌ | Eventual — nhanh nhất |
| ONE (1) | ALL (3) | 4 | ✅ | Write nhanh, read chậm |
| QUORUM(2)| QUORUM(2)| 4 | ✅ | Cân bằng — phổ biến nhất |
| ALL (3) | ONE (1) | 4 | ✅ | Write chậm, read nhanh |
| ALL (3) | ALL (3) | 6 | ✅ | Chậm nhất — hiếm khi dùng |QUORUM + QUORUM là lựa chọn phổ biến nhất vì cân bằng giữa latency và consistency. QUORUM = floor(N/2) + 1, với N = 3 thì QUORUM = 2.
Lưu ý quan trọng: R + W > N chỉ đảm bảo consistency khi không có node failure. Trong trường hợp hinted handoff hoặc node vừa recover, dữ liệu có thể inconsistent tạm thời. Sử dụng read repair và anti-entropy repair để giảm thiểu window of inconsistency.
Lab 2 — Read-after-write trong hệ thống distributed
Bài toán: User cập nhật email trong trang Settings. Hệ thống ghi vào leader (primary), response trả về thành công. User click sang trang Profile — request đọc đánh vào replica chưa kịp replicate. Email cũ hiển thị.
Giải pháp 1 — Read from leader sau write:
python
class UserService:
def __init__(self, leader_db, replica_db):
self._leader = leader_db
self._replica = replica_db
self._recent_writes: dict[str, float] = {} # user_id -> timestamp
def update_profile(self, user_id: str, data: dict) -> None:
self._leader.update(user_id, data)
self._recent_writes[user_id] = time.monotonic()
def get_profile(self, user_id: str) -> dict:
last_write = self._recent_writes.get(user_id, 0)
# Đọc từ leader nếu write xảy ra trong 10 giây gần đây
if time.monotonic() - last_write < 10:
return self._leader.get(user_id)
return self._replica.get(user_id)Giải pháp 2 — Version-based routing:
python
class VersionAwareProxy:
"""
Client gửi kèm version token nhận được từ write response.
Proxy đảm bảo đọc từ replica có version >= token.
"""
def route_read(self, key: str, min_version: int) -> tuple:
for replica in self._replicas:
version, value = replica.get_with_version(key)
if version >= min_version:
return version, value
# Fallback: đọc từ leader
return self._leader.get_with_version(key)Giải pháp 3 — Sticky session:
Route tất cả request của cùng user session về cùng replica. Đơn giản nhất nhưng có trade-off: mất khả năng load balancing linh hoạt, và nếu replica đó down, cần re-route toàn bộ session.
So sánh ba giải pháp:
| Giải pháp | Ưu điểm | Nhược điểm |
|---|---|---|
| Read from leader | Đơn giản, đảm bảo chắc chắn | Tăng load lên leader |
| Version-based | Linh hoạt, phân tải tốt | Phức tạp implementation |
| Sticky session | Dễ implement | Load balancing kém, failover phức tạp |
Sai lầm điển hình
❌ Dùng eventual consistency cho financial transaction
Tình huống: Hệ thống chuyển khoản đọc balance từ replica, cho phép chuyển tiền, nhưng replica chưa reflect transaction trước đó. Kết quả: overdraft — tài khoản bị âm. Financial data cần linearizability hoặc strict serializability ở mức tối thiểu. Không có ngoại lệ.
❌ Nghĩ rằng strong consistency = luôn đúng
Strong consistency đảm bảo ordering, không đảm bảo availability. Theo CAP theorem, trong network partition, bạn phải chọn giữa consistency và availability. Hệ thống linearizable sẽ từ chối serve request thay vì trả về stale data. Điều này có thể biến thành outage nếu partition kéo dài.
❌ Bỏ qua read-your-writes trong feature user-facing
User không quan tâm eventual consistency là gì. Họ chỉ biết: "Tôi vừa đổi tên mà sao vẫn thấy tên cũ?" Mọi user-facing write phải đi kèm read-your-writes guarantee, bất kể mô hình consistency tổng thể.
❌ Dùng strong consistency everywhere "cho chắc"
Linearizability trên mọi operation = throughput thấp, latency cao, availability giảm. View counter của blog post không cần linearizable. Product recommendation không cần real-time. Phân loại dữ liệu theo yêu cầu consistency và áp dụng mô hình phù hợp cho từng loại.
❌ Hiểu "eventual" là "nhanh thôi"
"Eventual" có nghĩa là không có upper bound cho convergence time. Trong điều kiện bình thường, replication lag có thể dưới 100ms. Nhưng khi mạng congestion, node overload, hoặc cross-region replication, lag có thể lên phút hoặc thậm chí giờ. Design cho worst case, không phải average case.
Under the Hood
Lamport Timestamps — Logical Clock
Leslie Lamport đề xuất logical clock năm 1978 để thiết lập happens-before relation mà không cần đồng hồ vật lý synchronized.
Mỗi process duy trì một counter C. Quy tắc:
- Trước mỗi event nội bộ:
C = C + 1 - Khi gửi message: đính kèm
Cvào message - Khi nhận message với timestamp
T:C = max(C, T) + 1
Happens-before relation (→):
- Nếu
avàblà event trên cùng process vàaxảy ra trướcb:a → b - Nếu
alà event gửi message vàblà event nhận message đó:a → b - Transitive: nếu
a → bvàb → cthìa → c
Tính chất quan trọng: Nếu a → b thì C(a) < C(b). Nhưng chiều ngược lại không đúng — C(a) < C(b) không có nghĩa a → b. Hai event có thể concurrent (không liên quan nhân quả) nhưng tình cờ có timestamp khác nhau. Đây là giới hạn fundamental của Lamport timestamp.
Vector Clocks — Nắm bắt quan hệ nhân quả
Vector clock khắc phục giới hạn của Lamport timestamp bằng cách duy trì một counter cho mỗi process trong hệ thống. Với N process, mỗi process duy trì vector V[0..N-1].
Quy tắc:
- Trước mỗi event nội bộ tại process
i:V[i] = V[i] + 1 - Khi gửi message: đính kèm toàn bộ vector
V - Khi nhận message với vector
V_msgtại processi:V[j] = max(V[j], V_msg[j])cho mọijV[i] = V[i] + 1
So sánh hai vector clock:
V1 ≤ V2nếuV1[i] ≤ V2[i]cho mọiiV1 < V2(V1 happens-before V2) nếuV1 ≤ V2vàV1 ≠ V2- Nếu không có
V1 < V2cũng khôngV2 < V1→ hai event là concurrent
Space overhead: Vector clock có kích thước O(N) với N là số process. Trong hệ thống hàng nghìn node, đây là vấn đề thực tế. Giải pháp: dotted version vectors, interval tree clocks, hoặc giới hạn số entry và garbage collect entry cũ.
Happens-before Relation — Định nghĩa hình thức
Quan hệ happens-before (ký hiệu →) là partial order trên tập hợp event trong hệ thống phân tán. Partial order nghĩa là không phải mọi cặp event đều so sánh được — hai event concurrent không có thứ tự.
Đặc tả hình thức:
Cho tập event E và quan hệ →:
1. ∀ a, b ∈ process P: index(a) < index(b) ⟹ a → b
2. ∀ send(m), recv(m): send(m) → recv(m)
3. ∀ a, b, c: (a → b) ∧ (b → c) ⟹ a → c
4. a ∥ b (concurrent) ⟺ ¬(a → b) ∧ ¬(b → a)Ý nghĩa thực tiễn: Khi hai event concurrent, hệ thống phải quyết định thứ tự bằng cơ chế khác — last-writer-wins (LWW), application-level merge, hoặc CRDT.
CRDT — Conflict-Free Replicated Data Types
CRDT là cấu trúc dữ liệu được thiết kế để tự động resolve conflict khi merge các replica. Không cần coordination, không cần consensus — mỗi replica cập nhật độc lập và merge bất kỳ lúc nào. Kết quả merge luôn deterministic và consistent.
Hai loại CRDT chính:
State-based (CvRDT): Mỗi replica gửi toàn bộ state. Merge bằng hàm join (phải là semilattice — commutative, associative, idempotent).
- G-Counter: grow-only counter, mỗi node có counter riêng, merge = max từng phần tử
- PN-Counter: positive-negative counter = hai G-Counter
- G-Set: grow-only set, merge = union
- OR-Set: observed-remove set, hỗ trợ add và remove
Operation-based (CmRDT): Mỗi replica broadcast operation. Yêu cầu reliable delivery (mọi operation đến mọi replica) và có thể yêu cầu causal delivery.
Use case thực tế: Collaborative editing (Google Docs dùng OT, nhưng CRDT là alternative — Xi editor, Yjs, Automerge), distributed counters (like/view count), shared shopping cart.
Quorum Formula — Toán học đằng sau consistency
Với hệ thống có N replica, W write quorum, R read quorum:
Strong consistency ⟺ R + W > NChứng minh trực giác: Nếu write thành công trên W replica và read truy vấn R replica, hai tập hợp có overlap khi W + R > N (pigeonhole principle). Overlap đảm bảo ít nhất 1 replica trong read quorum chứa giá trị mới nhất.
Các cấu hình phổ biến:
| Config | W | R | Đặc điểm |
|---|---|---|---|
| Write-heavy | 1 | N | Write nhanh, read chậm (phải hỏi tất cả) |
| Read-heavy | N | 1 | Write chậm (phải ghi tất cả), read nhanh |
| Balanced | ⌈(N+1)/2⌉ | ⌈(N+1)/2⌉ | Trade-off cân bằng |
Fault tolerance: Với quorum config, hệ thống chịu được N - W node failure cho write và N - R node failure cho read. Với W = R = majority, chịu được ⌊(N-1)/2⌋ node failure.
Checklist ghi nhớ
✅ Checklist triển khai
Chọn mô hình
- [ ] Phân loại dữ liệu theo mức consistency cần thiết (financial vs. social vs. analytics)
- [ ] Financial/inventory data → linearizability hoặc strict serializability
- [ ] User-facing writes → read-your-writes ở mức tối thiểu
- [ ] Analytics, counters, recommendation → eventual consistency đủ dùng
- [ ] Hiểu rõ CAP trade-off: consistency vs. availability trong network partition
Thiết kế hệ thống
- [ ] Xác định R, W, N cho từng loại query nếu dùng quorum-based system
- [ ] Implement read-your-writes cho mọi user-facing feature
- [ ] Cân nhắc monotonic reads để tránh "time travel" trong UI
- [ ] Thiết kế conflict resolution strategy cho eventual consistent data
- [ ] Tách biệt consistency level theo use case trong cùng hệ thống
Monitoring & debugging
- [ ] Monitor replication lag giữa leader và replica
- [ ] Alert khi lag vượt ngưỡng chấp nhận được
- [ ] Log consistency violation events (stale read detected)
- [ ] Track per-query consistency level distribution
Data modeling
- [ ] Thiết kế schema tương thích với conflict resolution (idempotent operations)
- [ ] Sử dụng CRDT khi cần merge concurrent writes tự động
- [ ] Tránh read-modify-write pattern trên eventual consistent data
- [ ] Dùng conditional write (compare-and-swap) thay vì blind overwrite
Bài tập luyện tập
🧠 Quiz — Bài 1 — Chọn đúng consistency model
Scenario A: Hệ thống quản lý inventory cho warehouse. Hai nhân viên cùng lúc xuất kho cùng một sản phẩm, chỉ còn 1 đơn vị.
Mô hình nào phù hợp?
- [ ] Eventual consistency
- [x] Linearizability
- [ ] Causal consistency
- [ ] Read-your-writes
Giải thích: Inventory có constraint "không âm". Hai concurrent reads + writes có thể oversell nếu không có linearizability. Cần CAS (compare-and-swap) operation đảm bảo chỉ 1 nhân viên xuất kho thành công.
Scenario B: News feed hiển thị bài viết từ các user khác nhau. User A post bài, User B comment. User C cần thấy bài trước comment.
Mô hình nào phù hợp?
- [ ] Linearizability
- [ ] Sequential consistency
- [x] Causal consistency
- [ ] Eventual consistency
Giải thích: Comment caused by post → quan hệ nhân quả. Causal consistency đảm bảo thứ tự nhân quả. Linearizability quá đắt cho social feed. Eventual consistency có thể hiển thị comment trước post.
Scenario C: Dashboard analytics hiển thị tổng page views trong 24 giờ qua.
Mô hình nào phù hợp?
- [ ] Linearizability
- [ ] Sequential consistency
- [ ] Causal consistency
- [x] Eventual consistency
Giải thích: Analytics aggregate không cần real-time accuracy. Sai lệch vài giây (hoặc phút) không ảnh hưởng business decision. Eventual consistency cho phép throughput cao nhất.
Bài 2: Tính toán Cassandra consistency — Intermediate
Cho: Cassandra cluster với replication factor N = 5.
Câu hỏi:
- Giá trị tối thiểu của
QUORUMlà bao nhiêu? - Nếu
W = 2, R = 3, hệ thống có strong consistent không? - Nếu
W = 3, R = 3, chịu được tối đa bao nhiêu node failure cho write? - Cấu hình nào cho read nhanh nhất mà vẫn strong consistent?
- Cấu hình nào cân bằng nhất?
Bài mở rộng: Nếu cluster cross 3 datacenter (2 node mỗi DC), N = 6, và yêu cầu mỗi write phải reach ít nhất 2 datacenter. Tính W tối thiểu và R tương ứng cho strong consistency.
💡 Gợi ý
- Công thức quorum:
⌊N/2⌋ + 1 - Strong consistency yêu cầu:
R + W > N - Fault tolerance cho write:
N - Wnodes có thể down
✅ Lời giải
QUORUM = ⌊5/2⌋ + 1 = 3R + W = 5 = N, cần> N→ Không (boundary case, không đủ)N - W = 5 - 3 = 2nodeW = N = 5, R = 1(nhưng write rất chậm và chịu 0 node failure cho write)W = 3, R = 3(QUORUM/QUORUM, chịu 2 node failure cho cả read và write)
Bài 3: Thiết kế consistency strategy cho e-commerce cart — Advanced
Yêu cầu: Thiết kế hệ thống shopping cart cho sàn e-commerce với 5 triệu DAU.
Ràng buộc:
- Cart data: add/remove item, update quantity
- Inventory check: khi checkout, inventory phải accurate
- User experience: user phải luôn thấy cart mình vừa cập nhật
- Multi-device: user có thể thao tác cart trên cả mobile và web
Câu hỏi thiết kế:
- Cart data nên dùng consistency model nào? Tại sao?
- Inventory check tại thời điểm checkout cần mức consistency nào?
- Khi user thêm item trên mobile và mở web — conflict resolution strategy?
- Nếu dùng CRDT cho cart, chọn CRDT type nào? Mô tả merge semantics.
- Vẽ sequence diagram cho flow: add item → sync across devices → checkout → inventory verify.
💡 Gợi ý
- Cart operations: eventual consistency + CRDT
- Checkout inventory: linearizability (CAS)
- Cross-device sync: causal consistency (operation log)
✅ Lời giải
- Cart operations: eventual consistency + CRDT (OR-Set cho items) + read-your-writes per device
- Checkout inventory: linearizability (CAS trên inventory count)
- Cross-device sync: causal consistency (operation log với vector clock)
- Conflict resolution: OR-Set semantics — add wins over concurrent remove