2PC — Two phase commit — [Notes]
--
· Advantages of 2PC
· Disadvantages of 2PC
· 2PC is not an option
2PC is widely used in database systems. For some situations, you can use 2PC for microservices. 2PC has two phases:
- Prepare phase (Phase 1): The controlling node asks all of the participating nodes if they are ready to commit. The participating nodes respond with yes or no.
- Commit phase (Phase 2): If all of the nodes replied in the affirmative, then the controlling node asks them to commit. Even if one node replies in the negative, the controlling node asks them to roll back.
- Even though 2PC can help provide transaction management in a distributed system, it also becomes the single point of failure as the onus of a transaction falls onto the coordinator.
- With the number of phases, the overall performance is also impacted. Because of the chattiness of the coordinator, the whole system is bound by the slowest resources since any ready node has to wait for confirmation from a slower node.
- Also, typical implementations of such a coordinator are synchronous in nature, which can lead to a reduced throughput in the future.
The point of 2PC is it performs tentative operations first and if all succeed the commits are confirmed.
This is possible to implement in a reactive way using asynchronous messaging. Of course, we need to have at-least-once delivery guarantee and therefore we need to have some kind of persistent storage and also we have to take care of deduplication or we have to use idempotent messages and so on. But the main problems are, it does not scale well, it’s slow and has certain deadlock issues. As I said, it was possible to implement this in Akka, but there is a better way.
In the example above, when a user sends an Order request, the Middleware or Coordinator will first create a global transaction (Create Transaction) with all the context information. It will then tell CustomerMicroservice to prepare for updating a customer table with the created transaction. The CustomerMicroservice will then check, for example, if the customer has enough funds to proceed with the transaction. Once CustomerMicroservice is OK to perform the change, it will lock down the object from further changes and tell the Middleware that it is prepared. The same thing happens while creating the order in the OrderMicroservice. Once the Middleware has confirmed all microservices are ready to apply their changes, it will then ask them to apply their changes by requesting a commit with the transaction. Once both phases are completed, records will be unlocked.
In the above process, if we get any failure at any point of time, the Middleware will abort the transactions and start the rollback process. Here is a diagram of a 2PC rollback for the customer order example:
In the above sequence diagram, the Customer microservice fails to prepare for some reason, but the Order microservice prepares to create the order. The Middleware will ask to abort all the prepared transactions i.e roll back changes and unlock the records.
Advantages of 2PC
- More atomic(A)
- Data consistency(C)
- Read-write Isolation(I)
- Durability(D)
Disadvantages of 2PC
- Request is Synchronous (Blocking)
- Lock records/object until transaction completes.
- Possibility of Deadlock between transactions.
- If one microservice becomes unavailable in the commit phase, there is no mechanism to roll back the other transaction.
- Other services must wait until the slowest service finishes its confirmation. The resources used by the services are locked until the whole transaction is complete.
- Due to their dependence on the transaction coordinator, two-phase commits are slow by design. This can cause scalability issues, particularly in a microservices-based application and in a roll-back scenario involving many services.