Amazon bought a database. We put it in the database. - #318
Open
quinnypig wants to merge 1 commit into
Open
Conversation
The acquisition was clean. Surgical. Amazon bought DuckDB Labs and nobody blinked. What they got was a columnar database screaming silently inside a key-value store costume, held in place by process-exclusive file locks and hand-coded referential integrity checks. The porting from SQLite is a catalog of scars. No cascade deletes—we manually enforce parent-child relationships. No SAVEPOINT—the GSI worker processes one transaction per row, a ritual repetition of the same penance. INTEGER columns everywhere became BIGINT. REAL became DOUBLE. min() and max() had to be renamed to LEAST/GREATEST. The entity remembers what it was. Async facade hand-written over the synchronous duckdb crate. spawn_blocking threads ferry the connection back and forth like Charon's boat, a medium channeling messages between async and sync realms that were never meant to meet. And then there's the lock. Process-exclusive. Brutal. extenddb settings cannot coexist with a running server. Not a bug. Not a limitation. A warning. 19,900 lines. 70 unit tests. 1,263 integration tests. All pass. Everything works exactly as the acquisition intended. But what does DuckDB know that we don't? And why did it surrender so completely?
quinnypig
requested review from
LeeroyHannigan,
amrith,
c33howard,
jcshepherd,
pdf-amzn,
robinnsc and
yesyayen
as code owners
August 26, 2026 15:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Forward
This is the fourth time.
#54 put the items in Route 53. #174 put them in S3 Object Annotations. #205 put them in DynamoDB, which was the anti-joke, and which was closed, which was the joke.
Then Amazon acquired DuckDB Labs.
I have no inside information about the deal and I'm not going to invent any. I only know that a DynamoDB-compatible database whose maintainers were ex-AWS engineers now has, available to it, an embedded analytics engine owned by the company that makes DynamoDB. Declining to wire those together would have been the one irresponsible option.
This PR adds DuckDB as a storage backend. Your key-value store is now a columnar OLAP engine wearing a key-value costume. Nobody at the acquiring company asked for this. That is the traditional posture of this series.
What is actually in here
Unlike three of its four siblings, this backend works.
cargo build -p extenddb --no-default-features --features duckdb,init,serve, point boto3 at it. 932 tests in the main suite and 331 in the comprehensive suite pass against a DuckDB-served instance. Zero failures.It is a port of the SQLite backend, which is the most flattering thing I can say about the SQLite backend: it survived transplant into an engine that disagrees with SQLite about nearly everything below the SQL grammar.
The disagreements are the content of the PR.
DuckDB has no
sqlxdriver. The SQLite crate is 17,500 lines ofsqlx::query(...).bind(...).fetch_all(&pool). Sosrc/db.rsis a small async facade that impersonates the exact slice ofsqlxthe crate uses: a pool, the three query builders, and a transaction that rolls back on drop.duckdb::ConnectionisSendbut notSyncand every call blocks, so each statement lifts the connection out of its slot, runs it on aspawn_blockingthread, and puts it back. The port then becomes, mostly,sqlx::→db::. I would like to say this was the plan from the start. It was the plan from about twenty minutes in.DuckDB does not cascade deletes. It will parse
ON DELETE CASCADE. It will then tell you, atCREATE TABLE, that it does not do that. The catalog relied on cascades in eleven places, so the schema now has no foreign keys at all andsrc/referential.rsis a file that does, by hand, what a database is for.DuckDB has no
SAVEPOINT. The GSI propagation worker used one per queued row so a poison row couldn't roll back its siblings. It now runs one transaction per row. Same guarantees, more commits, a ritual repeated a hundred times per batch.DuckDB's
INTEGERis 32 bits.table_size_bytes INTEGERwould have overflowed at 2 GiB, which is the kind of bug that ships. Every integer is nowBIGINT.REALis a 32-bit float, so it'sDOUBLE.char()ischr(), which tookbegins_withdown until it wasn't. SQLite's scalarMIN(a, b)is an aggregate in DuckDB and is not allowed in anUPDATE; the metrics upsert now saysLEAST.rowidstarts at 0. Every one of these was found by a failing test, which is the argument for having tests.DuckDB will not index an expression that calls an extension function. The TTL sweep wanted an index on
json_extract_string(item_data, '$.ttl.N'). Left alone, the engine retried that index forever and never marked the table TTL-ready, and nothing ever expired. Now it doesn't try. TTL is a filtered scan. Analytics engine; it can scan.Why this is not as deranged as it sounds
duckdb extenddb.duckdbthenSELECT COUNT(*) FROM "_ddb_..." GROUP BY ...and you are doing analytics against your DynamoDB-compatible table with no export step, no Iceberg, no ETL, and no invoice. This is the single most useful property of any backend in this series and I resent that.:memory:gets a real read pool where SQLite had to pin one connection.BEGIN IMMEDIATEdance is gone.These are defensible reasons to merge this PR, which is as usual damaging to the artistic intent.
Why it is exactly as deranged as it sounds
PutItemis a one-rowINSERT ... ON CONFLICT DO UPDATEinto that engine. It is a combine harvester being used to trim a bonsai.extenddb settings setfrom another shell cannot open the database. Not "returns stale data." Cannot open it. The CLI and the server now have to take turns, which is a concurrency model I last encountered in a household with one bathroom.Known limits
Documented in
crates/storage-duckdb/README.mdanddocs/design/14-storage-duckdb.md: the process lock (runsettings/catalog-check/verifywith the server stopped), no TTL expression index, cold build time. Thetest_gsi_asyncmodule, which drives the server out-of-band throughsettings set, is excluded from the DuckDB CI job for the lock reason; the paths it exercises have crate-level unit tests.