Handling distributed transactions in the microservices world
- In the microservices context, a distributed transaction is distributed to multiple services, called in a sequence, to complete the single transaction.
- The ACID (atomicity, consistency, isolation, durability) test is challenging for distributed transactions with microservices because you need to be able to roll back the entire sequence if a microservice later in the sequence returns a failure, but atomicity implies the transaction should complete in entirety or fail in entirety. Also, when handling concurrent requests, you might have an object from one microservice being persisted into the DB even as the second reads that same object. This challenges both consistency and isolation.
- One solution is a two-phase commit. This method splits transactions into a prepare and a commit phase, with a transaction coordinator to maintain the lifecycle of the transaction: first, all microservices involved will prepare for a commit and notify the coordinator when ready. Then the coordinator issues either a commit or rollback command to all the microservices.
- It guarantees atomicity, allows read/write isolation (no changes to objects until the commit), and makes a synchronous call to notify the client of success or failure. However, it is a slow method and also locks database rows which can become a bottleneck and can allow two transactions to reach a deadlock.
- Another solution is to use asynchronous local transactions for related microservices, which communicate through an event bus, guided by a separate choreographer system that listens for success and failures from the bus and chases a rollback up the sequence with a ‘compensating transaction’. This makes each microservice atomic for its transaction, hence the operation is faster, no database locks are needed and the system is highly scalable. However, it lacks read isolation. With many microservices, this is also harder to debug and maintain.
Full post here, 7 mins read