
Learn how to add semantic search to an existing DynamoDB table using native vector indexes and Amazon Bedrock embeddings. This guide shows you how to store embeddings alongside your data, create a vector index, and query by meaning without a separate search service.
Why Search Is Hard in DynamoDB: The Problem with GSIs and Pipelines
For enterprise software engineers, integrating search functionality into DynamoDB-backed applications has historically introduced significant architectural friction. Traditional approaches often forced engineers to choose between rigid indexing strategies or the operational overhead of external search infrastructure.
The primary challenges when implementing search in native DynamoDB include:
- Scaling Limitations of GSIs: Global Secondary Indexes (GSIs) are effective for exact-match lookups, but they become non-scalable when supporting multiple filter permutations. Creating a dedicated GSI for every possible query combination leads to excessive storage costs and write amplification.
- Operational Complexity of Pipelines: The historical fallback—offloading data to a separate search engine—necessitates a complex data synchronization pipeline. This introduces new failure points, requires managing infrastructure consistency, and forces engineers to handle the lag between data ingestion and searchability.
DynamoDB Vector Search addresses these issues by allowing developers to store vector embeddings—numerical representations of semantic meaning—directly alongside the source data. By leveraging native vector indexes, engineers can query data based on intent rather than just literal keyword matches, significantly improving the retrieval experience for natural language inputs.
Implementing this workflow involves several technical considerations:
- Embedding Generation: Generating embeddings inline during the write operation ensures the data is searchable immediately. However, if the latency overhead (e.g., calling models like Titan Text Embeddings) is prohibitive for the write path, engineers must implement asynchronous updates via DynamoDB Streams, while carefully avoiding recursion loops.
- Indexing and Filtering: Native vector indexes support configuration of distance functions—such as cosine similarity—and allow for "inline filters" on specific attributes. This enables pre-filtering datasets (e.g., scoping a semantic search to a specific category) without the need for additional complex indexes.
- Data Lifecycle: Migrating existing tables requires a backfill process to generate and store embeddings for legacy records, as they will not appear in vector search results until indexed.
By consolidating search within the existing storage layer, teams can eliminate external dependencies and synchronization logic, simplifying the overall architecture while enabling advanced semantic search capabilities.
Understanding Semantic Search and Embeddings
Semantic search represents a departure from traditional keyword-based retrieval by focusing on the underlying intent of a user's query rather than exact lexical matches. This methodology relies on embeddings, which are high-dimensional vectors—essentially lists of numerical values—that encode the contextual meaning of a text segment. By mapping these strings into a multi-dimensional vector space, semantically related concepts are positioned in close proximity to one another.
For example, in a culinary application, a semantic search engine will recognize that the phrase "spicy chicken stew" is contextually related to "hot and hearty poultry dish." Despite sharing almost no overlapping vocabulary, their proximity within the vector space allows the system to identify the latter as a relevant result for the former.
To implement this effectively, choosing an appropriate embedding model is critical for ensuring high-quality vector representations. Amazon Bedrock's Titan Text Embeddings V2 model is a robust choice for several technical reasons:
- High Dimensionality: The model produces 1024-dimension vectors, providing sufficient granularity to capture nuanced semantic relationships.
- Normalization: The output is returned as normalized vectors, which simplifies the mathematical process of determining similarity.
- Cosine Similarity Compatibility: Given the normalized output, the vectors pair naturally with cosine similarity, a standard metric for measuring the angle between two vectors to determine their semantic likeness.
When deploying these embeddings, it is essential to maintain consistency by using the same model and dimensionality for both the indexing phase (the data stored in your vector database) and the query phase. Using disparate models or dimensions would map query inputs into a different vector space, rendering any distance calculations meaningless. In practice, embedding structured data—such as aggregating fields like names, descriptions, ingredients, and cuisine types into a single string—often yields superior search performance, as it allows the vector space to encapsulate a more comprehensive representation of the item.
Building the Write Path: Embedding Your Data
Implementing semantic search within DynamoDB begins with effective data preparation. Because vector search models require a singular input string to generate a meaningful numerical representation, you must flatten structured recipe data—such as name, description, cuisine, dietary tags, ingredients, and preparation times—into a cohesive text block. This approach ensures the embedding captures the full context of the item, enabling matches based on user intent rather than simple keyword overlap.
The following implementation demonstrates how to synthesize recipe fields into a single string before invoking an embedding model:
function buildEmbeddingText(recipe: RecipeInput): string {
const ingredientNames = recipe.ingredients.map((i) => i.name).join(", ");
const dietaryInfo = recipe.dietary?.length ? `Dietary: ${recipe.dietary.join(", ")}.` : "";
return [
recipe.name,
recipe.description,
`Cuisine: ${recipe.cuisine}.`,
dietaryInfo,
`Ingredients: ${ingredientNames}.`,
`Prep time: ${recipe.prepTimeMinutes} minutes. Cook time: ${recipe.cookTimeMinutes} minutes.`
].filter(Boolean).join(" ");
}
Once the text is prepared, you utilize Amazon Bedrock’s amazon.titan-embed-text-v2 model to generate a normalized, 1024-dimension vector. By executing this generation inline at write time, your DynamoDB items become immediately searchable. Storing the resulting vector as an attribute directly on the DynamoDB item keeps the operational footprint minimal, eliminating the need for separate search infrastructure or asynchronous synchronization pipelines.
Key technical considerations for this architecture include:
- Consistency: Use the identical model and dimensionality for both write-time indexing and query-time search; utilizing disparate configurations will place data in incompatible vector spaces.
- Write Latency: Generating embeddings inline adds 100–150ms to the write operation. If your application requirements preclude this, consider moving the embedding process to a DynamoDB stream-triggered Lambda function.
- Backfilling: Existing items lack vector attributes. You must execute a one-time migration script—via a table scan or an S3 export—to generate and update embeddings for legacy records.
- Inline Filtering: Leverage the native vector index to include attributes like 'cuisine' as filters, which provides a performant way to constrain search results before performing similarity calculations.
Creating a Native Vector Index on Your Existing Table
DynamoDB vector indexes extend the table's native indexing model. Instead of routing items by a key, a vector index is built over an attribute that stores an embedding — a list of numbers representing the semantic meaning of the text. This allows queries using the dedicated SearchVectors API to find items by similarity rather than exact key match.
Creating a vector index resembles creating a Global Secondary Index (GSI). The UpdateTable command declares the index name, the vector attribute, the distance function, the dimension count, and an optional search schema for filtering:
await dynamodb.send(new UpdateTableCommand({
TableName: tableName,
AttributeDefinitions: [
{ AttributeName: "cuisine", AttributeType: "S" }
],
VectorIndexUpdates: [{
Create: {
IndexName: "recipe-vector-index",
VectorAttribute: { AttributeName: "embedding" },
SearchSchema: [
{ AttributeName: "cuisine", SearchSchemaElementType: "INLINE_FILTER" }
],
Projection: { ProjectionType: "ALL" },
Dimensions: 1024,
DistanceFunction: "COSINE"
}
}]
}));
Inline filters act as optional prefiltering. They behave like a partition key in a regular index, except they are not required at query time. A query can omit the filter entirely for a global semantic search, or supply a value to narrow the candidate set before vector scoring is applied.
CloudFormation limitation: CloudFormation does not yet support vector indexes. The index must be created by a post-deployment script that calls UpdateTable idempotently — checking whether the index already exists before attempting creation.
Query-time behavior: embed the user's query with the same model and dimensions used at write time (Titan Text Embeddings V2, 1024 dimensions), then call SearchVectors with the query vector and a TopK value. The response includes each matching item with a similarity score. Using a different model or dimension count places the query in a different vector space and produces meaningless results.
Operational considerations:
- Write latency: generating an embedding inline can add roughly 100–150 ms to each write. If that is unacceptable, generate embeddings asynchronously via DynamoDB Streams and a Lambda function; note that writing the embedding back to the item triggers the stream again, which can cause an infinite loop.
- Backfill: existing items lack the embedding attribute and will not appear in vector search results until updated. A one-time scan-and-update script, or DynamoDB Export to S3 followed by batched processing, is required.
- Index maintenance: the vector index lives in the same table as the source data, so there is no separate search service or synchronization pipeline to operate.
Implementing the Search Query with SearchVectors
Implementing semantic search within Amazon DynamoDB relies on maintaining consistency between the embedding generation process and the retrieval interface. To ensure accurate results, the user's natural language query—such as "something spicy with chicken"—must be transformed into a vector using the exact same Amazon Bedrock Titan Text Embeddings V2 model and dimensionality (1024) used during the initial data ingestion phase. Using a mismatched model or vector space will result in mathematically invalid similarity calculations.
The retrieval flow follows these technical steps:
- Embedding Generation: Invoke the Bedrock
InvokeModelCommandwith the user's input string, ensuring thedimensionsparameter is set to 1024 andnormalizeis set totrue. - Executing SearchVectors: Use the
SearchVectorsCommandagainst the DynamoDB table. The command requires the query vector (mapped to the required format), theIndexName, and aTopKparameter to constrain the number of returned results. - Response Mapping: DynamoDB returns the matching items along with a
Scoreattribute representing the similarity distance. The application layer iterates through theSearchResults, mapping the low-level DynamoDB attribute types (e.g.,Sfor strings,Lfor lists,Nfor numbers) back into the application's domain model.
By including the Score property in the final response, your application can rank recipes by intent strength rather than literal keyword matching. For example, a query for "something spicy with chicken" identifies recipes where the embedding of the combined name, description, and ingredient fields is closest to the query vector in the 1024-dimensional space. This architecture provides an integrated search experience without requiring external search services or complex data synchronization pipelines, keeping the semantic data local to your primary storage.
Practical Considerations: Latency, Backfilling, and Embedding Strategy
Implementing vector search directly within DynamoDB eliminates the need for separate search clusters or complex data synchronization pipelines. However, integrating this functionality requires careful architectural planning to maintain performance and data integrity.
Consider the following practical implementation trade-offs:
- Latency Management: Generating embeddings inline during write operations adds approximately 100-150ms to the request lifecycle. For latency-sensitive applications, move embedding generation to an asynchronous process using DynamoDB Streams coupled with an AWS Lambda function. When implementing this pattern, ensure your update logic includes checks to prevent infinite loops, which occur if the Lambda function's update to the item triggers a subsequent stream event.
- Backfilling Existing Data: Vector search indexes only reference items that contain the embedding attribute. Consequently, legacy data will be invisible to your semantic search queries. To rectify this, perform a one-time backfill by scanning the existing table to generate and append embeddings. For large datasets where a direct table scan is impractical, utilize DynamoDB Export to S3 to process records in bulk asynchronously.
- Embedding Signal Quality: The effectiveness of semantic search relies heavily on the quality of the source text. Rather than embedding individual fields (such as a name or description alone), create a combined string representation of the object. Aggregating key attributes—such as names, descriptions, categorical tags, and metadata—provides the embedding model with significantly more signal, leading to more accurate intent-based retrieval.
By storing vectors alongside your primary data and leveraging native vector indexing, you keep your infrastructure footprint minimal, removing the overhead of maintaining external search services. This approach maintains a consolidated source of truth within DynamoDB. In our next installment, we will explore how to evolve this architecture by transforming the existing REST API into an Model Context Protocol (MCP) server.
Editorial Policy & Research Methodology
Our findings are based on rigorous internal research, verified industry benchmarks, and direct technical implementation experience from our enterprise client projects. All statistics and technical claims are reviewed by senior engineers before publication to ensure accuracy, transparency, and helpfulness for our readers.
