Nền tảng Trung bình 6 phút đọc

CAP, PACELC và mô hình nhất quán cho engineer

CAP thật sự nói gì, vì sao PACELC là phiên bản bạn nên dùng, và consistency map sang database .NET ra sao. Bộ từ vựng cho mọi lựa chọn lưu trữ.

Mục lục
  1. CAP thật sự nói gì - và vì sao luôn bị trích sai?
  2. PACELC là gì và vì sao là khung tốt hơn?
  3. Mô hình consistency trong thực tế - và bạn cần cái nào?
  4. Triển khai strong consistency trên stack .NET ra sao?
  5. Triển khai eventual consistency an toàn ra sao?
  6. Lựa chọn consistency tạo ra failure mode nào?
  7. Khi nào cuộc trò chuyện consistency là phân tâm?
  8. Đi tiếp đâu từ đây?

Mọi quyết định lưu trữ trong phần còn lại của series đều được tranh luận bằng từ vựng nhất quán: "ở đây dùng Cassandra được vì counter là eventually consistent" hoặc "cái này cần strong consistency, nên giữ trong Postgres". Chương này định nghĩa các từ ấy một lần. Bản ngắn: CAP là ràng buộc ba chiều với một cái tên dở, và PACELC là phiên bản bạn nên trích.

CAP thật sự nói gì - và vì sao luôn bị trích sai?

CAP, do Eric Brewer phát biểu năm 2000, nói một hệ thống phân tán cung cấp được tối đa hai trong:

Cách trích sai là "chọn hai". Tuyên ngôn thật hẹp hơn: trong lúc partition mạng, bạn phải chọn C hoặc A. Không thể có cả hai vì hai nửa của hệ partition không thể đồng thuận write mới nhất nếu không giao tiếp được.

Khi không có partition - trường hợp phổ biến - bạn có cả ba. Đó là lý do "Postgres là CA" nghe sai nhưng chạy được trong thực tế: Postgres ưu tiên C khi partition, mà partition hiếm. Cách viết tắt gây hiểu nhầm vì hàm ý lựa chọn vĩnh viễn trong khi lựa chọn chỉ kích hoạt lúc có sự cố mạng.

PACELC là gì và vì sao là khung tốt hơn?

PACELC, do Daniel Abadi đề xuất 2010, sửa CAP bằng cách thêm trường hợp phổ biến:

Nửa "else" là trade-off hằng ngày. Cả khi mọi node khoẻ, strong consistency yêu cầu chờ một quorum replica xác nhận write - cộng thêm latency. Eventual consistency trả về ngay và để cluster đuổi kịp ở background.

Nhãn database theo PACELC:

Nhãn là cấu hình, không phải thương hiệu. Cùng database có thể PC/EC hay PA/EL tuỳ cách đặt replication và read concern.

Mô hình consistency trong thực tế - và bạn cần cái nào?

Năm mô hình thực dụng, từ yếu đến mạnh:

flowchart LR
    EV[Eventual<br/>đọc có thể cũ] --> RYW[Read-your-writes<br/>thấy write của chính mình]
    RYW --> MR[Monotonic Reads<br/>không lùi thời gian]
    MR --> CC[Causal<br/>nguyên nhân trước hệ quả]
    CC --> Strong[Strong / Linearizable<br/>thứ tự toàn cục duy nhất]

Sai lầm là chọn một mô hình cho cả hệ. Một app .NET thật trộn cả chùm: thanh toán strong, feed eventual, chat causal. Kiến trúc đi theo cái trộn đó.

Triển khai strong consistency trên stack .NET ra sao?

Cho phần lớn app .NET, "strong" nghĩa là một Postgres primary + EF Core transaction serializable:

// Strong consistency cho giảm inventory.
// Serializable isolation đảm bảo không hai request cùng pass
// kiểm tra "stock > 0" và giảm xuống dưới không.
await using var tx = await db.Database.BeginTransactionAsync(IsolationLevel.Serializable);

var item = await db.Items
    .Where(i => i.Id == itemId)
    .FirstAsync();

if (item.Stock <= 0)
{
    await tx.RollbackAsync();
    return Result.OutOfStock();
}

item.Stock -= 1;
await db.SaveChangesAsync();
await tx.CommitAsync();

Hai thứ cần biết. Một, transaction serializable trong Postgres có thể fail với serialization conflict (SQLState 40001) - bạn phải retry. Hai, chi phí xuất hiện dưới contention; nếu 1000 request đồng thời cùng giảm một row, mỗi lúc chỉ một thành công. Đó chính là đảm bảo đúng đắn bạn đang mua.

Khi cần strong consistency xuyên service, bạn lên cấp outbox pattern và cuối cùng saga.

Triển khai eventual consistency an toàn ra sao?

Ba quy tắc.

Quy tắc 1: nêu cửa sổ hội tụ. "Eventual" không kèm số là vô dụng. Nói "hội tụ trong 5 giây, p99 30 giây" để team biết kỳ vọng. Cửa sổ đến từ cấu hình replication.

Quy tắc 2: thiết kế thao tác idempotent. Hệ eventually consistent thường retry phía application. Nếu "tăng counter" không idempotent, retry làm phình count. Chương 10 xử lý idempotency key.

Quy tắc 3: đọc từ primary cho read-your-writes. Ngay cả trong hệ eventually consistent, bạn có thể route read của một user về primary trong vài giây sau write. Session affinity ASP.NET Core + header "cửa sổ stickiness" là cách .NET triển khai:

// Sau write, set cookie cho gateway dùng route các read kế tiếp
// của user này tới primary database.
Response.Cookies.Append("db-stickiness", "primary",
    new CookieOptions { MaxAge = TimeSpan.FromSeconds(5) });

Cookie được gateway / reverse proxy đọc và lái tải read. Phần lớn user không bao giờ nhận thấy eventual consistency vì họ bị dính vào primary trong lúc write của họ đang lan tới.

Lựa chọn consistency tạo ra failure mode nào?

Mỗi mô hình có lỗi đặc trưng:

Chương observability (13) theo dõi các lỗi này: replication lag tính bằng giây cho hệ eventual, transaction deadlock count cho hệ strong, alert vector-clock skew cho hệ causal.

Khi nào cuộc trò chuyện consistency là phân tâm?

Khi traffic thấp. Dưới ~100 RPS, một instance Postgres với replication đồng bộ là strong, nhanh, và đơn giản - không trade-off nào áp dụng. Cả thảo luận CAP/PACELC tồn tại vì ở quy mô lớn bạn không giữ được sự đơn giản đó.

Sai lầm thiết kế khó nhất là với tay sang Cassandra hoặc DynamoDB cho service 50 RPS. Thuế eventual consistency (replication lag, idempotency key, đọc cũ) tốn nhiều code hơn vấn đề throughput không tồn tại. Ở lại Postgres cho đến khi estimate QPS từ chương 2 nói khác đi.

Đi tiếp đâu từ đây?

Bạn đã nắm bộ từ vựng nền tảng - throughput/latency/QPS, ước lượng nhanh, CAP/PACELC. Chương kế tiếp: Redis caching trong .NET, khối xây dựng đầu tiên nằm giữa lưu trữ strong và đọc nhanh. Sau đó các chương building block lắp ghép tự do trên nền tảng này.

Câu hỏi thường gặp

Vì sao CAP cứ bị trích sai?
Vì phát biểu gốc 2000 ngắn và khiêu khích - 'chọn 2 trong CAP'. Hệ thật không chọn; partition hiếm, nên phần lớn thời gian bạn có cả ba. Phát biểu trung thực là PACELC: khi partition, chọn A hoặc C; ngược lại, chọn latency hoặc consistency. Câu này nắm trade-off hằng ngày, không chỉ ca biên.
Postgres là CP hay AP?
Postgres một node là CA theo nghĩa lỏng - không partition vì là một máy. Có sync standby là CP - primary từ chối write nếu standby không tới được. Replication async thì gần AP cho read nhưng write vẫn phải qua primary. Nhãn phụ thuộc vào cấu hình replication, không phải thương hiệu.
Khi nào eventual consistency là câu trả lời đúng?
Khi user không nhận thấy độ trễ. Counter like, follower count, view count, search index có thể trễ vài giây mà không ai phàn nàn. Mọi thứ liên quan đến tiền, bảo mật, hay nhân quả tuần tự (user reply post mình vừa đăng) cần strong consistency cho thao tác đó - dù các thao tác khác trên cùng dữ liệu có thể eventual.
Triển khai strong consistency trong EF Core ra sao?
Dùng một database primary, transaction serializable, và pessimistic hoặc optimistic concurrency. [ConcurrencyCheck]RowVersion của EF Core cho optimistic locking phát hiện lost update. Cho invariant đa dòng, bọc trong IsolationLevel.Serializable. Chi phí hiệu năng có thật - đo trước - nhưng đó chính là 'strong'.