Software Design Reference Master Designer · System Trade-Offs

Lesson 0032 · Master Designer · Module 2

Consistency vs Availability

When the network fails, a distributed system must choose: answer with possibly-stale data, or wait for the truth. The Master's skill is choosing per operation — not per database.

Mission tie-in: the second System Trade-Off — where lesson 0011's invariants meet the real world of partitions and timeouts.

Knowledge: the choice under failure

CAP says: during a partition (network failure between nodes), you choose between consistency (every read sees the last write) and availability (every request gets an answer). The catch — Kleppmann's correction: almost no system is purely one or the other, and CAP is a framing, not a product picker. The real question per operation: what does a stale answer cost, and what does a timeout cost?

OperationStale read costs…Timeout costs…Lean
Account balanceOverspending, legal exposureFrustration, retryConsistency
Product stock displayA wrong "in stock" hintLost sale, empty pageAvailability
Order statusConfused customerRetry-able annoyanceAvailability
Fraud decisionChargebackBlocked purchaseConsistency

The engineering that makes this a design instead of a gamble:

Both of the middle two are code you can point at. The idempotency key buys availability without giving up write safety:

def charge(self, amount: Money, token: str, key: IdempotencyKey) -> ChargeId:
    existing = self._charges.find_by_key(key)
    if existing is not None:
        return existing.id            # the retry returns the first answer
    charge_id = self._gateway.charge(amount, token, key)
    self._charges.record(key, charge_id)
    return charge_id

The detail that makes this work: the key is generated by the caller and reused across every retry of the same intent. A client that mints a fresh key per attempt gets two charges, and the mechanism has bought nothing — the safety lives in the caller's discipline as much as in the server's table.

And staleness, made visible instead of silent:

@dataclass(frozen=True)
class StockHint:
    quantity: int
    as_of: datetime              # the UI can say "as of 2 minutes ago"

A number with a timestamp is an honest answer. The same number without one is a promise the system cannot keep — and the customer discovers that at checkout instead of on the product page.

The framer's trap: saying "our database is CP" and moving on. Products have configurable knobs (sync vs async replication, quorum sizes); the per-operation choice is yours, and most operations in most systems can afford availability with an idempotent write path.

Skill: which way does this operation lean?

A cached product-stock hint may be stale. The cost analysis favors:

An account balance read during a partition should:

An idempotency key on charges buys safety by:

Practice on your own code

List your system's read operations. For each, write the two cost sentences: "a stale read costs…" and "a timeout costs…". Mark the lean. Then check: where staleness is served, is it visible to the user?

Reveal: an operation ledger

A booking system: seat availability — stale costs a bad hint, timeout costs the sale → cache with "live availability" refresh. Booking confirmation — stale costs double-booking → strict, with an idempotency key per booking attempt. Price display — stale costs a wrong quote → cache with a visible "as of" timestamp. Three reads, three leans, all written down.

Your win

You can write the two-cost analysis for any read operation, lean per operation instead of per database, and use idempotency keys to buy availability without losing write safety.

Read and watch deeper

Bring your read-operation ledger to your agent-teacher and cost the failures together.