Transactions Without Reads on DocumentDB
"MongoDB-Compatible" Has Fine Print
Our services are written against the MongoDB driver, but in production they run on AWS DocumentDB — a "MongoDB-compatible" managed service. Compatible covers most of the surface. It does not cover all of it, and one gap bit us: DocumentDB could not perform read operations inside a transaction the way our code expected.
The component that broke was a changeset manager — the piece that applies a group of related changes as one atomic unit, which is exactly the kind of code you want to be boring and reliable.
The Failing Sequence
The original flow read naturally but hid a read-after-uncommitted-write:
// 1. insert the changeset object (inside the transaction)
// 2. execute the pending operations
// 3. update the object by query (the query runs OUTSIDE
// the transaction, cannot see the uncommitted insert,
// matches nothing — the operation fails) On plain MongoDB replica sets the read participated in the transaction and everything held together. On DocumentDB the same code was a reliable failure — an infrastructure constraint surfacing as an application bug.
Remove the Need, Don't Emulate the Capability
There were workaround-shaped options: split the transaction, add retries, read through a separate session. All of them keep the fragile shape and paper over it.
The fix was to invert the order so the read never has to happen:
// 1. construct the changeset object in memory
// 2. execute the operations against it
// 3. write the finished object ONCE, inside the transaction One write instead of insert-then-update means there is nothing uncommitted to read back. The transaction shrinks to a single atomic commit of finished state — which is also simply a better design, independent of DocumentDB.
Takeaways
- Test against the database you deploy on. "Compatible" services diverge exactly in the corners transactions live in.
- Prefer removing the need for a capability over emulating it. The workaround keeps the fragile shape; the restructure deletes it.
- Buffer in memory, commit finished state. Transactions that only ever write completed objects are immune to a whole family of isolation surprises.