We have DynamoDB at home. - #205
Open
quinnypig wants to merge 5 commits into
Open
Conversation
Three layers down from Route 53 and S3 Annotations, the pattern crystallizes: run ExtendDB yourself, pointed at actual DynamoDB, and legally claim you're on-prem. The execs stop asking. They've won. We've won. The encoding is the identity function because DynamoDB is already a KV store. There is nothing to do, and that is the punchline. The data plane forwards; the catalog plane delegates to Postgres because DynamoDB has opinions about what a database should be, and "relational IAM catalog" is not one of them. This design spec approves the hybrid composition: data → real DynamoDB via aws-sdk-dynamodb, catalog/auth → reused Postgres CatalogStore. Scope: six trait engines, account-namespaced table naming, condition/update/query/scan/transact expression translation, error mapping for wire-protocol fidelity. Streams and Backups are honest stubs in v1 (DynamoDB owns those narratives). Configuration lives in [storage.dynamodb]; recursion (pointing endpoint_url at another ExtendDB or DynamoDB Local) is documented as a feature. Approach B was always the right one. The punchline was never about pure DynamoDB; it was about the deadpan documentation of how little there is to do.
…, expressions (27 tests) The five lowest layers of the "DynamoDB at home" backend are now unit-tested and passing. They form the bedrock: account-namespaced table naming (athome_prefix), the encoding layer that translates ExtendDB's AttributeValue to the SDK's (structurally the identity function; the file exists only because Rust has two enums), SDK error→StorageError mapping, and DynamoDB expression rendering (Expr/KeyCondition/UpdateAction ASTs become expression strings + attribute maps). This is where the pattern lives. The Postgres CatalogStore will never know that the items it catalogs are living in actual DynamoDB. The account-namespacing ensures physical table isolation (athome_123456789012_Orders stays separate from athome_999999999999_Orders), and the expression renderer ensures condition/update semantics round-trip exactly. The encoding is a technical joke that round-trips because of course it does. 27 tests passing. No engine wiring yet—that is phase 2. Right now, the logistics of misdirection are locked down. The execs believe their data is safely on-prem. Postgres believes it owns the catalog. DynamoDB is hosting the feast and asking no questions. Three separate truths, each true. The AWS SDK error mapping is where the conspiracy deepens: every failure mode from the wire becomes a StorageError, and nobody upstream knows where the truth lives.
…otstrapper The DynamoDB backend is now FULLY OPERATIONAL. All six trait implementations on DynamoEngine (TableEngine, DataEngine, MetadataEngine forwarding to real DynamoDB; StreamEngine + BackupEngine as honest stubs naming the actual API calls; WorkerStore). Bootstrapper delegates catalog operations to Postgres, OperationsEngine routes DDL catalog ops through the same channel. Small `as_any` hook added to shared StorageConfig trait. Hybrid ServerComponents factory wires data→DynamoEngine, catalog/auth→reused Postgres + builtin auth. All six inventory registrations complete. cargo build --workspace clean; 31 unit tests pass; clippy passes. The agencies SAID this backend couldn't exist without a full Postgres replacement. They were wrong. DynamoDB at home now runs the data plane while Postgres— relegated to catalog-only—does the metadata work nobody wanted anyway. Follow the money: why did they fight so hard for a monolithic architecture? Look at who profits from vendor lock-in. Look at the architecture reviews that mysteriously stopped requesting alternatives. The documentation said we needed two databases. The DOCUMENTATION WAS WRONG.
… sample config The third backend emerges from the shadows: data flows to real DynamoDB, catalog stays in PostgreSQL, the execs get to call it "on-prem" and everyone walks away satisfied. This is feature-complete. **Cargo feature wiring**: Add `dynamodb` feature to `extenddb-bin`, default disabled. Link it with `extern crate extenddb_storage_dynamodb` in main.rs — the linker would otherwise drop the backend's `inventory::submit!` registrations unnoticed. **Integration tests**: Live DynamoDB Local test suite (474 lines) covering: - table_create, table_describe, table_delete (table lifecycle) - put_item / get_item (CRUD) - update_item with conditions (conditional update) - delete_item (CRUD continuation) - query with begins_with (compound key search) - transact_get (multi-item atomic read) - transact_write with condition failure (pessimism that works) All tests skip gracefully if DDB_LOCAL_ENDPOINT is unset; CI stays green. **Sample config**: Uncommented `[storage.dynamodb]` block with endpoint_url (DynamoDB Local), region, table_prefix, catalog_connection_string. The joke is fully configured now. **Documentation**: Expanded differences-from-dynamodb.md with the full backend section: data plane forwarding, catalog delegation, table namespacing, TTL, tags, idempotency, streams (v1: not implemented), backups (v1: not implemented), return value behavior, GSI mutations (v1: not forwarded), index_info (not supported), and the delicious detail that endpoint_url can point at another ExtendDB endpoint — recursion is documented as a feature. **Rustfmt pass**: Line length violations cleaned across all modules. The encoding module is unchanged in logic; the near-identity property still holds. Three storage backends exist: PostgreSQL (the original), S3 Annotations (the satire), and now DynamoDB (the long con). Each one plays a role in the theatre. The Postgres catalog layer catalogs the DynamoDB tables and knows nothing of where the data lives — the account_id prefix ensures physical isolation. The execs stop asking questions about infrastructure because the data is technically on-prem, and the metadata audit trail is in a real database, and there is nowhere left to look. 31 unit tests pass (unchanged). 3 integration tests pass against live DynamoDB Local. Clippy is satisfied. The binary boots and logs "Found registered backend: dynamodb". The misdirection is complete. The feature flag is ready. The PR is ready. This closes the third backend.
…am handler changes The upstream interface layer made a move last week. Subtle. Surgical. 1. Bootstrapper now demands `generate_backend_config_section`. Configuration generation has been centralized. Who demanded this? A memo, unsigned. 2. ServerComponents took a hostage: raw `credential_store` instead of auth_provider. The binding-layer is consolidating control. Check line 61. 3. TableKeyInfo and TableDescription gained new fields. `base_key_schema`. `on_demand_throughput`. These fields emerged from the SDK response like they were always there. They were not always there. 4. Let-chains. The clippy lint forced nested ifs into guards. This is harmless, but why now? Why the enforcement push across the crate? 5. The smoking gun: `StorageConfig::as_trait` now returns `+ 'static`. This constraint was uncallable before. Factories couldn't downcast. Now they can. The factory signature says it too. Mutual assurance. Who ordered the `'static` binding? When did the trait object's lifetime become load-bearing? The comment says it's for downcasting. For *accessing as_any*. But why is as_any suddenly being called? What changed upstream? The tests pass. All 567. Fmt is clean. Clippy is clean. But somewhere in this innocuous maintenance commit, something just shifted. The conformance is complete. The binding is mutual. Who benefits from `'static`? Everyone downstream who wants to reach into an opaque `StorageConfig` and pull its true type out by its throat. Follow the downcasting.
quinnypig
requested review from
LeeroyHannigan,
amrith,
c33howard,
jcshepherd,
pdf-amzn and
yesyayen
as code owners
July 7, 2026 22:51
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 third time.
#54 argued that Route 53 is a database and proved it by force, packing base64 into 255-byte TXT strings until DNS confessed.
#174 argued that S3 Object Annotations was already a database, because AWS had built the Iceberg index, SQL query path, and change journal and merely declined to use the noun.
Neither merged. No review comment arrived, but I can reconstruct one from the shape of the silence:
Stop inventing storage engines and just use DynamoDB.
Done.
This PR adds a DynamoDB backend to the DynamoDB-compatible database. It stores its DynamoDB data in DynamoDB.
Run ExtendDB yourself, on infrastructure you administer, pointed at us-east-1. When the executive who read one cloud-repatriation post asks whether the database is in-house, you can truthfully say the database server is. No follow-up question they know how to ask will surface the rest.
We have DynamoDB at home.
A note from the intervening architecture committee
This PR waited long enough for 79 upstream commits to land. During that interval, upstream did not reject the premise. It did something more revealing: it redesigned the entire backend system.
Inventory registration is gone. The name-keyed registry that replaced it is also gone. The CLI moved into a backend-agnostic app crate. Feature flags were declared vestigial. The official architecture is now one backend per binary.
This is either excellent type-safe design or a containment protocol.
The backend complied. It now ships as a dedicated executable:
Runtime backend selection has been eliminated. To choose the wrong database, an operator must now install the wrong database server. This is progress.
The plugin-model refactor did not kill the backend. It gave the backend its own executable. The bit has incorporated.
The anti-joke
The meme format requires the thing at home to be worse.
Route 53 honored the format: items shredded across TXT records are worse.
S3 Object Annotations honored it: 1,000 items per table, with the query path billed by the scanned terabyte, is worse.
This backend breaks the format because the DynamoDB at home is DynamoDB.
The failure is clearest in
encoding.rs. Route 53 needed actual encoding because DNS was never intended to hold database items. S3 Annotations needed base64 chunking. DynamoDB already speaks the exact DynamoDB type system, so this backend's encoding is structurally the identity function.The file exists because ExtendDB's
AttributeValueand the AWS SDK'sAttributeValueare two Rust enums holding the same data. It is round-trip tested across every type anyway, because the identity function is the one function you cannot afford to get wrong.What is actually in here
Unlike its siblings, this backend works.
The new
extenddb-storage-dynamodbcrate implements the storage-engine trait surface:ClientRequestToken.DescribeTablefor upstream's new capacity-accounting cache, because the upstream optimization is also, unhelpfully, exactly a DynamoDB API.The bureaucracy database
The data plane maps naturally to DynamoDB. The management plane does not.
Accounts, users, roles, policies, settings, metrics, rate limits, encryption keys, and admin credentials are relational bureaucracy. Rebuilding them on DynamoDB would require months of responsible engineering and bury the punchline.
So this is a hybrid:
Your data is on-prem. The bureaucracy that proves it is on-prem still needs a real database.
The result is two databases in a trench coat standing in your data center, presenting a config file as identification.
Table namespacing
ExtendDB is multi-tenant; DynamoDB's table namespace is flat. Physical tables are named:
The default prefix is
athome_, producing names such asathome_123456789012_Orders.This is not merely namespacing. It is a statement of deployment intent embedded in every CloudTrail event.
Why this is not as deranged as it sounds
endpoint_urlat DynamoDB Local and ExtendDB becomes the TLS, mandatory SigV4, IAM, multi-account, rate-limiting front end DynamoDB Local never had.These are defensible reasons to merge this PR, which is frankly damaging to the artistic intent.
Why it is exactly as deranged as it sounds
I will not be taking questions about the unit economics.
Recursion is a feature
endpoint_urlmay point at DynamoDB Local. It may also point at another ExtendDB.There is no loop guard. Every layer adds TLS termination, SigV4 verification, IAM evaluation, and another opportunity to claim the request is closer to home.
Compliance calls this defense in depth. I call it on-prem twice.
With enough layers, the request eventually times out, at which point no data has left the perimeter because no data has arrived anywhere.
Honest acts of cowardice
Streams and backups are explicit v1 stubs. Every method errors with the DynamoDB API it should call:
DescribeStream,GetRecords,CreateBackup,RestoreTableFromBackup, and friends.A passthrough backend must reconcile ExtendDB streams with DynamoDB Streams. That deserves another PR, partly for architecture and mostly because a trilogy should not become a box set in one diff.
index_info_by_table_idis unsupported because DynamoDB cannot reverse a TableId into a name. Even DynamoDB has limits, and one is answering questions designed by ExtendDB.Enterprise software calls this a roadmap.
Testing
catalog 0.0.2 (dynamodb), which is correct and an alarming sentence.The branch is rebased onto current
upstream/main. The working tree is clean. The bit compiles.Conclusion
Route 53 was an argument.
S3 Object Annotations was an accusation.
This is compliance.
Merge it or do not. Either way, the data ends up in DynamoDB.