Skip to main content

Distributed Transactions with Seata

· 6 min read

In enterprise application development, as distributed frameworks have matured, production environments have come to hold many database instances. In the microservices world in particular, we usually give each business Service module its own DB storage node for its own data, and then deploy that node for high availability.

The transaction problem

When writing Service business logic, a single business operation often makes remote calls to other business modules, and the resulting data lands in different storage nodes. Ensuring the ACID properties of a transaction across multiple database instances is where we run into the distributed transaction problem.

What a distributed transaction has to accomplish boils down to two things:

  1. If every step in the business call chain succeeds, all the databases involved in the chain commit their transactions.

  2. If an exception is thrown anywhere in the call chain, all the databases involved in the chain roll back.

The roles in Seata

The Seata distributed transaction solution generally involves the following roles:

  • TC (Transaction Coordinator): coordinates the transaction.
  • TM (Transaction Manager): the transaction manager, integrated into the AP.
  • RM (Resource Manager): manages the resources handled by branch transactions; talks to the TC to register branch transactions and report their status, and drives branch transactions to commit or roll back.
  • AP (Application Program): the application that accesses the RM.

The TC coordinator component in the diagram must be deployed separately and joined to the registry of the whole microservice cluster. All the common registries are supported — Nacos, Zookeeper, Etcd, Eureka, Consul — and the AP needs the corresponding connection configuration. The TC module must be highly available: if it goes down, distributed transactions are off the table entirely.

Transaction Groups and High Availability

Rigid vs. flexible transactions

Rigid transactions: usually require no business-code changes, offer strong consistency with native rollback and isolation support, handle low concurrency, and suit short transactions. Corresponding solutions: the XA protocol (2PC, JTA, JTS) and 3PC.

Flexible transactions: require business-code changes, offer eventual consistency, need you to implement compensation and resource-locking interfaces, handle high concurrency, and suit long transactions. Corresponding solutions: TCC/FMT, Saga (state machine mode, AOP mode), local transactional messages, and message transactions (half messages).

So what counts as a long transaction versus a short one?

Long transaction: holds some resource (exclusive locks, gap locks, table locks, page locks) for an extended time, modifies a lot of data or runs slowly, and spans a long business flow. Other transactions accessing the resource are blocked waiting, which easily leads to deadlocks.

Short transaction: holds resources only briefly, modifies little data or completes quickly, spans a short business flow, and finishes fast, so the chance of deadlock is small.

The four transaction modes

Seata currently supports four modes for managing distributed transactions.

AT mode

AT mode is arguably Seata's signature mode. In essence it is an optimized 2PC — a form of two-phase commit. Using it requires creating an undolog table in the local database node. A DataSourceProxy intercepts the DML SQL statements the application executes, parses their semantics, converts them into query SQL, and stores an image of the data as it was before execution in the undolog table for use in phase-two rollback. All of these steps are handled by the proxy, so it achieves zero intrusion into business code.

But the approach is far from perfect, and the performance cost is considerable: each DML SQL statement incurs an extra round of semantic conversion and query execution, plus writing a data image into the undolog table; if a phase-two rollback happens, a local lock also has to be acquired before restoring the original data image. There is another fairly serious issue as well: if, at rollback time, another local transaction bypasses Seata's global transaction management (and its global lock) and directly modifies data covered by an image in the undolog, Seata will find during rollback that the image no longer matches the current local data, resulting in data loss — somewhat like the ABA problem in CAS.

TCC mode

TCC mode is comparatively simple — it too is 2PC two-phase commit. The difference from AT mode is that you implement the phase-one prepare, phase-two commit, and rollback logic yourself, and Seata's transaction manager invokes these implementations in turn.

Saga mode

A Saga consists of a series of local transactions. After each local transaction updates its database, it publishes a message or event that triggers the next local transaction in the Saga. If a local transaction fails because a business rule cannot be satisfied, the Saga runs compensation operations for all the transactions that had already committed successfully before the failure.

That makes Saga the most laborious mode to implement, but a Saga-based state machine gives you very flexible control over every step of the distributed transaction. For long-transaction scenarios you especially want that fine-grained control over every situation: you don't track the state of the whole distributed transaction — you just handle each event properly and keep your own methods idempotent. However, there is no notion of staged commit in its transactions; everything commits locally and directly. If other transactions read and modify that data mid-rollback, you get dirty reads and dirty writes. For that case, I think you'd still need to build your own txid scheme to guard against it.

XA mode

XA mode first requires the database to support the XA protocol. Its implementations include 2PC and 3PC, provided entirely by the database itself — the application doesn't need to do anything. It is fundamentally quite similar to AT mode; the difference is that during an XA transaction the database, for the sake of strong consistency, keeps holding data resources (exclusive locks, gap locks, table locks, page locks, depending on the data range). With long transactions under high concurrency, deadlocks arise very easily and database performance degrades quite badly. If everything is short transactions, this mode is usable.

Wrapping up

The essence of a distributed transaction is making a cross-database call chain commit together or roll back together. Seata productizes this through the division of labor among TC, TM, and RM, and offers four modes: AT, TCC, Saga, and XA. AT achieves zero intrusion via data images and fits most everyday scenarios; TCC and Saga hand the compensation logic to the business in exchange for higher concurrency and flexibility; XA relies on native database support and only suits short transactions. When choosing, first work out whether your business runs long or short transactions and whether it needs strong or eventual consistency — then match the mode accordingly.

COMMENTS