---
title: "Running DynamoDB vector search locally - Martin Hicks"
description: "Dynoxide runs DynamoDB vector indexes and SearchVectors on your own machine. What I measured about the index lifecycle before building it, where it will never match AWS, and how a validation-error rollout I caught in June finally ended."
canonical: https://martinhicks.dev/articles/running-dynamodb-vector-search-locally
last-updated: 2026-09-08
---

![The Dynoxide hexagonal logo centred on a near-black background](https://martinhicks.dev/images/articles/running-dynamodb-vector-search-locally.png)8th September 2026

# Running DynamoDB vector search locally

[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.

I 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.

## The index says ACTIVE about fifteen minutes early

Before 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://martinhicks.dev/articles/dynoxide-conformance-suite) and [the docs didn't agree with themselves](https://martinhicks.dev/articles/dynamodb-vector-search-docs-get-wrong) about when an index is ready.

Add 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.

It reaches `ACTIVE`, you search it, and it refuses.

On a table with twenty-five items in it, that carried on for about a quarter of an hour.

Dynoxide 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".

The full lifecycle, for reference:

Phase

`TableStatus`

`IndexStatus`

`Backfilling`

`SearchVectors`

Delete of the index

Allocating

`UPDATING`

`CREATING`

`false`

refused

refused

Backfilling

`ACTIVE`

`CREATING`

`true`

refused

accepted

Active, not searchable

`ACTIVE`

`ACTIVE`

absent

refused

accepted

Searchable

`ACTIVE`

`ACTIVE`

absent

served

accepted

The 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.

And `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://martinhicks.dev/articles/dynamodb-vector-search-docs-get-wrong).

Timings 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.

## Where it won't ever match

Dynoxide 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.

Ties 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.

My 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.

## Three months watching one error message

1.1.0 is a single behaviour change, and it's the end of something I started writing about in June.

Back then [my conformance suite went red and the thing that failed was AWS](https://martinhicks.dev/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.

The 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.

The 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.

## The boring bits

1.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.

One 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.

Everything 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.

## Get it

-   **npm:** `npm install --save-dev dynoxide` to pin it in a project, or `npx dynoxide` to run the latest without installing
-   **Homebrew:** `brew install nubo-db/tap/dynoxide`
-   **Cargo:** `cargo install dynoxide-rs` ([crates.io](https://crates.io/crates/dynoxide-rs), [docs.rs](https://docs.rs/dynoxide-rs))
-   **Docker:** `ghcr.io/nubo-db/dynoxide`, mirrored best-effort to Docker Hub (`nubodb/dynoxide`) and ECR Public
-   **GitHub:** [source and pre-built binaries](https://github.com/nubo-db/dynoxide/releases/tag/v1.1.0), plus the `nubo-db/dynoxide/action` GitHub Action for CI

## Further reading

-    [When a DynamoDB vector index is actually ready
    
    AWS said to wait for IndexStatus ACTIVE and Backfilling false. I measured it, and that state never happens. AWS have since rewritten the docs.](https://martinhicks.dev/articles/dynamodb-vector-search-docs-get-wrong)
-    [I watched DynamoDB change under my conformance suite
    
    My DynamoDB conformance suite went red and the test that failed was real AWS. It had reworded its validation errors, and the change hasn't reached every region yet.](https://martinhicks.dev/articles/dynamodb-changed-its-validation-errors)
-    [Building a DynamoDB conformance suite
    
    How I built a 526-test DynamoDB conformance suite, and what it found in DynamoDB Local, LocalStack, Dynalite, and Dynoxide.](https://martinhicks.dev/articles/dynoxide-conformance-suite)
-    [Introducing Dynoxide: a fast, embeddable DynamoDB engine
    
    Dynoxide is a DynamoDB-compatible engine in Rust, backed by SQLite, that starts in milliseconds. I built it to embed in a native app, and it turned into something bigger.](https://martinhicks.dev/articles/introducing-dynoxide)

---

Source: https://martinhicks.dev/articles/running-dynamodb-vector-search-locally
