Section 4: Four Verbs, Carefully Priced - API Design (with Pushback)
Executive Summary: This section designs the contract the 64-node fleet from Section 3 exposes to its tenants — and the surface is deliberately tiny: get, put, delete, and a conditional put. The 401 substance lives beneath the signatures: what a successful put actually promises (two of three replicas, not "everywhere, forever"), why a timeout is indeterminate rather than a failure, why retries must be idempotent and never compensating, and why compare-and-swap is a separate endpoint carrying a 3–4× latency price instead of the default. Two interviewer pushbacks land here, and both are answered the same way: every guarantee has a price — name it, and let the tenant choose.
The fleet exists. Sixty-four nodes, sized in Section 3 against three constraints that happened to agree. Now the checkout team opens your API docs, and here is the uncomfortable truth of platform engineering: those docs are the entire product. Your tenants will never see your ring, your quorums, or your storage engine — they will see four function signatures and the promises attached to them. A weak candidate rushes past this section to get to consistent hashing. A Staff candidate slows down, because an API for infrastructure is not a list of endpoints; it is a set of semantics under failure, and every word of it is a commitment you will keep at 2 a.m.
The Surface: Small on Purpose
| Call | Signature | Semantics |
|---|---|---|
| Get | get(key, [consistency: R1|R2]) |
Returns value + version; R2 default, R1 for tenants who accept staleness for speed |
| Put | put(key, value, [ttl]) |
Acks after 2 of 3 replicas fsync the WAL; returns the new version |
| Delete | delete(key) |
A write in disguise (a tombstone — Section 9 explains why) |
| Conditional put | put_if(key, value, expected_version) |
Atomic compare-and-swap; separate endpoint, ~3–4× put latency |
| Batch get | batch_get(keys[]) |
Fan-out convenience; per-key results, no atomicity claim |
Three things to say aloud as you write this on the board. First, every response carries a version — a (timestamp, node-id) pair today. The checkout team doesn't need it for a blind overwrite of cart:8842, but the version is what makes conditional writes, idempotent retries, and conflict reasoning possible at all; omitting it welds shut doors you'll want open by Section 9. Second, the consistency parameter on get is not clutter — it is the tunable-consistency promise from Section 2 made concrete at the call site. Third, notice what is absent: no scan, no multi-key transaction, no "list keys." Those were non-goals with named doors in Section 12, and an API that quietly includes them has silently un-made every scoping decision you negotiated in Section 1.
Sign in to continue reading
The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.
Sign in to read the full lesson