{"slug": "running-dynamodb-vector-search-locally", "title": "Running DynamoDB vector search locally", "summary": "Dynoxide, a DynamoDB-compatible local database, has added vector search support in versions 1.0.0 and 1.1.0, including vector indexes, SearchVectors, and write paths. The developer behind Dynoxide documented the behavior of DynamoDB's vector search, revealing that indexes can report ACTIVE status while still refusing searches, and that AWS's documented readiness checks are unreliable. Dynoxide deliberately replicates this behavior to ensure compatibility, and the new version also changes validation error handling to match AWS's updated messages.", "body_md": "[Dynoxide](https://dynoxide.dev) has had DynamoDB vector search since 1.0.0 landed on the 25th of August: vector indexes, `SearchVectors`, and the write paths keeping them current. It's on the native binary, the browser build and the MCP server, so an agent can create an index and search it without any of that reaching AWS.\n\nI announced it at the time and then went on holiday instead of writing it up. This is the write-up, plus 1.1.0, which landed on the 3rd.\n\nBefore building any of it I spent two days pointing things at real DynamoDB and writing down what came back, because I wanted vector search in the [conformance suite](https://dev.to/articles/dynoxide-conformance-suite) and [the docs didn't agree with themselves](https://dev.to/articles/dynamodb-vector-search-docs-get-wrong) about when an index is ready.\n\nAdd a vector index to a table that already has data in it and it has to backfill. Fine, expected. So you poll `DescribeTable` and wait for `IndexStatus` to reach `ACTIVE`, which is what you'd do for a GSI and what AWS's docs told you to do at the time.\n\nIt reaches `ACTIVE`, you search it, and it refuses.\n\nOn a table with twenty-five items in it, that carried on for about a quarter of an hour.\n\nDynoxide does the same thing, deliberately. The index goes `ACTIVE`, searches still get refused, and a while later it starts answering. A readiness check built on polling for `ACTIVE` therefore breaks on your laptop rather than in production. Put the search in a retry loop and treat the refusal as \"not yet\".\n\nThe full lifecycle, for reference:\n\n| Phase | `TableStatus` | `IndexStatus` | `Backfilling` | `SearchVectors` | Delete of the index | \n|---|---|---|---|---|---|\n| Allocating | `UPDATING` | `CREATING` | `false` | refused | refused | \n| Backfilling | `ACTIVE` | `CREATING` | `true` | refused | accepted | \n| Active, not searchable | `ACTIVE` | `ACTIVE` | absent | refused | accepted | \n| Searchable | `ACTIVE` | `ACTIVE` | absent | served | accepted | \n\nThe base table catches people out too. It's back to `ACTIVE` while the index is still `CREATING`, so a table waiter looks like the right gate about three phases too early.\n\nAnd `Backfilling` doesn't go `false` when it finishes, it disappears. Which means \"wait until `IndexStatus` is `ACTIVE` and `Backfilling` is `false`\" never comes true, on AWS or here. That was AWS's own documented advice until they [rewrote it](https://dev.to/articles/dynamodb-vector-search-docs-get-wrong).\n\nTimings are the one thing not to copy across. Dynoxide's phases run in tens of seconds so you can watch them happen; AWS's are minutes and vary with the table. Don't write `sleep(30)` and assume you're ready.\n\nDynoxide compares your query against every vector in the index, so you get the genuinely nearest results back. AWS builds an approximate index and skips most of the comparisons, which is how it stays quick on a huge table and why it will occasionally miss something that was really there. So don't pin an exact list of results in a test that also runs against AWS - assert what you actually care about, like \"the item I put in comes back\". It also means a local table holding millions of vectors isn't what this is for.\n\nTies are the other one. Dynoxide breaks them by primary key, AWS promises nothing, and three identical calls came back in three different orders. Sort them yourself if it matters.\n\nMy favourite is `VectorSearchRequestBytes`, where AWS can't reproduce its own number. Five identical searches over an index nobody had touched: 14214, 13903, 14214, 14214, 14518. Dynoxide reports something stable instead, which is handy for comparing one local search against another and useless for asserting against AWS.\n\n1.1.0 is a single behaviour change, and it's the end of something I started writing about in June.\n\nBack then [my conformance suite went red and the thing that failed was AWS](https://dev.to/articles/dynamodb-changed-its-validation-errors). It had quietly reworded a pile of its validation errors, with nothing in any changelog. I checked four regions: two had the new wording, two didn't. I said I reckoned that was a rollout caught mid-flight rather than a permanent regional split, and that I had no way to prove it.\n\nThe suite sweeps 33 regions now. As of the 2nd September, `BatchGetItem` with an empty `RequestItems` map answers the new wording in 32 of them, and me-central-1 is the last holdout. So it was a rollout, and it took about three months.\n\nThe part I didn't see coming is that it moves per operation as well as per region. `BatchWriteItem` sits right beside it, takes the same empty map, hits the same validation, and is still on the old wording nearly everywhere - one region, eu-north-1, had crossed as of the 22nd August. Dynoxide follows eu-west-2, so one of the pair changed in 1.1.0 and its sibling didn't. They answer differently now, with a test pinning each, because that's what the real thing does.\n\n1.0.0 carried a lot besides vector search. Consumed capacity is sized the way DynamoDB sizes it now, a stack of PartiQL fixes landed, and several requests that used to be accepted aren't any more. If you've got tests asserting on exact answers, read [the changelog](https://github.com/nubo-db/dynoxide/blob/main/CHANGELOG.md) before you upgrade.\n\nOne worth calling out on its own: `StorageBackend` is sealed. Vector indexes alone added seven methods to that trait, and left open, every one of those would have been a major.\n\nEverything moved to 1.0.0 together and the browser engine dropped its `-preview` suffix. That number isn't a claim that conformance is finished - the [live standings](https://paritysuite.org) are the honest answer to that.\n\n`npm install --save-dev dynoxide` to pin it in a project, or `npx dynoxide` to run the latest without installing`brew install nubo-db/tap/dynoxide`\n`cargo install dynoxide-rs` (`ghcr.io/nubo-db/dynoxide`, mirrored best-effort to Docker Hub (` nubodb/dynoxide`) and ECR Public` nubo-db/dynoxide/action` GitHub Action for CI", "url": "https://wpnews.pro/news/running-dynamodb-vector-search-locally", "canonical_source": "https://dev.to/martinhicks/running-dynamodb-vector-search-locally-203o", "published_at": "2026-09-08 07:21:32+00:00", "updated_at": "2026-09-08 07:31:35.101787+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Dynoxide", "AWS", "DynamoDB"], "alternates": {"html": "https://wpnews.pro/news/running-dynamodb-vector-search-locally", "markdown": "https://wpnews.pro/news/running-dynamodb-vector-search-locally.md", "text": "https://wpnews.pro/news/running-dynamodb-vector-search-locally.txt", "jsonld": "https://wpnews.pro/news/running-dynamodb-vector-search-locally.jsonld"}}