Tran Manh Tuan

Payments chạy bất đồng bộ, vì sao ledger vẫn cần strong consistency?

Một payment pipeline event-driven có lý do để tồn tại: tách service, hấp thụ burst và cho phép phần việc nặng tiếp tục chạy mà không giữ user request quá lâu. Nhưng financial state nằm ở cuối pipeline đó không thể được xem như một cache eventual consistency.

Gulfstream, nền tảng payments của Uber, là ví dụ rõ về ranh giới này. Phần lớn money movement đi qua stateless microservices và Apache Kafka theo hướng bất đồng bộ, trong khi balance của từng ledger entity vẫn được giữ strong consistency. Uber cho biết Gulfstream đã tăng từ mức hỗ trợ 50 tỷ USD lên 217 tỷ USD annualized gross bookings, xử lý tổng money movement gần gấp đôi con số đó và duy trì ledger balance cho hơn 1,2 tỷ entities. Gross bookings không phải doanh thu thuần; đây là giá trị giao dịch được quy năm đi qua nền tảng.

Câu hỏi thiết kế không phải là chọn async hay consistency. Câu hỏi là phần nào được phép bất đồng bộ và tại đâu hệ thống phải xác lập một financial state duy nhất, đúng đắn.

Money Order bất biến là điểm bắt đầu

Gulfstream biểu diễn một commerce event bằng Money Order: tập hợp các debit và credit giữa từ hai entities trở lên. Một chuyến đi, đơn giao đồ ăn, freight shipment hay nạp stored value đều có thể được mô hình hóa bằng biểu diễn tổng quát này.

Quy tắc quan trọng là immutability. Sau khi Money Order được ghi, hệ thống không update trực tiếp. Nếu cước thay đổi hoặc có món hàng bị thiếu, nền tảng ghi thêm một Money Order bổ sung để phản ánh adjustment.

Lựa chọn này giải quyết một vấn đề audit phổ biến. Một record bị ghi đè khiến người kiểm tra không biết hệ thống từng tin điều gì và tại sao giá trị đó thay đổi. Một record bù trừ giữ lại cả quyết định gốc lẫn correction, nên financial history vẫn kiểm tra được thay vì xóa dấu vết của sai lệch.

Double-entry là invariant, không phải bước kiểm sau cùng

Mọi Money Order áp dụng double-entry bookkeeping: mỗi credit có debit tương ứng. Uber chạy pre-commit validation để kiểm tra tổng các entries bằng 0. Theo mô hình này, hệ thống không thể tự tạo ra hoặc làm biến mất tiền.

Zero-sum là điều kiện cần nhưng có phạm vi giới hạn. Nó chứng minh entries cân bằng về mặt toán học; nó không tự chứng minh mọi business input, fare rule hay payment instruction đều chính xác.

Uber sau đó đưa kỷ luật này lên upstream qua Entity Fares. Bằng cách ghi rõ fare cho từng entity tham gia và enforce zero-sum ngay tại điểm tính toán, cấu trúc sai có thể bị chặn trước khi đến Payments Platform. Đây là một pattern hữu ích: enforce invariant tại tầng sớm nhất còn đủ dữ kiện để kiểm tra, rồi validate lại ở financial boundary.

Ba data model tách intent, state và history

Nền tảng tách ba mối quan tâm thường bị gộp nhầm vào một mutable record:

  • Money Order mô hình hóa một money movement.
  • Ledger biểu diễn entity thực tế như spender, earner hay business, cùng một hoặc nhiều account balance.
  • Entity changelog ghi tuần tự các balance update, tạo audit trail và dữ kiện để dựng lại ledger từ đầu.

Trong thiết kế ban đầu, Uber đặt mọi sub-ledger accounts của một entity vào một DynamoDB row. Entity điển hình có kích thước khoảng 1 KB. Việc gom account của một entity vào cùng row là cơ chế dùng để bảo vệ balance bằng strong consistency. Những sub-ledger account có balance bằng 0 được prune để row của entity tồn tại lâu không phình vô hạn.

Changelog tạo ra khác biệt quan trọng. Current balance cần cho quyết định vận hành, nhưng chưa đủ cho điều tra hoặc recovery. Lịch sử tuần tự cho phép hệ thống dựng lại cách state hiện tại đã được hình thành.

Async ở bên ngoài, strong consistency tại ledger boundary

Gulfstream dùng các stateless microservices độc lập cho từng stage như Money Order creation, processing, collection và disbursement. Apache Kafka là messaging bus nền tảng. Money Order Store và Entity Balance Store là hai source of truth dùng chung, và async path này xử lý phần lớn money traffic của Uber.

Với những flow diễn ra khi user đang chờ, Uber dùng Cadence để điều phối workflow đồng bộ mà không thay thế async backbone.

Điều đó không có nghĩa toàn pipeline phải globally serialized. Nó có nghĩa balance write của ledger entity phải đúng khi có concurrent update. Một event đến muộn tại notification service có thể chấp nhận được. Hai mutation đồng thời làm balance không còn phản ánh valid order hoặc valid order sequence thì không thể chấp nhận.

Kiến trúc payments ở hai region, trong đó payment service cục bộ ghi order và changelog có async replication, còn entity read/write được route về West region.
Order và changelog replicate bất đồng bộ, còn entity read/write được route về một region để duy trì consistent update path cho ledger.

Sơ đồ cho thấy ranh giới này. Cả hai region đều có thể chạy Payment Platform, Order Service và các gateway. Order và changelog replicate bất đồng bộ. Tuy nhiên, entity reads/writes được route về West region. Route đó tạo một write path duy nhất cho entity balance, thay vì để các writer ở nhiều region cạnh tranh cập nhật cùng financial state.

Hot entity cho thấy cái giá của correctness

Strong consistency trở nên khó khi rất nhiều transaction lặp lại update vào một entity. Uber gọi đây là hot entity problem. Sự tăng trưởng của Uber for Business và Uber Direct tạo ra entities có transaction traffic tập trung cao. Serialized write vào một strongly consistent ledger row bắt đầu thành throughput constraint.

Uber triển khai specialized serialized batch-write mechanism và báo cáo throughput ledger mutations tăng 10x cho nhóm high-traffic entities. Phạm vi của con số này phải được giữ đúng: nó là throughput của ledger mutations cho nhóm hot entities, không phải throughput toàn platform hay latency improvement cho mọi payment flow.

Bài học không phải là bỏ serialization. Làm vậy sẽ làm yếu điều kiện đang bảo vệ balance. Việc cần làm là tối ưu mechanism serialization nhưng vẫn giữ thứ tự mutations mà ledger yêu cầu.

Storage tiến hóa theo audit requirements

Uber ban đầu dùng DynamoDB cho orders và changelogs. Khi yêu cầu audit, regulatory và compliance tăng lên, Payments cùng Storage Platform xây dựng LedgerStore, storage layer dành riêng cho auditability và tamper-evident record-keeping.

Một lựa chọn transactional database không chỉ là quyết định về throughput. Financial system còn cần lời giải thích bền vững cho những gì đã xảy ra, khả năng phát hiện modification bất thường và cách vận hành để reconstruct state.

Generic abstraction ngăn payment logic bị phân mảnh

Gulfstream chủ động không gắn Money Order core với một line of business. Collection, disbursement và refund là platform concepts thay vì khái niệm chỉ dành cho Rides. Cùng primitive đó đã hỗ trợ các mảng mới như Freight và Ads mà gần như không cần đổi core payment logic.

Payment integration boundary dùng các thao tác tổng quát như charge, disburse và refund. Logic riêng của từng instrument nằm ở integration implementation, không nằm ở platform core. Nhờ đó card, local wallet và stored-value product có thể đi qua cùng một interface mà không nhét provider-specific rule vào ledger.

Các ledger primitive này cũng hỗ trợ stored value như Uber Cash và B2B billing states gồm unbilled, billed, settled. Uber gọi cách đóng gói đó là multi-tenant Ledger-as-a-Service.

Điều backend engineer có thể mang sang hệ thống khác

Gulfstream gợi ý một số design check cho distributed payment system:

  • Tách immutable movement record, current balance và history thành các concern riêng.
  • Validate double-entry và zero-sum trước khi commit financial state; đưa check lên upstream tại nơi có thể chứng minh calculation sớm nhất.
  • Correction nên là additive. Không overwrite historical financial decision chỉ để current state trông sạch hơn.
  • Xác định consistency boundary tường minh. Async replication có thể phù hợp để phân phối history, còn concurrent balance mutation cần một correct ordering path.
  • Dự liệu hot key. Strongly consistent record có thể thành throughput bottleneck; cần đo và tối ưu serialization mechanism thay vì âm thầm làm yếu correctness.
  • Giữ đủ ordered history để reconstruct state và phục vụ audit.

Bài Uber là architectural overview, không phải protocol specification. Nó không cung cấp đủ chi tiết để kết luận về ACID trên toàn pipeline, exactly-once semantics hoặc thuật toán của serialized batch-write. Đây là các implementation question quan trọng, nhưng không nên suy diễn từ mô tả platform ở mức cao.

Kết luận

Async processing và strong consistency không phải hai architecture style đối nghịch. Chúng thực hiện những công việc khác nhau. Async service làm payment platform lớn trở nên elastic và loosely coupled. Strongly consistent ledger giữ financial state duy nhất mà downstream service, user và auditor phải tin cậy.

Gulfstream kết hợp hai điều này bằng Money Order bất biến, zero-sum trước commit, changelog tuần tự và consistent write path cho balance mutation. Kết luận không phải là “đồng bộ hóa mọi thứ”. Quy tắc hẹp hơn và hữu ích hơn là: để outer pipeline scale bất đồng bộ, nhưng làm ledger boundary đủ nghiêm ngặt để tiền không bao giờ phụ thuộc vào ambiguous state.

Nguồn và đọc thêm