Giao dịch phân tán và nhất quán xuyên service
Bài toán
Với một DB quan hệ, BEGIN … COMMIT lo hết. Khi dữ liệu bị chia (shard, tách service, thêm Redis bên cạnh Postgres), một thao tác nghiệp vụ chạm nhiều nơi, và câu hỏi khó nhất trở thành "timeout rồi, việc đó đã xảy ra chưa?". Có ba hướng:
- Giữ những thứ phải nhất quán với nhau ở cùng một DB để vẫn dùng được transaction cục bộ.
- Hạ tầng lo giao dịch xuyên DB (2PC, các protocol như GRIT).
- Ứng dụng lo: saga, TCC, bù trừ, idempotency, đối soát; chấp nhận nhất quán cuối cùng.
Diễn tiến theo thời gian
-
2013 · Jepsen: Row isolation của Cassandra chỉ là xác suất: trùng timestamp (độ phân giải mili giây trong stack) thì ghép cell từ hai ghi, khoảng 1/200 dòng hỏng. Jepsen 2013
-
2015 · Wix: dùng MySQL như kho key/value, không dùng transaction. Họ thay bằng thứ tự ghi có chủ đích: ghi site trước, ghi route sau. Route chưa có thì site chưa ai tìm thấy, nên trạng thái luôn nhất quán, cùng lắm có bản ghi mồ côi. Wix 2015
-
2015 · Riot Games: bỏ khoá và 2PC khi chuyển chat sang Riak (AP); hai server cùng sửa danh sách bạn tạo sibling, và lần đọc sau gộp mutation log dạng CRDT một cách xác định (hai lời mời đồng thời đều còn). Riot Games 2015
-
2016 · Ele.me: khi tách giao dịch thuận/nghịch, họ chưa tách hẳn trạng thái khỏi sự kiện nghiệp vụ phía trên. Theo tác giả, chính điều đó gây nhiều vấn đề ở ranh giới và distributed transaction về sau. Ele.me 2020
-
2016→2026 · Uber: nền tảng thanh toán Gulfstream: money order bất biến (điều chỉnh là money order mới), kiểm tổng bằng không trước commit cùng bút toán kép, đẩy dần nguyên tắc này lên hệ thượng nguồn (Entity Fares); microservice stateless qua Kafka, luồng người dùng đang chờ dùng Cadence; sau đó LedgerStore cho kiểm toán và ghi lô cho thực thể nóng. Hơn 1,2 tỉ thực thể sổ cái (bài 8/2026). Uber 2026
-
2016 · Square: Cash App không chấp nhận transaction xuyên shard trong đường xử lý tiền: entity group (khách hàng) luôn cùng shard và ACID bên trong, ghi xuyên nhóm phải có biện pháp riêng. Square 2018
-
2018→2020 · Clear Street: bỏ 2PC (theo họ chậm và phức tạp), dùng transactional outbox + CDC (Debezium) để publish lên Kafka nhất quán với giao dịch Postgres. Clear Street 2020
-
2018 · Dropbox: transaction chéo shard trong Edgestore bằng 2PC sửa đổi: transaction record bền bên ngoài làm điểm commit tường minh (leader chết thì abort), staging copy-on-write (khuếch đại ghi giảm tới 95%), đọc chỉ cần kiểm bảng staging nhỏ. Kiểm bằng harness dựng đồ thị thứ tự từ lịch sử (nhiều năm CPU) và chạy giao thức bóng trên production. Theo bài, khả thi vì chỉ 5–10% transaction chéo shard và cache đỡ hơn 95% đọc. Dropbox 2018
-
2019 · eBay (GRIT): hướng "hạ tầng lo": kết hợp OCC, 2PC và deterministic DB.
- thực thi lạc quan ghi lại read-set/write-set;
- mỗi DB kiểm tra xung đột rồi một bộ điều phối toàn cục ra quyết định; commit "logic" khi write-set đã ghi bền vào log;
- áp xuống storage bất đồng bộ.
Không khoá bi quan; hợp với giao dịch ít xung đột. eBay 2019
-
2019 · Mercari (Merpay): hướng "ứng dụng lo" cho thanh toán qua nhiều service, kể cả service bên ngoài:
- chốt ID giao dịch thành một pha riêng trước mọi thao tác;
- chia giao dịch thành các pha, coordinator là state machine (retry hoặc rollback theo chuyển trạng thái);
- mọi API phía dưới phải nhận idempotency key;
- bù trừ kiểu TCC (giữ chỗ → thực hiện);
- đối soát đồng bộ và bất đồng bộ;
- fault injection bắt buộc cho mọi luồng.
Không dùng XA/2PC vì lo ngại hiệu năng, khả dụng và không áp dụng được cho bên ngoài. Mercari 2019
-
2019 · Walmart: DR cho microservice gặp đúng định lý BAC: không thể vừa availability vừa consistency khi backup cả hệ; chọn về trạng thái nhất quán gần nhất bằng Entity Mutation Log + phát lại + consumer idempotent, chấp nhận mất một ít dữ liệu. Walmart 2019
-
2019 · Airbnb: Orpheus, thư viện idempotency cho thanh toán SOA (không phải service, để giữ latency thấp): request chia Pre-RPC/RPC/Post-RPC, không gọi mạng trong transaction và không đụng DB trong pha RPC; lỗi chia retryable/non-retryable; lease có hạn trên idempotency key; client giữ key và retry có jitter. Theo Airbnb, nhất quán thanh toán đạt five nines khi khối lượng tăng gấp đôi. Airbnb 2019
-
2019 · Netflix: Giữ Elasticsearch và kho dẫn xuất đồng bộ với MySQL bằng CDC (DBLog/Delta) thay cho dual write hay transaction phân tán. Netflix 2019
-
2020→2021 · Notion: shard theo workspace và đưa mọi bảng nối tới
blockvào cùng shard, chính để xoá block và cập nhật comment vẫn nằm trong một transaction. Notion 2021 -
2020 · Wix: trong luồng Payments → Checkout → Delivery/Inventory, Checkout phát trùng là giao hai lần; họ dùng Kafka transaction gói event mới cùng offset vừa đọc và idempotent producer ở đầu luồng, để cặp consume-produce exactly-once. Chỉ phủ phần nằm trong Kafka. Wix 2020
-
2020 · Jepsen (PostgreSQL): kiểm theo tính chất bằng Elle: "repeatable read" là snapshot isolation (có G2-item), "serializable" không serializable vì bug phát hiện xung đột có từ 2011 khi có insert đồng thời; bộ test chọn tay của PostgreSQL và Hermitage bỏ sót suốt 9 năm. Jepsen 2020
-
2020→2022 · Etsy: Chọn
transaction_mode=singlecủa Vitess: transaction chạm nhiều shard thì lỗi rõ ràng, thay vìmulti(có thể commit một phần) haytwopc(thử nghiệm, chậm); rà tay mọi chỗ dùng transaction trước cutover. Etsy 2023 -
2022→2024 · Figma: sharding tự xây cố ý không hỗ trợ transaction nguyên tử xuyên shard. Bảng cùng khoá shard được gom thành "colo" để vẫn join và transaction được bên trong. Transaction xuyên shard cho nghiệp vụ sống còn vẫn là việc còn lại. Figma 2024
-
2022 · Zalando: Outbox không cần bảng riêng: DynamoDB Streams (CDC) làm outbox, Lambda phát event lên Nakadi, hết retry thì vào SQS DLQ và CronJob gửi lại; request chỉ còn phụ thuộc DynamoDB, đổi lại không giữ thứ tự event. Trả lời một phần câu hỏi outbox ở production. Zalando 2022
-
2022 · Jepsen: "Exactly-once" của Kafka/Redpanda không kiểm được vì client xử lý lại message ngay trong một transaction; cách dùng an toàn mà Redpanda gợi ý không có trong tài liệu công khai. Jepsen 2022
-
2023 · Deno: Deno KV chỉ có giao dịch không tương tác: điều kiện (
versionstamp), lệnh ghi và mutation không xung đột gửi một lần, xong trong 1–2 round-trip. Sequencer cấp số thứ tự tăng đơn điệu, evaluator dựng đồ thị phụ thuộc theo lô và thực thi suy đoán trên kết quả chưa bền, writer ghi xuống FoundationDB; hiệu ứng chỉ lộ ra sau commit. Deno 2023 -
2023 · Grammarly: hệ billing/payment nhiều PSP dùng transactional outbox, optimistic locking, idempotent message handling và retry thay cho giao dịch xuyên service (chỉ liệt kê, không có chi tiết). Grammarly 2023
-
2023 · Uber: lô cập nhật tài khoản: nhiều processor ở nhiều zone tranh một lô, chỉ một ghi thành công nhờ optimistic locking; audit log tạm được lưu trước rồi mới thành vĩnh viễn sau khi ghi; MicroUAC dưới 100 byte vẫn đủ cho idempotency; kiểm bằng shadow traffic so số dư cuối giữa hệ cũ và mới, khuếch đại shadow 10–15 lần để tìm trần (bài 3/2026). Uber 2026
-
2023 · Jepsen (MySQL 8.0.34): Repeatable Read mặc định của InnoDB cho phép G2-item, G-single, lost update và đọc không lặp lại được ngay trên một node khoẻ (một lần ghi làm giao dịch thấy thay đổi của giao dịch khác); theo Jepsen nó không phải Snapshot Isolation, nên mẫu ORM đọc-sửa-ghi có thể mất thay đổi đã commit. RDS "Serializable" đọc từ replica vi phạm serializability khi
replica_preserve_commit_order=OFF. Khuyến nghị: dùng Serializable hoặcSELECT ... FOR UPDATE. Jepsen 2023 -
2024 · Trigger.dev: queue trên Redis, trạng thái trên Postgres. Khi Postgres quá tải, hai bên lệch nhau và một số queue kẹt, phải can thiệp tay. Họ cần bảo đảm giao dịch mạnh hơn giữa hai kho. Trigger.dev 2024
-
2025→2026 · Shopify: chiều ngược lại: reservation trên Redis và sổ tồn kho trên MySQL là hai hệ, nên bước claim không nguyên tử. Họ đưa reservation về MySQL (mỗi đơn vị hàng một dòng +
SKIP LOCKED) để có ACID cho cả reserve lẫn claim, bỏ hẳn một lớp bug. Shopify 2026 -
2025 · Mercari: quản lý đơn hàng mới cho Global App: lifecycle trạng thái theo từng mặt hàng; đặt đơn là saga có điều phối (kiểm tra → trừ coupon → thanh toán Merpay → ghi đơn → yêu cầu đối tác mua hộ) với giao dịch bù khi lỗi, chạy trên engine nội bộ Magician của Merpay; chọn saga thay TCC vì TCC bắt mọi module viết Try/Confirm/Cancel, chọn orchestration thay choreography để có điểm nhìn toàn cục. Mercari 2025
-
2025 · Jepsen: "Repeatable Read" (Snapshot Isolation) không giữ được khi đọc từ reader endpoint của RDS Multi-AZ; có thể chỉ là Parallel Snapshot Isolation. Jepsen 2025
-
2026 · Stripe: mọi dịch chuyển giá trị của credit đi qua Originated Money Transfer, vòng đời ba pha
submitted → prepared → disbursedtrong sổ cái; vòng đời credit là máy trạng thái có papertrail và duyệt hai người. Kết hợp nguy hiểm (tài khoản logic với tài khoản tiền thật) bị chặn ở tầng nền tảng chứ không trông vào caller. Stripe 2026 -
2026 · Render (nhà cung cấp, bài khái niệm): agent gọi tool có tác dụng phụ trên queue at-least-once cần cả idempotency (một bước chạy hai lần) lẫn saga (một lần chạy dừng giữa chừng: mỗi hành động có hành động bù, chạy ngược thứ tự). Hành động bù cũng phải idempotent, cũng có thể lỗi nên cần retry có trần, dead-letter và lối thoát bằng tay; bù không phải mặc định khi thành công một phần là chấp nhận được. Workflow engine là chỗ tự nhiên cho saga vì đã ghi bước nào xong. Render 2026
-
2026 · Optiver: tránh dual-write bằng cách chỉ ghi Postgres và phát thay đổi từ WAL (logical decoding); dùng thông tin hiển thị của transaction (
pg_current_snapshot()trong Repeatable Read) để biết thay đổi nào đã nằm trong snapshot client nhận. Optiver 2026
Các lựa chọn và đánh đổi
| Hướng | Ví dụ | Được | Mất |
|---|---|---|---|
| Gom dữ liệu liên quan vào cùng DB hoặc shard | Notion (bảng nối tới block), Figma (colo), Shopify (reservation về MySQL) | Transaction cục bộ, đơn giản | Ràng buộc cách chia; một kho có thể thành nút cổ chai |
| Thứ tự ghi có chủ đích | Wix (site trước, route sau) | Không cần transaction | Có bản ghi mồ côi; chỉ hợp ca đơn giản |
| Hạ tầng điều phối (2PC, GRIT) | eBay | Ứng dụng viết như có ACID | Cần tầng điều phối chung; OCC kém khi xung đột nhiều |
| Saga / TCC + idempotency + đối soát | Mercari | Chạy được với service ngoài, ưu tiên khả dụng | Code khó đọc (state machine), phải test lỗi và đối soát liên tục |
| Hai kho, chấp nhận lệch rồi phát hiện và bù | Trigger.dev (Redis + Postgres) | Throughput của kho chuyên dụng | Lệch khi sự cố, cần can thiệp |
Quy tắc rút ra
- Cách rẻ nhất để có giao dịch phân tán là không cần nó: chọn khoá shard hoặc ranh giới service để những thứ phải nhất quán nằm cùng một nơi (Notion, Figma, Shopify). Xem Sharding và tách database.
- Mọi lời gọi có thể bị retry phải idempotent, và ID giao dịch phải được chốt trước khi gọi ra ngoài (Mercari).
- Nhất quán cuối cùng phải được kiểm chứng, bằng đối soát và fault injection, không chỉ bằng thiết kế (Mercari).
- Tách trạng thái khỏi sự kiện để ranh giới giữa các hệ rõ ràng (Ele.me).
- Hai kho cho cùng một trạng thái (cache/queue cộng DB) là một giao dịch phân tán trá hình; hoặc gom về một chỗ (Shopify), hoặc chuẩn bị cơ chế phát hiện lệch (Trigger.dev). Xem Chọn database theo workload.
- Idempotency và bù là hai việc khác nhau: một cho bước chạy hai lần, một cho lần chạy dừng giữa chừng; hành động bù cũng cần idempotent, retry có trần và lối thoát bằng tay. Render 2026 (nhà cung cấp, bài khái niệm)
- Thêm ngữ nghĩa giao dịch mạnh vào hệ đang chạy: chứng minh bằng kiểm tính chất trên lịch sử (đồ thị thứ tự), rồi chạy giao thức mới ở chế độ bóng trên traffic thật, với đường ghi cũ vẫn là nguồn sự thật. Mức cô lập của DB phải đọc theo định nghĩa hình thức, không theo tên: "repeatable read" của PostgreSQL là snapshot isolation, và "serializable" từng có bug 9 năm mà test chọn tay bỏ sót. Dropbox 2018 Jepsen 2020
- Tên mức isolation không cho biết ngữ nghĩa: "Repeatable Read" của PostgreSQL là Snapshot Isolation, của MySQL InnoDB thì yếu hơn và cho phép lost update; read-modify-write trong giao dịch MySQL mặc định cần
SELECT ... FOR UPDATEhoặc Serializable. Tham số mặc định của dịch vụ managed (RDS) có thể khác upstream và phá serializability trên replica (Jepsen 2023). Jepsen 2023 Jepsen 2020
Câu hỏi mở
- 2PC trong production: đã có Dropbox 2018 (Edgestore, 10 triệu request/giây) (Dropbox 2018), nhưng chỉ 5–10% transaction là chéo shard và cache đỡ hơn 95% đọc; chưa có nguồn kể 2PC khi phần lớn transaction chéo shard, hay XA giữa các DB khác loại. GRIT chỉ có bài giới thiệu protocol.
- Saga hay outbox pattern: Clear Street (2018–2020) kể outbox + Debezium ở production nhưng chưa kể sự cố (Clear Street 2020); Zalando 2022 là nguồn production thứ hai: DynamoDB Streams làm outbox, Lambda + DLQ, chấp nhận lệch thứ tự event, nhưng cũng không có số liệu sự cố (Zalando 2022); saga vẫn chưa có nguồn. Grammarly 2023 xác nhận dùng transactional outbox trong hệ billing nhưng chỉ liệt kê, không kể cách triển khai hay sự cố (Grammarly 2023).
- Liên hệ: Monolith và microservices — đi và quay lại (tách service sinh ra bài toán này), Migration không downtime (ghi kép cũng là một dạng giao dịch hai kho).
- Liên hệ: Event-driven, message queue và Kafka (event và CDC thay giao dịch xuyên service; outbox: Grammarly 2023 nhắc tên, Clear Street 2020 kể cách làm).
Được nhắc tới trong
- Jepsen 2013
- Riot Games 2015
- Wix 2015
- Dropbox 2018
- Square 2018
- Airbnb 2019
- Walmart 2019
- Netflix 2019
- eBay 2019
- Mercari 2019
- Ele.me 2020
- Wix 2020
- Clear Street 2020
- Jepsen 2020
- Notion 2021
- Jepsen 2022
- Zalando 2022
- Grammarly 2023
- Deno 2023
- Jepsen 2023
- Etsy 2023
- Figma 2024
- Trigger.dev 2024
- TigerBeetle 2024
- Jepsen 2025
- Mercari 2025
- Uber 2026
- Stripe 2026
- Render 2026
- Optiver 2026
- +2 nữa