Cruxly

Cruxly › System design › Principles of Distributed Design

Boundaries Are Where Guarantees Die

at every hop, name the guarantee you just lost

Open the interactive lesson

The principle: inside a single process you get ordering, atomicity and immediate consistency almost for free. Every boundary you cross — process, network, service, datacentre — silently removes some of them. At each hop, name which guarantee you just lost, because the bug you are about to write lives exactly there.

Most distributed systems bugs are not exotic. They are a developer reasoning with single-process intuitions about code that crossed a boundary where those intuitions stopped holding.

What each boundary actually takes away

BoundaryGuarantee lost
Function → another function, same processEssentially none
Process → process (same machine)Shared memory; failure is now partial
Machine → machine (network)Ordering, bounded latency, reliable delivery
Service → serviceAtomicity across the two; independent deployment/versioning
Region → regionLow latency; and consistency becomes an explicit, expensive choice
// Looks like two statements. Is not two statements.
orderService.createOrder(order);      // succeeds
paymentService.charge(order.total);   // times out — did it charge?

Designing across the boundary instead of pretending it isn't there

Three mechanisms recover most of what the boundary took, each recovering something different.

:::key Why "just use a distributed transaction" is the wrong instinct Two-phase commit does exist and does provide atomicity — by holding locks across services for the duration and blocking if the coordinator dies mid-protocol. Across independent services and third-party APIs (your order service and a payment provider), that availability cost is almost always unacceptable, which is why the industry standardised on idempotency plus reconciliation there. Saying this out loud, with the reason, is a much stronger answer than either using 2PC uncritically or dismissing it without knowing why.

Inside a distributed database, though, 2PC is standard. Spanner and CockroachDB run two-phase commit across shards, but each participant is itself a consensus-replicated group, so a coordinator crash doesn't block anything — another replica takes over the coordinator's role. The problem isn't 2PC itself; it's 2PC across components that can't make themselves highly available. :::

Ordering, versioning, and the boundaries people forget

Encoding formats and schema evolution

The versioning diagram above says "tolerate unknown fields" as if every format made that easy. It does not. Whether v1 can read v2's data is decided by the encoding, and you pick the encoding long before you ever deploy twice.

:::key Verified November 2026 Confluent Schema Registry compatibility modes: BACKWARD (the default) means consumers on the new schema can read data written with the old one, so upgrade consumers before producers. FORWARD means consumers on the old schema can read data written with the new one, so upgrade producers first. FULL means both, which lets producers and consumers upgrade in any order, and only adding or removing optional fields qualifies. The TRANSITIVE variants check against every earlier version, not just the last. NONE disables the check. The default is non-transitive, which matters if old data or old consumers live for months. :::

The rolling deploy is where this stops being theory. While the rollout runs, old and new code are both writing, and anything they write (rows, queue messages, cache entries) may be read later by either. You need both directions at once: new code reading old data (backward) and old code reading new data (forward). That is the FULL case, and it is why the safe recipe is the same in every format: add only optional fields with defaults, never repurpose or renumber, remove in a later release after every writer stopped using the field, and keep the removed number reserved.

Further reading: Martin Kleppmann, Designing Data-Intensive Applications, on encoding and evolution. Primary sources: the Protocol Buffers language guide (protobuf.dev), the Avro specification (avro.apache.org) and the Confluent Schema Registry compatibility documentation.

How to say it in the interview

"The moment this crosses a service boundary I lose atomicity between the two writes, so I want to be explicit about what happens if the first succeeds and the second fails — because that is not a rare case, it is a case that will happen thousands of times a day at this volume."

Checklist

Practice this interactively
Share this lessonWhatsAppLinkedInX

Related lessons