Disclosure. Dynoxide is maintained by the author of this board. Its score is produced by the same automated tests as every other engine, derived from the conformance suite's published results, and isn't adjusted by hand.
Other builds
Same project, built for a different place to run. Each is measured separately, because what a build implements can differ from what the project does.
How to run it
Every way Dynoxide is distributed, each linking to the project's own documentation for it. These are the project's claims, not the suite's measurements.
Needs: none - a single binary, with no runtime to install
Per-region divergence
The headline is 26 regions, measured against 26 of 33 observed regions, and scored in af-south-1. Real DynamoDB doesn't behave identically in every region, so a target can match some regions more closely than others. Each figure below is divergence in those regions, best first. eu-west-2 is marked as the historical baseline, and a region that couldn't be resolved this sweep is flagged rather than counted as a disagreement.
Feature support
Measured across the suite's tests for each feature. Compare with other emulators.
Core DynamoDB
- Vector search
- Dynoxide Vector search: supported (52 pass)
- GSI
- Dynoxide GSI: supported (176 pass)
- LSI
- Dynoxide LSI: supported (158 pass)
- PartiQL
- Dynoxide PartiQL: supported (210 pass)
- Transactions
- Dynoxide Transactions: supported (127 pass)
- Streams
- Dynoxide Streams: supported (18 pass)
- TTL
- Dynoxide TTL: supported (7 pass)
- Legacy params
- Dynoxide Legacy params: supported (49 pass)
Other AWS services
- Backups / PITR
- Dynoxide Backups / PITR: not supported (5 skip)
- Export / import
- Dynoxide Export / import: not supported (2 skip)
- Kinesis
- Dynoxide Kinesis: not supported (1 skip)
- Resource policies
- Dynoxide Resource policies: not supported (2 skip)
- Contributor Insights
- Dynoxide Contributor Insights: not supported (2 skip)
- Account API
- Dynoxide Account API: not supported (2 skip)
Fully supports
Operation areas this target implements completely - every test passes, nothing skipped. Areas it only partly gets wrong are in “Where it falls short” below; the ones it only partly attempts are in “What it doesn’t attempt”.
- batchGetItem tier 1
- batchWriteItem tier 1
- createTable tier 1
- deleteItem tier 1
- deleteTable tier 1
- describeTable tier 1
- getItem tier 1
- listTables tier 1
- putItem tier 1
- query tier 1
- scan tier 1
- updateItem tier 1
- updateTable tier 1
- partiql tier 2
- streams tier 2
- tags tier 2
- transactions tier 2
- ttl tier 2
- updateTable tier 2
- vectorSearch tier 2
- legacy-api tier 3
- limits tier 3
- validation-ordering tier 3
Divergence and coverage over time
Both are plotted because neither reads correctly alone. Divergence falls when a target stops attempting an operation it used to get wrong, so a fall in the first plot is only an improvement if the second one holds.
Divergence
Coverage
By operation
Every operation this target implements, with how much of it diverges and how much of it it covers, on the same two axes as the figures above. The counts are fails over the operation's whole size. full, partial, failing, unsupported. The emulator directory lists support across targets.
Tier 1 - Core
- batchGetItem 0/15 0.0% 100.0% batchGetItem: supported, diverges on 0.0% of it, covers 100.0% (15 pass)
- batchWriteItem 0/18 0.0% 100.0% batchWriteItem: supported, diverges on 0.0% of it, covers 100.0% (18 pass)
- createTable 0/30 0.0% 100.0% createTable: supported, diverges on 0.0% of it, covers 100.0% (30 pass)
- deleteItem 0/13 0.0% 100.0% deleteItem: supported, diverges on 0.0% of it, covers 100.0% (13 pass)
- deleteTable 0/3 0.0% 100.0% deleteTable: supported, diverges on 0.0% of it, covers 100.0% (3 pass)
- describeTable 0/4 0.0% 100.0% describeTable: supported, diverges on 0.0% of it, covers 100.0% (4 pass)
- getItem 0/39 0.0% 100.0% getItem: supported, diverges on 0.0% of it, covers 100.0% (39 pass)
- listTables 0/5 0.0% 100.0% listTables: supported, diverges on 0.0% of it, covers 100.0% (5 pass)
- putItem 0/129 0.0% 100.0% putItem: supported, diverges on 0.0% of it, covers 100.0% (129 pass)
- query 0/95 0.0% 100.0% query: supported, diverges on 0.0% of it, covers 100.0% (95 pass)
- scan 0/60 0.0% 100.0% scan: supported, diverges on 0.0% of it, covers 100.0% (60 pass)
- updateItem 0/70 0.0% 100.0% updateItem: supported, diverges on 0.0% of it, covers 100.0% (70 pass)
- updateTable 0/15 0.0% 100.0% updateTable: supported, diverges on 0.0% of it, covers 100.0% (15 pass)
Tier 2 - Complete
- account 0/2 · 2 skip n/a 0.0% account: unsupported, implements none of it (2 skip)
- backups 0/5 · 5 skip n/a 0.0% backups: unsupported, implements none of it (5 skip)
- contributorInsights 0/2 · 2 skip n/a 0.0% contributorInsights: unsupported, implements none of it (2 skip)
- export 0/2 · 2 skip n/a 0.0% export: unsupported, implements none of it (2 skip)
- kinesis 0/1 · 1 skip n/a 0.0% kinesis: unsupported, implements none of it (1 skip)
- partiql 0/186 0.0% 100.0% partiql: supported, diverges on 0.0% of it, covers 100.0% (186 pass)
- resourcePolicy 0/2 · 2 skip n/a 0.0% resourcePolicy: unsupported, implements none of it (2 skip)
- streams 0/18 0.0% 100.0% streams: supported, diverges on 0.0% of it, covers 100.0% (18 pass)
- tags 0/8 0.0% 100.0% tags: supported, diverges on 0.0% of it, covers 100.0% (8 pass)
- transactions 0/62 0.0% 100.0% transactions: supported, diverges on 0.0% of it, covers 100.0% (62 pass)
- ttl 0/7 0.0% 100.0% ttl: supported, diverges on 0.0% of it, covers 100.0% (7 pass)
- updateTable 0/14 0.0% 100.0% updateTable: supported, diverges on 0.0% of it, covers 100.0% (14 pass)
- vectorSearch 0/36 0.0% 100.0% vectorSearch: supported, diverges on 0.0% of it, covers 100.0% (36 pass)
Tier 3 - Strict
- error-messages 5/185 2.7% 100.0% error-messages: partially supported, diverges on 2.7% of it, covers 100.0% (180 pass, 5 fail)
- legacy-api 0/42 0.0% 100.0% legacy-api: supported, diverges on 0.0% of it, covers 100.0% (42 pass)
- limits 0/164 0.0% 100.0% limits: supported, diverges on 0.0% of it, covers 100.0% (164 pass)
- validation-ordering 0/34 0.0% 100.0% validation-ordering: supported, diverges on 0.0% of it, covers 100.0% (34 pass)
Where it falls short
Operations Dynoxide implements and answers differently from real DynamoDB, biggest gap first. This is what its divergence figure counts. Open one for the exact tests, or see the conformance suite.
-
error-messages Tier 3 5 diverging
View these tests in the suite →- Scan - exact error messages Segment >= TotalSegments: full out-of-range error source:62
- Scan - exact error messages Limit of 0: full minimum-value error source:103
- Scan - exact error messages negative Segment: full minimum-value error source:127
- Scan - exact error messages malformed ExclusiveStartKey: long schema-mismatch error source:211
- Scan - exact error messages Select SPECIFIC_ATTRIBUTES without ProjectionExpression: full required-projection message source:236
What it doesn't attempt
Operations Dynoxide declines rather than gets wrong, so none of this counts towards its divergence. It is what the gap in its 98.9% coverage is made of. Each test here skipped itself because the target's own feature probe said the operation isn't implemented, which is often a deliberate choice rather than a defect.
-
backups Tier 2 none of it 5 not attempted
View these tests in the suite →- Continuous backups - PITR reports PITR DISABLED by default
- Continuous backups - PITR enabling PITR transitions PointInTimeRecoveryStatus to ENABLED
- On-demand backups - lifecycle and restore CreateBackup → DescribeBackup → ListBackups → DeleteBackup
- On-demand backups - lifecycle and restore RestoreTableFromBackup initiates a restore into a new table
- On-demand backups - lifecycle and restore DescribeBackup on a deleted backup throws BackupNotFoundException
-
account Tier 2 none of it 2 not attempted
View these tests in the suite →- Account reads - DescribeLimits, DescribeEndpoints DescribeLimits returns positive account and table capacity limits
- Account reads - DescribeLimits, DescribeEndpoints DescribeEndpoints returns at least one endpoint with an address
-
contributorInsights Tier 2 none of it 2 not attempted
View these tests in the suite →- Contributor insights - enable/describe/list reports DISABLED by default
- Contributor insights - enable/describe/list enabling transitions the status and lists the table
-
export Tier 2 none of it 2 not attempted
View these tests in the suite →- Export and import - S3 ExportTableToPointInTime initiates an export and reports it
- Export and import - S3 ImportTable ingests S3 data into a new table
-
resourcePolicy Tier 2 none of it 2 not attempted
View these tests in the suite →- Resource policies - Put/Get/Delete GetResourcePolicy on a table with no policy throws PolicyNotFoundException
- Resource policies - Put/Get/Delete Put then Get round-trips the policy, and Delete removes it
-
kinesis Tier 2 none of it 1 not attempted
View these tests in the suite →- Kinesis streaming destination enables a streaming destination and reports it via Describe
Run history
Every percentage here is divergence, per tier and over the whole suite, so lower is better in each column. Coverage is the exception it is named as.
| Run | Gradecurrent criteria | Divergence | Movement |
|---|---|---|---|
| Grade A | 0.0% | diverged 0.8 percentage points less | |
|
Suite on 3.6.0 was cut but never published. eu-west-2 changed how it validates TransactWriteItems while the 3.6.0 measurement was running, so five tests failed against DynamoDB itself and no board was produced. This release carries the fix, and the changes listed under 3.6.0 are first measured here. eu-north-1 and ap-northeast-2 made the change first, and eu-west-2 followed on 2026-10-08, between two runs on main 30 minutes apart. The three now check a transaction's request before running it. An empty TransactItems gets the validation framework's generic constraint message. A 32-level value in the request is refused with a top-level ValidationException, where the transaction used to cancel on it or go on to evaluate the condition. A transacted Update that UpdateItem's statement-sized rule refuses is refused the same way, where it used to be stored. The other 30 regions still answer as before. The five tests accept either answer, exactly, until the regions settle. No engine loses a test over it, and on the 3.5.0 board's results the engines that already refuse a 32-level value up front pass tests they used to fail: three for LocalStack, and one each for DynamoDB Local, ExtendDB and ExtendDB (SQLite). The tests keep their names, which describe what the 30 regions do, until one answer is pinned, because a rename needs every committed results file re-run. The board and the results table now sort by grade, best first, and rows sharing a grade by the effective figure the letter is read from. They used to sort on divergence alone, and coverage lowers a letter without moving a row, so a target diverging slightly less over much less of the suite could sit above a better-graded one. No grade or figure changes. |
Grade A | 0.8% | unchanged |
|
Suite on Five Scan error messages are now regional splits. The validation-framework
rollout reached Scan's parameter checks in September, and eu-west-2,
eu-central-1, us-west-1 and ap-northeast-2 now word the errors for The negative The nesting-depth row moves af-south-1, ap-southeast-3, ap-southeast-4, ap-southeast-5 and il-central-1 to the strict side, leaving 22 regions strict and 11 lax. ap-northeast-1 answers both ways and stays lax for now. UpdateItem's item size is now a regional split, across eleven tests. Twenty-two regions, eu-west-2 among them, size an update by what the statement writes, as the suite already pinned, while ten cap the finished item at 409,600 like any other write. Engines that apply the plain cap are credited in those ten. Fourteen cases from @exoego for the 4096-byte expression cap and the 32-level nesting cap on the batch and transact write surfaces, captured against eu-west-2 and us-east-1.
|
Grade A | 0.8% | diverged 0.6 percentage points more |
|
Suite on Kumo joins the board, suggested by @exoego. It is a Go emulator covering 82 AWS services, of which DynamoDB is one, and it runs from a container image or a single binary with nothing else to install. The board's separate support and capability pages are now one emulator directory, with each project's feature and operation support in a single entry. |
Grade A | 0.2% | unchanged |
| Grade A | 0.2% | unchanged | |
|
Suite on Thirteen captures against eu-west-2 turned into coverage, and the suite grew from 1056 tests to 1251. All of it was ground truth nothing asserted, so an engine could discard a PartiQL index qualifier or store an item AWS would refuse and still score a clean pass.
Two fixes came with it. A local run against real DynamoDB takes its own table
namespace, so it can no longer delete a CI run's tables. And a
The over-25-requests assertion beside it spans both cohorts instead of becoming a second row. Neither wording is byte-stable: the table name carries a per-run suffix, the old cohort echoes all 26 requests back, and seven regions echo them as a JVM object identity rather than expanded fields. A row records one verbatim answer per region and there is nothing verbatim here to record, so the anchored pattern grew a second branch instead. It still refuses a wrong limit and a wrong table, and it matches every one of the 33 answering regions. The capture harness can take that evidence without creating tables. |
Grade A | 0.2% | diverged 0.2 percentage points more |
| Grade A | 0.0% | unchanged | |
| Grade A | 0.0% | diverged 0.9 percentage points less | |
| Grade A | 0.9% | unchanged | |
|
Suite on AWS corrected the vector index readiness documentation, prompted by a write-up of the earlier guidance that drew on the suite's measurements. The ACTIVE-plus-backfilling state the old advice was built around, and which no index ever occupies, is gone from the three pages that described it. The wait now reads "Backfilling is not true" rather than "is false", so a check written literally from it fires on both creation paths instead of neither. The tutorial no longer says a search during backfill can return incomplete results. Two things the suite had measured but nobody had written down are documented as well: that DescribeTable reporting ACTIVE leads the dedicated search endpoint, and that the readiness check depending on neither status field is a real search in a retry loop. That contract is now pinned rather than described. The UpdateTable walk asserts that Backfilling true is only ever reported alongside CREATING, that an ACTIVE index reports no Backfilling field at all, and that the base table goes ACTIVE while the index is still building, which is what makes a table waiter the wrong gate for a search. The first search that succeeds has to carry every seeded item, since the backfill window answers with an error rather than a partial view. On the CreateTable path a new test runs the documented check the way an application would, and every rejection before the first served response has to be the retryable ValidationException rather than a not-found. The suite's own search wait now absorbs those two rejections and rethrows every other answer, so a fixture waiting on an index that is ACTIVE but not yet served no longer fails on the lag it was waiting out. Two files asserting exact rejection messages wait for a served search rather than for ACTIVE: "does not have the specified index" is also what a freshly ACTIVE index says, and it would otherwise stand in for whichever message the case asked for. The tutorial's other new claim, that a table cannot be deleted while a vector index is being created, has a test of its own. Only the UpdateTable path can ask it. Across three runs an index created with its table reached ACTIVE in the same 250ms poll as the table, so the table is never ACTIVE with the index still building there, and a DeleteTable during creation is refused for the table's own status rather than with the documented index wording. Adding an index to a live table opens that window about thirty seconds in. The test cancels the index afterwards instead of waiting out the backfill, which a still-creating vector index turns out to accept the way a backfilling GSI does, so it costs a minute rather than seventeen. A release now dispatches its measurement as the results bot rather than with
the workflow's own token. GitHub raises no |
Grade A | 0.9% | unchanged |
|
Suite on The board now measures the most recent release tag rather than Every board now says what produced it. A board is graded against the suite manifest and split registry as they stood at the ref it measured. Region health is the exception and is read live, so a region dropped since the tag still counts against today's cohorts. That is the one input allowed to move under a board without a new measurement, and the board carries a health date beside its measurement date to say so. Releases are cut by one workflow dispatch: it bumps the version, dates this section, installs against the bumped tree, tags, and opens a draft release, then starts the measurement. The draft publishes itself when the board carrying that version lands, which takes about three hours. eu-west-2 has crossed to the validation framework's generic constraint message
for The matching validation-ordering row is retired. Both wordings refuse an empty
A probe absent from the baseline is no longer reported as drift. Adding a probe to the capture script leaves every older baseline without it, and a scheduled red then named the new probe as the thing that had moved. The weekly cross-region capture now includes eu-west-2, so the drift lens reads its baseline and the candidate regions from one capture taken at one moment rather than comparing today's candidates against an older baseline file. A scheduled red also keeps the eu-west-2 capture its drift verdict was read from, which was previously discarded with the runner. |
Grade A | 0.9% | unchanged |
| Grade A | 0.9% | unchanged | |
|
Suite on ExtendDB's SQLite backend joins the run, built from the same release as the PostgreSQL one and held to the same TLS, SigV4 and IAM posture, so the storage engine is the only thing that differs between them. A project's other builds now sit behind a disclosure on its row. Every build is measured in full and has a row of its own with its own figures; the disclosure starts closed only when every build under it reads the same grade, divergence and coverage as the row above, and only when each of them was measured in that run: a carried row on either side opens it, and so does a run the suite declined to score. It is read from each run, so a build can start closed on one and open on the next. The README table has no disclosure to offer, so it lists every build outright. Every target in the data endpoints gains two fields.
The |
Grade A | 0.9% | unchanged |
| Grade A | 0.9% | diverged 0.9 percentage points more | |
|
Suite on Read this first if you consume the JSON. The data endpoints go from schema 2 to schema 4 in one step. Schema 3 was never published on its own, so everything on 2 crosses both steps at once. Schema 3 breaks in four ways:
Schema 4 is additive on top: each target carries its grade and the full criteria
in A score is two figures, never oneDivergence is the share of the whole suite a target answers differently from real DynamoDB. Coverage is the share it implements at all. They are reported apart and never summed, because a declined operation is discoverable in minutes and a wrong one in production. No target was re-run for the change and no pass, fail or skip moved: what changed is how the same counts are expressed. Everything else that was a percentage followed the headline down - tier figures, the per-region drilldown, the per-operation table, and the colour bands, which inverted with them. A target's history is two plots rather than one, because divergence falls when a target stops attempting something it used to get wrong, so a divergence line alone can render a withdrawal as an improvement. Every target wears a letterDivergence sets it - A under 5%, B under 15%, C under 25%, D under 35%, F beyond
These are grading criteria version 1, dated in the methodology, which carries the derivation. Where a threshold sits is a hand-picked input to a published letter, and moving one regrades targets whose results never changed, so any change to a band, the coverage weight or the A+ gate bumps the version. The suite grows from 998 tests to 1054Vector search (#125). 42 tests over the deterministic surface DynamoDB
shipped in August 2026: the index lifecycle on both creation paths, request
validation on each plane, rejection wording, write-path validation, search on a
fixture where the nearest neighbour is unambiguous, the two new capacity shapes,
and PartiQL's inability to reach a vector index. Every pinned value was
characterised against real DynamoDB in eu-west-2 before it was asserted. Two
findings worth naming: searching during a backfill is an error, which settles
which side of a contradiction in AWS's own documentation is right - three
developer-guide pages say the call fails, the tutorial page says results can be
incomplete - and an overwrite leaving the stored vector unchanged reports no
vector write capacity at all, because index replication is delta-based. Both
sides are captured in Index write costs (#124). 14 tests. The suite's only per-index capacity assertion was on a Query, so the write side - the half you get billed extra for - went unmeasured. A sub-1KB write costs one unit for the table and one for each index it lands in, and LSI units fold into the total exactly as GSI units do. Moving an item to a new GSI key costs two on that index, a delete and an insert; touching a projected attribute costs one; touching a non-projected attribute costs nothing, and the response carries no arm for that index rather than a zero. An overwrite that leaves the item unchanged reports no index cost whatsoever. The index exclusions create no indexed table (#116). Corrections
|
Grade A | 0.0% | unchanged |
| Grade A | 0.0% | unchanged | |
| Grade A | 0.0% | unchanged | |
| Grade A | 0.0% | unchanged | |
| Grade A | 0.0% | unchanged | |
| Grade A | 0.0% | unchanged | |
| Grade A | 0.0% | diverged 5.2 percentage points less | |
| Grade B | 5.2% | diverged 1.5 percentage points more | |
|
Suite on Grew to 982 tests, up 28, all characterised against real DynamoDB across the per-region ground truth 2.0.0 put in place. New coverage is PartiQL's RETURNING clause; the GSI lifecycle also joins the ground truth, and the sweep's drift classifier gains a converged case.
|
Grade A | 3.7% | diverged 2.9 percentage points more |
| Grade A | 0.7% | unchanged |
Showing the 24 most recent runs. The chart above covers the full history, and every run is browsable from Runs.