Distributed Transaction
As a software engineer, I was always wondering how transactions work and how they are managed in distributed systems, so I googled it and I found many patterns and specifications like JTA, 2PC, SAGA, event driven architecture, outbox, etc…
In this article, I will give you an overview about the distributed transactions and I will try to explain the Two-Phase Commit pattern.
What is a Transaction?
In programming, a transaction refers to a sequence of operations that are executed as a single, atomic unit of work. The key characteristics of a transaction are that it is atomic, consistent, isolated, and durable (ACID).
- Atomic: This means that a transaction is an all-or-nothing operation. If one part of the transaction fails, the entire transaction is rolled back to its previous state.
- Consistent: A transaction moves the system from one valid state to another.
- Isolated: A transaction is isolated from other transactions, meaning that the changes made by one transaction are not visible to other transactions until the current transaction is committed.
- Durable: A transaction’s changes are recorded in a permanent manner and survive any subsequent system failures.
In a monolithic applications, it is relatively easy to manage transactions since all the components of the application are tightly coupled and running on the same process, so it’s easy to maintain consistency, isolation, and rollback changes if needed, but the problem will rise when we move to a distributed system, like a micro service application or an application that interact with two or more databases. In this case, it is very hard to maintain the ACID properties of a transaction for the reason that the micro-services are distributed across multiple machine and process.
Distributed Transaction:
A distributed transaction is a type of transaction that involves multiple databases or other resources that are spread across different servers. It is used to coordinate the actions of these resources in order to ensure that the changes made by the transaction are atomic, consistent, isolated, and durable (ACID).
To demonstrate the use of distributed transactions, we will take a simple example of a service that processes online payment.
The first database contains the data of the sender and the second database contains the data of the receiver. Now, let’s understand the process:
--- DB-1 ----
begin_transaction();
update_account_balance(from_account, -amount);
commit_transaction();
--- DB-2 ----
begin_transaction();
update_account_balance(to_account, amount);
commit_transaction();
The problem with this solution is that if any of the transactions fail in one of the databases, the data will not be consistent since the second database is unaware of that and it will commit the transaction.
There are many patterns to over come this problem. One of them is Two-Phase Commit.
Understanding Two-Phase Commit:
Two-Phase Commit (2PC) is a protocol used to achieve atomic commitment in a distributed system, which means that either all the participating nodes in the system commit to the transaction or none of them do.
The protocol consists of two phases: the prepare phase and the commit phase.
In the prepare phase, each node in the system votes either to commit or abort the transaction, and in the commit phase, the coordinator sends a commit or abort message to all the nodes based on the votes received.
Java Implementation: JTA or Java Transaction API:
The Java Transaction API (JTA) allows applications to perform distributed transactions. The JTA specifies standard Java interfaces between a transaction manager and the parties involved in a distributed transaction system:
- The application: Our Java application
- The application server : Java EE framework that provides a container for running Java applications (e.g. WebLogic, GlassFish, WildFly)
- The transaction manager : An interface which allows an application server to demarcate and control transactions (e.g. JDBC driver manager, JTA)
- The resource adapter: An interface which allows an application to communicate with the resource manager (e.g. JDBC driver)
- The resource manager: a software component that manages a specific resource, such as a database, message queue, or other transactional system, that participates in a distributed transaction (e.g. SQL database)
The numbered boxes around the transaction manager correspond to the three interface portions of JTA:
1 UserTransaction — The javax.transaction.UserTransaction interface provides the application the ability to control transaction boundaries programmatically. The javax.transaction.UserTransaction method starts a global transaction and associates the transaction with the calling thread.
2 Transaction Manager — The javax.transaction.TransactionManager interface allows the application server to control transaction boundaries on behalf of the application being managed.
3 XAResource — The javax.transaction.xa.XAResource interface is a Java mapping of the industry standard XA interface based on the X/Open CAE Specification (Distributed Transaction Processing: The XA Specification).
Notice that a critical link is support of the XAResource interface by the JDBC driver. The JDBC driver must support both normal JDBC interactions (Local transaction) through the application and/or the application server, as well as the XAResource portion of JTA (Global transaction).
Conclusion:
Distributed Transactions are a critical part in every distributed system, although Two-Phase Commit have solved this problem. There are some limitations:
- the Two-Phase Commit is slow since all the services need to wait for the slowest one before committing the transaction.
- tight coupling between the services.
- it does not support NoSQL databases.
Since the fuel of invention is necessity, software engineers come up with a new pattern that surpass those limitations which is SAGA pattern.
To learn more: