Trang thanh toán báo giao dịch đã thành công. Khách hàng refresh và vẫn thấy đơn hàng chưa được trả tiền, nên họ thử lại. Mười phút sau, hai khoản trừ tiền xuất hiện. Mỗi server đều xử lý đúng theo dữ liệu cục bộ, replication phục hồi đúng thiết kế, nhưng toàn hệ thống vẫn phá vỡ lời hứa với người dùng.
Lời hứa đó là một mô hình nhất quán: quy tắc mô tả giá trị mà read được phép trả về khi thao tác chồng lấn, replica chậm, client chuyển vùng hoặc mạng bị đứt. “Strong” và “eventual” chỉ là cách nói tắt. Đội ngũ cần biết thứ tự nào quan sát được, anomaly nào chấp nhận được và lúc nào coordination đáng để đổi lấy latency cùng availability.
Consistency cũng không đồng nghĩa với transaction isolation. Consistency nói về visibility và ordering giữa các bản sao; isolation nói về transaction đồng thời. Database có thể cung cấp linearizable read nhưng vẫn cho phép write skew dưới snapshot isolation, hoặc serialize transaction trên leader nhưng trả dữ liệu cũ từ follower. Product contract phải bao phủ cả hai chiều khi cần.
Consistency là contract về thứ tự quan sát được
Ta mô hình hóa một lần chạy thành history gồm các sự kiện gọi và trả về. Ký hiệu khi thao tác hoàn tất trước lúc thao tác bắt đầu theo thời gian thực. Một register có tính linearizable nếu các thao tác đã hoàn tất có thể được sắp thành một history tuần tự hợp lệ sao cho:
Mỗi thao tác có vẻ hiệu lực tại một linearization point giữa lúc gọi và trả về. Nếu write của An đã xong trước khi Bình đọc, Bình phải thấy giá trị ấy hoặc mới hơn; các thao tác concurrent có thể xếp theo một trong hai thứ tự. Linearizability có tính compositional: từng register linearizable thì tập hợp của chúng cũng vậy. Khi có lỗi, implementation thường cần leader coordination, consensus hoặc quorum protocol.
Sequential consistency cũng yêu cầu một tổng thứ tự hợp lệ giữ program order của từng client, nhưng không giữ real-time order giữa các client. Bình có thể đọc giá trị cũ sau khi write của An hoàn tất nếu global sequence đặt read trước write mà không đảo thao tác của riêng ai. Điều này ít đáng kể trong một số batch workload nhưng gây bất ngờ trong sản phẩm tương tác.
Các mô hình dưới đây không tạo thành một chiếc thang lồng nhau hoàn hảo. Causal consistency và session guarantee ràng buộc những quan hệ khác nhau; một hệ thống có thể kết hợp chúng.
| Mô hình | Cam kết về ordering hoặc visibility | Bất ngờ vẫn được phép | Trường hợp phù hợp |
|---|---|---|---|
| Linearizable | Một thứ tự hợp lệ tôn trọng thời gian thực | Latency cao hơn hoặc request thất bại khi mất coordination | Lock, uniqueness, số dư, control plane |
| Sequential | Một thứ tự hợp lệ giữ program order của từng client | Lệnh ghi từ xa đã xong vẫn có thể xuất hiện muộn | Tính toán phối hợp không dựa vào wall clock |
| Causal | Nguyên nhân hiện ra trước kết quả; replica có thể xếp khác nhau các write độc lập | Update đồng thời xuất hiện theo thứ tự khác nhau | Hội thoại, cộng tác, social data |
| Session guarantees | Một số bảo đảm monotonic áp dụng trong một client session | Session khác vẫn có thể chậm | Profile, cart, thao tác đọc theo vùng |
| Eventual | Replica hội tụ nếu update dừng và message được chuyển tới | Read có thể stale hoặc đi lùi | Cache, counter, discovery, feed ít quan trọng |
Tip
Hãy gọi tên invariant mà người dùng nhìn thấy trước khi gọi tên consistency model. “Coupon đã dùng không thể dùng lần nữa” có thể kiểm thử. “Hệ thống dùng strong consistency” vẫn bỏ ngỏ phạm vi object, hành vi khi lỗi và read path.
Causality và session giữ lại kỳ vọng tự nhiên
Real-time ordering thường mạnh hơn điều một tính năng cần. Causality nắm bắt thứ tự mà thông tin có thể ảnh hưởng tới thông tin khác. Nếu thao tác đứng trước trong cùng process, nếu đọc giá trị do ghi, hoặc nếu các quan hệ đó nối với nhau theo tính bắc cầu, thì happens before :
Hệ causally consistent không cho thấy mà thiếu prerequisite . Nếu Linh đăng “Migration đã xong” và Minh trả lời “Tôi sẽ deploy”, không ai nên thấy reply trước bài gốc. Hai reaction không liên quan vẫn có thể xuất hiện theo thứ tự khác nhau. Vector clock, dependency set hoặc hybrid logical clock mang context cần thiết, đổi lại metadata và thời gian chờ tăng lên.
Session guarantee cung cấp bộ từ vựng thực dụng, lấy client làm trung tâm:
- Read your writes: sau khi đổi avatar, cùng session không hiện avatar cũ.
- Monotonic reads: khi session đã thấy version 12, lần sau không quay về version 11.
- Monotonic writes: các write của một client trở nên visible theo thứ tự đã gửi.
- Writes follow reads: write được tạo sau khi đọc version 12 phải đứng sau version đó.
Kết hợp chúng giúp một client tiến gần causal behavior mà không ép mọi read thành linearizable. Gateway có thể gắn version token, route request tới replica đủ mới hoặc đợi replica bắt kịp. Token phải sống qua reconnect và chuyển vùng nếu đó vẫn là cùng session. Sticky routing đơn thuần sẽ mất bảo đảm khi replica restart hoặc failover.
Phạm vi cần được viết rõ. Read-your-writes cho một tài khoản không ngăn hai nhân viên hỗ trợ ghi đè lên nhau. Monotonic read không đảm bảo trang nhiều key là một snapshot nhất quán. Dashboard có thể ghép tổng invoice ở version 20 với line item ở version 18, tạo fractured read, dù từng key riêng lẻ chỉ tiến về trước.
Eventual convergence và ý nghĩa thật của quorum
Eventual consistency cam kết hội tụ, không cam kết deadline hay kết quả conflict cụ thể. Nói không quá hình thức: nếu không có update mới và mọi update đã nhận cuối cùng đều được chuyển tới, các replica sẽ tiến tới trạng thái tương đương:
Hệ thống vẫn cần merge rule tất định. Last-write-wins có thể làm mất update khi clock lệch; version vector phát hiện concurrency nhưng cần resolve conflict. CRDT hội tụ đối với kiểu dữ liệu được hỗ trợ, song semantics vẫn quan trọng: union hai cart có thể làm sống lại món hàng đã xóa. “Cuối cùng giống nhau” không đảm bảo kết quả đúng với nghiệp vụ.
Hãy xem một write được một replica xác nhận trước khi replication tới replica còn lại:
Với replica, hệ thống chọn read quorum và write quorum . Điều kiện khiến read set giao write set; khiến các write set giao nhau. Nhưng đó mới là hình học. Node vẫn phải so sánh version, xử lý concurrent writer, sửa bản stale và xử lý lúc thiếu quorum.
Pseudocode kiểu TypeScript sau phác thảo ABD multi-writer atomic register. Tag được sắp theo counter rồi writer ID. Read có phase write-back; nếu bỏ bước này, một giá trị từng được thấy từ partial write có thể biến mất ở lần đọc sau.
type Tag = readonly [counter: number, writerId: string];
type Versioned<T> = { tag: Tag; value: T };
interface Replica<T> {
query(): Promise<Versioned<T>>;
store(candidate: Versioned<T>): Promise<void>; // giữ tag lớn nhất
}
declare function majority<T>(requests: Array<Promise<T>>): Promise<T[]>;
declare function maxByTag<T>(values: Versioned<T>[]): Versioned<T>;
async function read<T>(replicas: Replica<T>[]): Promise<T> {
const observed = await majority(replicas.map((replica) => replica.query()));
const latest = maxByTag(observed);
await majority(replicas.map((replica) => replica.store(latest)));
return latest.value;
}
async function write<T>(
replicas: Replica<T>[],
writerId: string,
value: T,
): Promise<void> {
const observed = await majority(replicas.map((replica) => replica.query()));
const latest = maxByTag(observed);
const next: Versioned<T> = {
tag: [latest.tag[0] + 1, writerId],
value,
};
await majority(replicas.map((replica) => replica.store(next)));
}
Với link đáng tin cậy, node crash-stop, writer ID duy nhất và một majority hoạt động, protocol tạo linearizable register. Production còn cần retry, deduplication, membership epoch và persistence. Dynamo-style quorum có sloppy quorum, hinted handoff, last-write-wins hoặc membership đổi sẽ có bảo đảm khác. Không thể suy ra model chỉ từ , và .
CAP và PACELC mô tả đánh đổi, không phải nhãn kiến trúc
CAP áp dụng khi network partition khiến một số node không giao tiếp được. Hệ partition-tolerant lúc đó phải chọn giữa consistency, thường được hiểu là linearizability cho register đang bàn, và availability, nghĩa là mọi request gửi tới node không hỏng cuối cùng đều nhận response không lỗi. Hệ CP có thể từ chối hoặc trì hoãn thao tác ở phía minority. Hệ AP có thể nhận thao tác rồi reconcile state bị phân kỳ sau đó.
Đây không phải bài toán “chọn hai trong ba” lúc bình thường. Partition là điều kiện xảy ra; hệ thống có thể chọn theo operation. Catalog có thể trả description cũ trong khi inventory reservation buộc đi qua leader. Bounded waiting biến quyết định latency thành error response, và API contract phải nói response đó có chấp nhận được không.
PACELC bổ sung trường hợp thường: nếu Partition, chọn Availability hoặc Consistency; Else, chọn Latency hoặc Consistency. Synchronous replication xuyên vùng giữ ordering mạnh hơn nhưng đặt khoảng cách mạng trên write path. Local read và async replication giảm latency nhưng cho phép staleness. Placement, batching và leader lease chỉ dịch chuyển, không xóa đánh đổi.
Warning
Đừng giấu failure semantics sau timeout. Write bị timeout vẫn có thể đã commit. Retry không có idempotency key có thể trừ tiền hai lần; báo thất bại khi kết quả thật ra chưa xác định sẽ đánh lừa cả người dùng lẫn compensation workflow.
CAP không quyết định conflict semantics, durability, isolation hay staleness bound. Hệ available vẫn có thể mất acknowledged write; hệ linearizable trên một key vẫn có thể thiếu atomic snapshot nhiều key. CAP và PACELC là câu hỏi làm lộ lựa chọn, không phải huy hiệu database.
Chọn và kiểm thử từ product invariant
Bắt đầu từ workflow, thiệt hại và đường phục hồi. Thu hồi quyền, claim uniqueness, giảm inventory và dịch chuyển tiền thường đáng coordination vì hành động lặp lại rất đắt. Search index, presence, analytics và recommendation feed thường chịu được bounded staleness. Profile có thể chỉ cần read-your-writes; collaborative editing cần causal ordering cùng merge rule của domain.
Viết contract quan sát được: “Sau khi checkout trả success, mọi authorization đều thấy reservation”, hoặc “Search được chậm 30 giây.” Ghi phạm vi key, tenant, region, session và hành vi khi không giữ được bảo đảm: fail closed, trả stale data kèm tuổi, queue hoặc degrade. Chọn model yếu nhất vẫn thỏa invariant vì coordination mạnh tiêu thụ latency và failure budget.
Các anomaly có tên giúp review cụ thể. Stale read trả version cũ. Monotonic-read violation đưa client lùi lại. Lost update ghi đè concurrent work. Read skew ghép giá trị từ các logical time khác nhau. Write skew giữ rule từng row nhưng phá invariant xuyên row, thường thuộc isolation. Duplicate effect thường do retry, nhưng với người dùng vẫn là consistency promise bị phá.
Unit test thông thường không thể chứng minh distributed guarantee. Cần thêm các lớp tương xứng với rủi ro:
- Model-based test sinh operation history rồi kiểm tra bằng reference state machine nhỏ.
- Deterministic simulation thay đổi thứ tự message, delay, crash, restart và clock nhưng giữ seed để replay.
- Fault-injection test chia cắt replica, pause leader, làm mất acknowledgement và chuyển client giữa các region.
- Linearizability checker tìm trong concurrent history một thứ tự hợp lệ có tôn trọng real time hay không.
- Product test assert session token, idempotency key, conflict response và stale-read indicator tại API boundary.
Ghi invocation time, completion time, session ID, operation, input, output, replica và version metadata; log chỉ có giá trị cuối không dựng lại được ordering. Hãy test ambiguous outcome bằng cách ngắt kết nối sau commit nhưng trước response, rồi xác minh retry cùng command không áp dụng lần hai.
Production nên đo phân phối replication lag, quorum failure, leader change, conflict rate, session-token wait và tuổi dữ liệu. Average che khuất người dùng ở replica chậm. Alert phải gắn với contract: search lag 45 giây đạt objective một phút nhưng vi phạm objective năm giây.
Tổng kết
Linearizability cho các operation một thứ tự hợp lệ duy nhất có tôn trọng real time. Sequential consistency giữ program order của client nhưng bỏ cam kết real-time xuyên client. Causal consistency xếp cause trước effect, còn session guarantee giữ một số kỳ vọng trên hành trình của một người dùng. Eventual consistency chỉ cam kết hội tụ sau khi update dừng và delivery thành công; nó không tự định nghĩa freshness hay cách resolve conflict đúng với nghiệp vụ.
Quorum là nguyên liệu, không phải kết luận. Bảo đảm phụ thuộc vào versioning, read repair, membership, failure assumption và protocol cụ thể. CAP giải thích lựa chọn khi partition; PACELC nhắc rằng đánh đổi giữa latency và coordination vẫn tồn tại khi network khỏe mạnh.
Phương pháp thực dụng bắt đầu từ product invariant và failure behavior tường minh. Dùng coordination mạnh nơi hành động trùng hoặc đảo thứ tự gây thiệt hại không chấp nhận được; dùng model yếu hơn nơi stale information có thể phục hồi; và kiểm thử history thật dưới fault. Consistency model chỉ có giá trị khi kỹ sư nói được người dùng sẽ quan sát gì vào ngày tệ nhất, không chỉ database chạy ra sao trong benchmark sạch.