DynamoDB Vector Search
DynamoDB gained native vector search on August 5, 2026. You store embeddings as a plain List of Number values on your items, add a vector index, and run approximate nearest neighbor queries with the new SearchVectors API.
Until now, similarity search meant replicating your table into OpenSearch or a separate vector database and keeping the two in sync. That pipeline is gone. The billing model that replaces it is unlike anything else in DynamoDB.
Does DynamoDB support vector search?
Yes — natively, since August 5, 2026. You store embeddings as a plain List of
Number values, add a vector index, and run approximate nearest neighbor
queries with the SearchVectors API — no OpenSearch replica, no separate vector
database. It works on on-demand tables only, up to 4,096 dimensions, and is
billed per byte written, searched, and stored.
- A third index family: vector indexes sit beside and LSIs. One new read API (
SearchVectors), ANN only, on-demand tables only, up to 4,096 dimensions. - It is fast, measured: against our live 1024-dim index in us-east-1,
SearchVectorsanswered as quickly asGetItemfrom the same client (p50 39 ms vs 44 ms), and a fresh write became searchable in ~136 ms. - Metering is per byte: $0.52 per GB of vector writes, $0.002 per GB of vector data a search examines, $0.25 per GB-month of storage (us-east-1). Indexing an embedding makes its base-table copy bill at exactly 4 bytes per dimension; the same list unindexed bills ~1.9×.
- S3 Vectors is still the bulk store: roughly 8× cheaper at rest and far cheaper to batch-load. DynamoDB wins on millisecond reads, streaming writes, and vectors living next to the item they describe.
How a vector index works
There is no new attribute type. An embedding is an ordinary list of numbers on the item, {"L": [{"N": "0.0132"}, {"N": "-0.0475"}, …]} on the wire, written with the same PutItem and UpdateItem you already use.
The index is a separate structure. DynamoDB replicates the vector into it asynchronously at 32-bit float precision, along with any attributes you project or filter on. Search results are eventually consistent, like a read.
The lag is small in practice. Against our live test index, a freshly written vector turned up in search results about 136 ms after the PutItem returned. Still, never build a read-your-own-write flow on it.
Where it sits next to the index types you know:
| Vector index | GSI | LSI | |
|---|---|---|---|
| Max per table | 5 | 20 | 5 |
| Read API | SearchVectors | Query, Scan | Query, Scan |
| PartiQL | No | Yes | Yes |
| Capacity mode | On-demand only | Both | Both |
| Consistency | Eventual | Eventual | Strong available |
| Add after table creation | Yes | Yes | No |
Each index fixes its dimension count (up to 4,096) and one of three distance functions at creation. COSINE and EUCLIDEAN score lower-is-more-similar; DOT_PRODUCT scores higher-is-more-similar and can go negative. None of it can be changed later.
One precision note before you benchmark anything. The index holds vectors at f32; higher-precision values are accepted but lose precision on the way in. If you arrive with float64 embeddings, every distance is computed against the f32 copy, so measure recall against f32, not your originals.
Create one and search it
Say you run semantic search over support tickets, so an agent can find "customers who hit this before" without matching keywords. Each ticket item carries an embedding of its subject and body, generated by any model you like (Titan Text Embeddings V2 costs $0.02 per million input tokens on Bedrock).
Add the index to the existing table. The HASH element scopes every search to one product value; INLINE_FILTER attributes (up to 18) allow equality filters at search time:
aws dynamodb update-table \
--table-name SupportTickets \
--attribute-definitions AttributeName=product,AttributeType=S \
AttributeName=severity,AttributeType=S \
--vector-index-updates '[{"Create": {
"IndexName": "TicketEmbeddings",
"VectorAttribute": {"AttributeName": "embedding"},
"SearchSchema": [
{"AttributeName": "product", "SearchSchemaElementType": "HASH"},
{"AttributeName": "severity", "SearchSchemaElementType": "INLINE_FILTER"}
],
"Projection": {"ProjectionType": "KEYS_ONLY"},
"Dimensions": 1024,
"DistanceFunction": "COSINE"
}}]'The build behaves like a GSI backfill with sharper edges. SearchVectors returns ValidationException for the whole build, with no partial results.
AWS warns the search endpoint can keep rejecting for a while even after DescribeTable says ACTIVE. There is no waiter; probe with a real search in a retry loop. When we created the index together with an empty table, it went ACTIVE in 26 seconds and accepted searches 0.6 s later.
Searching takes the query embedding as a bare JSON array of {"N": …} values. Do not wrap it in a DynamoDB L. The stored attribute uses the list type, the request parameter does not, and mixing them up is an easy first mistake:
aws dynamodb search-vectors \
--table-name SupportTickets \
--index-name TicketEmbeddings \
--search-vector file://query-embedding.json \
--top-k 5 \
--search-condition-expression "product = :p AND severity = :sev" \
--expression-attribute-values '{":p": {"S": "checkout"}, ":sev": {"S": "high"}}'You get back up to TopK items sorted most-similar-first, each with a Score, plus ConsumedCapacity when you ask for it. TopK caps at 100, there is no pagination, and the response caps at 16 MB.
The embedding itself is excluded from results unless you project it and request it. That default is deliberate; returning vectors inflates both the response and the search bill.
Filter expressions accept equality only, with no BETWEEN, IN, or begins_with. When the index defines a HASH attribute, every search must pin exactly one value for it. AWS's wording on range operators is "not yet available", so this may loosen.
Two operational surprises are worth knowing before the first deploy. SearchVectors needs the new dynamodb:SearchVectors IAM action, which none of your existing read policies include.
It also talks to a separate endpoint, search-dynamodb.{region}.amazonaws.com. Egress allowlists and VPC endpoint configs that only cover dynamodb.{region} break vector search alone, with a connection error that never says why.
What a vector index bills
Three new meters, all per byte, with a 1 KB minimum per write and per search request, on top of normal table charges (us-east-1, from the AWS pricing API, 2026-08-15):
| Meter | Standard | Standard-IA |
|---|---|---|
| Vector writes | $0.52/GB | $0.65/GB |
| Vector data examined per search | $0.002/GB | $0.0025/GB |
| Storage (table and index) | $0.25/GB-mo | $0.10/GB-mo |
The docs warn that the base-table copy of an embedding, stored as decimal strings inside a List, can be "considerably larger" than the f32 copy in the index. We measured write-unit billing against live tables in us-east-1, and the truth is stranger.
An embedding on an attribute with no vector index bills the documented decimal rule, roughly 1.9× the f32 size. Point a vector index at that same attribute and its base-table billing drops to exactly 4 bytes per dimension:
| Dimensions | Unindexed List attribute (billed) | Same attribute, vector-indexed (billed) |
|---|---|---|
| 256 | 1,914 B | 1,024 B |
| 768 | 5,760 B | 3,072 B |
| 1,024 | 7,653 B | 4,096 B |
| 1,536 | 11,501 B | 6,144 B |
| 3,072 | 22,957 B | 12,288 B |
Measured by binary-searching the write-unit boundary with a padding attribute, fresh item key per write, calibrated to the byte. Our full 1024-dim ticket item billed 5 write units; the identical item without an index on embedding bills 8.
The vector-write meter tracked the f32 size closely in the same runs. VectorWriteRequestBytes came back as 4 bytes per dimension plus 11 B of key overhead on a bare index, and plus 65 B with our two-attribute search schema.
Search billing is the meter you cannot compute in advance. VectorSearchRequestBytes tracks how much vector data the ANN traversal examined, and AWS's own guidance is to measure it via ReturnConsumedCapacity rather than estimate from dimension count.
Our probe gives the first data points. TopK=10 over a 50-vector partition examined 22.2-22.4 KB per search; the same search over a 1-vector partition still examined 21.4 KB, so at small scale there is a floor of roughly 21 KB (about $0.00000004) per query. AWS's tutorial reports 31,449 bytes for its own 50-vector example.
Model the table-side costs in the pricing calculator; the vector meters stack on top of the write units it already computes.
DynamoDB vector search vs S3 Vectors
AWS now sells two serverless vector stores, and they are built for opposite access patterns. S3 Vectors (GA December 2025) holds up to 2 billion vectors per index at $0.06/GB-month, answers in the 100 ms-to-1 s range, and bills every query against the whole index's size.
DynamoDB answers in milliseconds and bills against what the search examines, not against what the index holds.
| DynamoDB vector search | S3 Vectors | |
|---|---|---|
| GA | Aug 2026 | Dec 2025 |
| Latency class | Single-digit ms (AWS claim) | ~100 ms frequent, sub-1 s infrequent (AWS claim) |
| Scale ceiling | No stated vector cap; 600 GB table cap for index creation (soft) | 2B vectors per index |
| Max dimensions | 4,096 | 4,096 |
| Distance functions | Cosine, Euclidean, dot product | Cosine, Euclidean |
| Index writes | Async from table (eventually consistent) | Strongly consistent |
| Filtering | Equality only, ≤18 attributes + 1 partition key | Rich metadata filters, 2 KB filterable cap per vector |
| TopK | 100, no pagination | 10,000, paginated |
| Storage | $0.25/GB-mo, twice (table + index) | $0.06/GB-mo, once |
| Writes | $0.52/GB, 1 KB min/request | $0.20/GB, 128 KB min/PUT |
| Queries | $0.002/GB examined | $2.50/M requests + whole-index processed-bytes charge |
The write minimums decide the streaming case, and they point the opposite way from the storage rates. Writing one 1024-dim vector at a time, per million writes (from the verified rates and our measured 5 write units + 4,161 vector-write bytes per item):
| Write pattern | DynamoDB | S3 Vectors |
|---|---|---|
| Single-vector writes | ~$5.14/M | ~$24.41/M |
Batched (500 per PutVectors) | n/a (writes are per item) | ~$0.78/M |
S3's 128 KB minimum per PUT makes it the expensive option for exactly the workload people assume it is cheap for. Stream vectors one at a time into S3 Vectors and you pay almost 5× DynamoDB's rate; batch-load them and you pay roughly 7× less.
Monthly storage and query totals for a 1024-dim corpus at 1M queries per month, computed from the verified rates (write costs are the per-million table above). S3 Vectors' query charge follows its published formula, whole index size times a tiered rate.
DynamoDB's query charge depends on examined bytes, so we show a sensitivity range instead of pretending to know your traversal:
| Corpus | DynamoDB storage | DynamoDB queries (4 / 40 / 400 MB examined) | S3 Vectors storage | S3 Vectors queries |
|---|---|---|---|---|
| 1M vectors | ~$1.95 | $8 / $80 / $800 | ~$0.23 | ~$11 |
| 10M vectors | ~$19.50 | $8 / $80 / $800 | ~$2.35 | ~$80 |
| 100M vectors | ~$195 | $8 / $80 / $800 | ~$23.50 | ~$217 |
Two things fall out of that table. DynamoDB's per-query cost does not grow with corpus size — an ANN search examines a neighborhood, not the index, and partition-key scoping shrinks it further.
S3 Vectors' storage advantage (~8×, since DynamoDB stores two f32 copies at 4× the rate) compounds forever, whether or not anyone queries.
When to use which
- Vectors describe live items you already keep in DynamoDB (tickets, products, user sessions, agent memory): use the vector index. One write path, one item, no sync pipeline to drift.
- Millions of embeddings, queried occasionally (RAG over documents, archives, nightly jobs): use S3 Vectors. Batch-load cheaply, pay $0.06/GB at rest, tolerate a few hundred ms.
- High QPS with hybrid ranking (text relevance + vectors, faceting, aggregations): OpenSearch remains the answer, at an infrastructure floor of roughly $350/month for a classic serverless collection.
- Vectors joined to relational data: Aurora PostgreSQL with pgvector, which scales to zero and lands under ~$50/month for small RAG workloads.
The honest default for a DynamoDB shop is both. Keep hot, filterable vectors on the table where writes are atomic with the item, and archive the long tail to S3 Vectors, whose strongly consistent batched writes make it a clean sink.
The traps
- Silent de-indexing: an item missing the index's
HASHattribute writes to the table fine and never enters the vector index. We reproduced it live: thePutItemsucceeded, and the vector was absent from every partition 15 seconds later. No error, no result, nothing in the response to tell you. - Wrong-dimension writes are rejected: switch embedding models without migrating and every write fails with a
ValidationExceptionnaming the attribute and both sizes (Invalid size for parameter, captured verbatim on its own page), since the index pins the dimension count forever. - Stale embeddings: DynamoDB never recomputes vectors. Edit a ticket's text without rewriting
embeddingand searches silently match the old content. Streams plus a regeneration consumer is the standard fix. TopKalways returns K items: with three good matches and--top-k 10, you still get 10. Judge relevance byScore, not by result count, and remember the score direction flips between distance functions.- The 1 KB minimums: low-dimension vectors do not meter proportionally cheaper, on writes or searches.
- Everything is immutable: dimensions, distance function, and an
INCLUDEprojection's attribute set all require delete-and-recreate to change. Index storage bills for the index's whole lifetime, queried or not.
Try it against your own tables
Vector search inherits the cost discipline the rest of DynamoDB taught you. Size the embedding before you commit to it, because item size limits still apply and a 3072-dim embedding adds 12 KB to every item write on both meters.
The index mechanics will feel familiar if you know how GSIs replicate asynchronously and when to pick a GSI over an LSI. For lexical search over the same data, DynamoDB still has no full-text engine; vector search matches meaning rather than spelling.
Check an embedding's real byte cost in the item size calculator, then try DynoTable to browse the items behind your vector index — embeddings render as ordinary list attributes right next to the fields you filter on.