Transactions, Consistency, and the Myth of Distributed ACID

By NTh Hai - 9 min read

“Just wrap it in a transaction” works beautifully inside one database. Stretch that instinct across microservices, message queues, and multiple data stores, and you invent latency, locking nightmares, or false confidence.

Solution architecture needs a clear story about what is atomic, what can lag, and how you recover.

What ACID Actually Gives You

Within a single transactional store:

  • Atomicity — all statements in the transaction commit or none do
  • Consistency — constraints and invariants the DB enforces hold after commit
  • Isolation — concurrent transactions do not see each other’s dirty middle states (to a chosen isolation level)
  • Durability — committed data survives crashes (within the durability model you configured)

Isolation levels matter. READ COMMITTED vs. REPEATABLE READ vs. SERIALIZABLE change anomaly risk and contention. Architects who ignore isolation get “impossible” bugs under load.

The Boundary Problem

A business action often spans:

  • Order DB
  • Payment provider API
  • Email/notification service
  • Search index

There is no single ACID umbrella over all of them. Even two-phase commit across systems is rare in modern web architectures because of availability and operational cost.

So you design explicit boundaries:

  1. Commit the source of truth in one transaction where possible
  2. Reliably emit a signal that other systems can process
  3. Make downstream steps idempotent and retry-safe
  4. Build compensating actions when a later step fails

Outbox and Inbox Patterns

A proven approach:

Transactional outbox. In the same DB transaction as the business write, insert an “outbox” row describing the event. A publisher reads outbox rows and pushes to the bus. Downstream consumers process idempotently (inbox / dedupe keys).

This beats “commit then publish” where a crash between the two loses the message, and beats “publish then commit” where consumers see events for data that rolled back.

Saga Thinking Without the Ceremony

A saga is a sequence of local transactions with compensations. You do not need a heavy framework to use the idea:

  • Reserve inventory → charge payment → confirm order
  • If charge fails → release reservation
  • If confirm fails after charge → refund + release

Document the happy path and the compensations. Timeout and “unknown” payment states need explicit handling — the hardest bugs live there.

When Strong Consistency Is Non-Negotiable

Keep these inside one transactional boundary when you can:

  • Ledger and balance updates
  • Unique constraints that prevent double booking
  • Permission grants that gate immediate access
  • Inventory decrement that must not go negative (or a deliberate oversell policy)

If the product requires two of these across services simultaneously, reconsider the service cut — or accept a single-writer data ownership model.

Eventual Consistency Is a Product Decision

“Eventual” is not an excuse for chaos. Define:

  • What users may see stale (follower counts vs. bank balances)
  • How stale is OK (seconds, minutes)
  • How conflicts resolve (merge, last-write-wins, manual)

UI copy and loading states are part of the architecture. Showing “payment processing…” is often better than pretending a distributed flow is instantaneous and atomic.

Closing Thought

Distributed ACID is mostly a myth you should not bet the company on. Reliable systems combine tight local transactions, reliable messaging, idempotent consumers, and honest UX around lag. Design the consistency story on purpose — it will design itself poorly if you do not.