Why is 2-phase commit not suitable for a microservices architecture?

The "We can not" here really means "It's a bad idea, and I don't want to, and if I admit the possibility then I might not be able to convince you not to insist".

Of course you can implement 2-phase commit across microservices, but:

  • 2-phase commit requires a significant development effort in every service that can participate in a transaction,
  • It causes a lot of contention between clients that grows with the communications latency between servers; and
  • All the services involved have to agree on a lot of protocol, configuration, deployment, and other details that determine how the 2-phase commit will actually work.

These problems are hard enough to manage among a few closely-coupled services on co-located servers with dedicated networks. In the more heterogeneous environments, with more servers, and higher latencies, that characterize microservice deployments, it becomes much, much harder.


Whole idea of microservices is loosely coupled and independent services. Since 2pc means we have 2 phase to commit the transaction. controlling node will drive the transaction and all other nodes first respond they are ready and in second phase they all commit or roll back depending on phase one.

what happens if controlling node is down? what happens when any of the other nodes are down? because of this limitation your whole transaction can't get through. In distributed transactions your nodes can be in different data centers or regions. the slowest node to respond will keep the other nodes in the waiting state while they could move on. so atomicity is hampering performance.

You can't scale the system and whole point the services should be independent and scaleable is lost.

2pc is not the wrong answer but in most cases we consider eventual consistency. if you have system that requires strong consistency then 2pc may be the option.


The main reason for avoiding 2-phase commit is, the transaction co-ordinator is a kind of dictator as it tells all other nodes what to do. Usually the transaction co-ordinator is embedded in the application server. The problem happens when after the 1st phase or prepare phase the transaction co-ordinator or the application server goes down. Now, the participating nodes don't know what to do. They cannot commit because they don't know if others have replied to the co-ordinator with a "no" and they cannot rollback because others might have said a "yes" to the co-ordinator. So, until the co-ordinator comes back after 15 minutes (say) and completes the 2nd phase, the participating data stores will remain in a locked state. This inhibits scalability and performance. Worse things happen when the transaction log of the co-ordinator gets corrupted after the 1st phase. In that case, the data stores remain in the locked state forever. Even restarting the processes won't help. The only solution is to manually check the data to ensure consistancy and then remove the locks. These things usually happen in a high pressure situation and therefore it's definitely a huge operational overhead. Hence the traditional 2-phase commit is not a good solution.

However, it should be noted here that some of the modern systems like Kafka have also implemented a 2-phase commit. But this is different from the traditional solution in that here every broker can be a co-ordinator and thus the Kafka's leader election algorithm and the replication model alleviate the issues mentioned in the traditional model.


Some things to note and also give some background:

  1. In most scenarios microservices interact via HTTP (a stateless protocol) and as a result global/ XA transactions are just not applicable/ possible.
  2. Exactly once semantics are not possible and you should go for "at least once". This means all services should be idempotent.
  3. One good example of why is not possible of achieving "exactly once" semantics in such a setup is that http connections very frequently are lost on the way back to the client. This means that via a POST the state of the server has changed, while the client receives a timeout error.
  4. Inside the boundaries of a microservices you can use them just fine. As you mentioned Kafka you can quite easily consume (from 1 topic) and produce (to 1 or more topics) a single atomic/ all or nothing operation (exactly once semantics).
  5. But if you want global and long running transactions among microservices that interact via http the only practical option (you might see global transaction via http if you google, but for a production system just ignore them), is to design for eventual consistency. In brief this means, retry for ever for recoverable errors (this is a whole chapter in itself) and expose compensating endpoints or produce compensating events that will eventually amend non-recoverable errors. Check out the sagas pattern. Narayana Transaction Manager has good Sagas support and a good products comparison.
  6. Check out the related microservices patterns that offer an alternative to XA transactions (you might see this as global transactions or 2 phase commit/ 2PC) like Transactional Outbox or Event Sourcing that offer nice "at least once semantics".
  7. Distributed systems are very complicated and you should have a reason to go for such a solution. If you go distributed, operations that your monolith can safely delegate to your transaction manager, will have to be dealt by the developer/ architect :-).
  8. Also, the majority of non SQL databases/ systems do not support XA transactions (i.e. global transactions) at all, as they slow processing dramatically.