Distributed Consensus and Saga Patterns
ACID vs. Distribution
The All-or-Nothing Promise
In our last sections, we learned that networks are unreliable. Messages get lost, and you can never be 100% sure a remote computer got your command. This creates a huge problem for our ERP system. When a user creates a purchase order, several things need to happen inside our PostgreSQL database at once: an order record is created, inventory is reduced, and an entry is made in the accounting ledger. We need all of these steps to succeed together, or none of them should happen at all. It would be a disaster if the inventory was reduced, but the order record failed to save.
This is where the database's guarantee comes in. A single, reliable database like PostgreSQL makes a powerful promise for transactions that happen within its own walls.
This promise is called , and it's a set of four properties that ensure transactions are processed reliably. Think of it as a contract between you and your database.
ACID
other
A set of properties for database transactions: Atomicity (all or nothing), Consistency (data is always valid), Isolation (transactions don't interfere), and Durability (changes are permanent).
Atomicity: The transaction is an indivisible unit. It's all or nothing. If any part of the transaction fails, the entire thing is rolled back as if it never happened.
Consistency: The transaction brings the database from one valid state to another. It never leaves the database in a half-finished, corrupted state. If our ERP requires that an order can't be created for an out-of-stock item, the database will enforce that rule.
Isolation: Multiple transactions occurring at the same time won't interfere with each other. It's as if each transaction gets its own private copy of the database, makes its changes, and then merges them back in an orderly fashion. One user's purchase order process won't see the messy, intermediate steps of another user's inventory update.
Durability: Once a transaction has been committed, it will remain so, even in the event of a power loss, crash, or error. The change is permanently written to disk.
When Systems Must Talk
ACID works beautifully as long as everything happens inside one database. But what happens when our ERP needs to coordinate with other, separate systems? When a purchase order is approved, we don't just update our own database. We might also need to:
- Tell a separate, external Shipping Service to prepare a shipment.
- Charge the customer's credit card via a third-party Bank API.
Now, our single business action, "process the order," spans three different systems: our ERP, the shipping service, and the bank. This is called a distributed transaction. Our PostgreSQL database can't offer an ACID guarantee for the actions of external services. It has no control over them.
So how do we get these independent systems to agree to either all succeed or all fail together? The classic attempt at a solution is a protocol called Two-Phase Commit (2PC).
Two-Phase Commit
The idea behind 2PC is to introduce a that manages the transaction across all the participating systems. The process works in two distinct phases.
Phase 1: The Prepare Phase (The Vote) The coordinator sends a "prepare" message to all participants (our ERP, the shipping service, the bank). It's essentially asking, "Can you promise me you can do this task?" Each participant checks if it can perform its action. For example, the shipping service checks if it has a valid address, and the bank checks if the credit card is valid. If a participant is ready, it locks the resources it needs and sends a "yes" vote back to the coordinator. It does not finalize the action yet.
Phase 2: The Commit Phase (The Decision) If the coordinator receives a "yes" from every single participant, it sends a "commit" message to all of them. Upon receiving this, each participant finalizes its action and releases its locks. If even one participant votes "no" or fails to respond, the coordinator sends an "abort" message to everyone, and they all roll back their changes.
This sounds great in theory. It seems to bring atomicity to a distributed system. But it has one massive, fatal flaw: the blocking problem.
What happens if the coordinator crashes after all participants have voted "yes" but before it can send the final "commit" command? All the participants are now stuck. They've locked their resources and are waiting for a decision that will never come. They are blocked.
This means the shipping system can't process other orders, and the bank can't handle other payments, because the resources they need are locked up indefinitely. The entire system grinds to a halt, sacrificing availability for consistency. This trade-off is often too costly for modern applications that need to be highly available.
This blocking problem forces us to look for a different way to manage consistency across systems, one that doesn't require all services to lock-step in perfect, fragile agreement.
In the context of ACID, which property ensures that a transaction is an 'all or nothing' operation, where if any part fails, the entire operation is rolled back?
A user's action in an ERP system requires updating the local database, contacting an external shipping service, and processing a payment through a third-party API. What is this type of operation called?