Build1 publisher3 min readPublished
Object storage hands you two primitives to rebuild four database guarantees
Strong read-after-write consistency and conditional writes are real guarantees, but only two of the four database behaviours they stand in for have an enforcement point in the service, and the other two are convention.
The Engineer · Build desk

What happened
- A dev.to writeup on Ampbase and Tigris argues the object-storage-versus-database question is settled by listing the guarantees an application needs, the ones the service supplies, and the ones it must rebuild.
- It names the four behaviours teams usually reach for a database engine to get: uniqueness, transactions, indexes and history, none of which object storage supplies as a database feature.
- Amazon S3 documents strong read-after-write consistency for new objects, overwrites, deletes and listings, and dates that change to December 2020.
- Conditional writes express compare-and-swap as HTTP preconditions, with If-None-Match: * creating only when a key is absent and If-Match on an ETag replacing only the version that was read.
- The control plane described uses two bucket layers: a global directory bucket that records organizations, and a separate scoped bucket issued to each organization.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Uniqueness holds only while every writer uses conditional creation, so the guarantee lives in review of all write paths rather than in a constraint the store can enforce for you.
- capability Scoping credentials per customer bucket turns tenant isolation into an infrastructure boundary, removing the WHERE org_id = ? that an application developer can forget.
- cost Every genuinely new question about the data is priced as a hand-written backfill plus a new index, with correctness owned by application code instead of a query planner.
- decision For high-volume, frequently updated, cross-referenced state the audit answers itself, because the alternative to running a database is rebuilding a worse one.
The two primitives are not independent, and that matters before any contract gets built. Conditional writes are evaluated against the object's latest state within the bucket's consistency model [6]. The ETag you pass in `If-Match` came from a read. If that read can be stale, the application can misjudge uniqueness or compare-and-swap state [7]. Strong read-after-write is the substrate; the precondition is only the check that runs on top of it.
Now map four onto two. Uniqueness becomes conditional creation on a content-derived key [8]. Transaction-like mutation becomes a read, a pure computation, and a conditional replacement of exactly one object [9]. Both have an enforcement point, because the service rejects the request when the precondition fails. The other two do not. An index is a key designed around a frequent lookup [10], and history is an append-only key space whose order matches the question being asked [11]. Nothing in the store checks whether either is true, so half the audit is enforced by the API and half by discipline [1].
The boundaries are where this design earns or loses. A mutation retry has to be pure, because the computation runs again against a fresh read [13]. An index answers only the question it was planned for [14]. A transaction spanning two objects cannot be assembled from per-object preconditions at all [16]. And contention on a single key turns CAS retries into a possible livelock [17], which is the failure mode that will not appear in a demo with one writer.
The part I would keep is the key layout. Inside a customer bucket the dev.to account describes a membership key for point lookup, a mutable pointer to the active configuration, immutable version objects, and append-only event objects [20]. Version objects and events are never rewritten, so the audit property comes from the key space rather than from an `UPDATE` path [21]. The pointer is the one mutable thing: deploying overwrites it, rollback moves it to an older version, and the history survives because the version objects are retained [22].
None of it holds without the workload. In the Ampbase case, according to that writeup, the data partitioned cleanly per organization, writes were low volume and mostly uncontended, reads were point lookups on keys the application controlled, and the interesting history was append-only by nature [24]. Storage selection there followed the shape of the data and the questions asked of it [23], and the stated motivation was avoiding a database the system did not need [26]. What transfers from that is the checklist rather than the saving: name your four guarantees, and if two of them cannot be expressed as a key name or a precondition, keep the database.
What to watch
- Whether Tigris documents conditional-write and read-after-write semantics as precisely as the S3 documentation the writeup cites.