On a single PostgreSQL server, transferring money between two accounts is simple. You wrap the two operations in a BEGIN TRANSACTION and COMMIT block. The database guarantees ACID semantics: either both the debit and credit succeed, or both fail (Atomicity). You never end up in a state where money is created or destroyed.
But what happens when you shard your database? What if Alice's account lives on Server A in New York, and Bob's account lives on Server B in London?
You write an API endpoint that executes this code:
If step 1 succeeds, but step 2 fails (because Server B crashed, or the network between New York and London went down), you have a massive problem. Alice lost $100, but Bob never received it. The money was literally destroyed.
Click "Transfer $100" to simulate a successful transfer across the network. Then, click "Transfer & Crash Network" to watch how a partial failure destroys data integrity.
In a distributed system, you cannot simply execute independent queries on different servers and hope they both succeed. If there's a failure in the middle of the workflow, you are left with an inconsistent system state.
We need an algorithm that can guarantee Atomicity (all or nothing) across multiple physical machines. The classic solution to this is the Two-Phase Commit (2PC) protocol, which we will explore next.