The full picture
- Ask the boundary question first and out loud: 'why are these two writes in different services?' If the answer is 'because we split by noun' or 'because a different team owns it', the fix is the service boundary, not a transaction protocol. An invariant that must hold atomically is the strongest possible signal that the data belongs to one aggregate — this is the single most valuable point you can make in a system-design interview on this topic.
- Consensus doesn't make atomic commit non-blocking — replication does. These are two different problems that both use quorums: consensus makes one group agree on a value; atomic commit makes several groups agree to commit together, and it still needs 2PC on top. What changes is that a participant is now a fault-tolerant group rather than a single machine, so 'the participant is unreachable' stops being a permanent state. Being able to separate those two ideas is what distinguishes this answer.
- The latency bill is unavoidable and worth quoting: a cross-shard transaction in one of these databases costs the 2PC round trips plus a Raft commit at every participant, so single-region cross-shard is roughly 2–4× a single-shard write, and cross-region is dominated by speed of light. That's exactly why they all give you tools — interleaved tables, locality-aware partitioning,
REGIONAL BY ROW— to keep transactions inside one shard or one region. The optimization is always to avoid distributing the transaction, not to make it faster. - Where atomicity is genuinely non-negotiable: double-entry accounting (debit and credit must land together or the ledger doesn't balance), the last unit of limited inventory, regulatory records, and uniqueness on something with legal weight. For these, either colocate the data so it's one local transaction, or accept a real distributed transaction and its cost. A saga here means someone will eventually reconcile by hand.
- Bounded-scope transactions are the pragmatic middle ground most teams should reach for: DynamoDB
TransactWriteItems(all-or-nothing across up to 100 items in one region), MongoDB multi-document transactions, Postgres inside one instance, or a Kafka transaction for consume→process→produce within Kafka. Each is atomic within a boundary the platform can enforce cheaply. Design so your invariants fit inside one of those boundaries and the whole question disappears.
| Situation | Reach for | Why |
|---|---|---|
| Two writes, one aggregate | one local transaction | the boundary was wrong — fix that, not the protocol |
| State change + event | transactional outbox | atomic locally; at-least-once downstream |
| Multi-step business flow | saga + compensations | no blocking; you own the isolation |
| Hold a claim while deciding | TCC / reservation with TTL | expiry replaces the compensation |
| Cross-shard invariant, one platform | 2PC over Raft groups (Spanner, CRDB, TiDB) | blocking bounded by leader election |
| Cross-org / third-party API | saga — nothing else exists | you cannot enlist someone else's system |