The dense_vector field type stores fixed-length arrays of numeric values that represent embeddings used for k-nearest-neighbor (kNN) search in Elasticsearch. Each document stores one vector per field; the vector's length is declared once via dims and enforced on every write. dense_vector is the canonical type for semantic search, recommendation, image similarity, and any workload that needs to score documents by vector distance rather than by inverted-index term matching.
How dense_vector Works
A dense_vector field is backed by either no index (brute-force exact kNN) or an Approximate Nearest Neighbor (ANN) index. From Elasticsearch 8.0, index: true is the default and uses an HNSW (Hierarchical Navigable Small World) graph per segment. Queries traverse the graph to retrieve approximate top-k neighbors in sub-linear time. Setting index: false falls back to exact scoring via script_score, which scans every vector and only scales to small corpora.
Similarity is chosen at mapping time and cannot be changed without reindexing. Supported values are cosine (default when index: true), dot_product (requires unit-normalized vectors), l2_norm (Euclidean), and max_inner_product (added in 8.11 for non-normalized inner-product scoring). Internally, Elasticsearch may pre-normalize vectors at index time for cosine so query-time comparisons reduce to dot products.
PUT products
{
"mappings": {
"properties": {
"embedding": {
"type": "dense_vector",
"dims": 1024,
"index": true,
"similarity": "cosine",
"index_options": {
"type": "int8_hnsw",
"m": 16,
"ef_construction": 100
}
}
}
}
}
dense_vector Configuration Reference
| Parameter | Values | Notes |
|---|---|---|
dims |
1-4096 (8.11+); 1-2048 (8.0-8.10); 1-1024 (7.x) | Required when not using a model. Cannot be changed after creation. |
index |
true (default 8.0+) / false |
false disables ANN; use script_score for exact kNN. |
similarity |
cosine, dot_product, l2_norm, max_inner_product |
max_inner_product added in 8.11. |
index_options.type |
hnsw, int8_hnsw (8.13+ default), int4_hnsw (8.15+), flat, int8_flat, int4_flat |
Quantized variants cut memory ~4x (int8) or ~8x (int4) with minor recall loss. |
index_options.m |
16 (default) | Graph connectivity. Higher = better recall, more memory. |
index_options.ef_construction |
100 (default) | Build-time quality knob. Higher = slower indexing, better graph. |
element_type |
float (default), byte, bit (8.15+) |
bit vectors are 1 bit per dim and use Hamming distance. |
Common Pitfalls with dense_vector
- Setting
dimsto a value that doesn't match the embedding model. Mismatched vectors are rejected at index time withvector length must be [X], but was [Y]. - Using
dot_productwithout normalizing vectors. The scoring assumption breaks and recall degrades silently. - Leaving the default
hnsw(float32) when memory is tight.int8_hnswis the 8.13+ default precisely because it cuts memory ~4x with negligible recall loss; switching is a mapping change plus reindex. - Querying with
script_scoreon a large indexed field. Ifindex: true, use theknnquery orknnsearch section instead;script_scoreforces an exhaustive scan. - Forgetting that HNSW graphs are per-segment. Force-merging to one segment improves recall and latency but is expensive. Schedule it during quiet periods.
Operating dense_vector at Scale
Vector search workloads are memory-bound. HNSW graphs are kept in the OS page cache, so under-provisioning RAM destroys query latency long before CPU becomes the bottleneck. Plan for roughly num_vectors * dims * 4 bytes per shard for float32, divided by 4 or 8 if you use int8/int4 quantization. Track dense_vector segment sizes and circuit-breaker tripping on the hot path.
Prevent dense_vector Misconfiguration with Pulse
Pulse is an AI DBA for Elasticsearch and OpenSearch that tracks dense_vector mappings - dims, similarity, index_options.type, m, ef_construction, and element_type - against actual workload behavior, flagging:
- Drift between intended mappings and what's deployed (a new index template still using float32
hnswafter 8.13 whenint8_hnswshould be the default and cuts memory ~4x) - Mappings that are unsafe for your workload (e.g.
dot_productsimilarity without normalized vectors,dimsthat does not match the embedding model and gets silently rejected,script_scoreexact kNN on a large indexed field instead of theknnquery) - The downstream operational impact: HNSW graph build time per segment, off-heap memory pressure (
num_vectors * dims * 4bytes for float32, divided by 4 for int8), kNN query latency p95, and circuit-breaker trips on hot vector search paths
When a dense_vector index starts thrashing or recall degrades after a re-shard, Pulse names the root cause and the mapping change that would fix it - before the next production kNN latency incident.
Frequently Asked Questions
Q: What is the maximum number of dimensions for an Elasticsearch dense_vector field?
A: The dense_vector dimension limit was 1024 in 7.x, raised to 2048 in 8.0, and raised again to 4096 in Elasticsearch 8.11. Exceeding the limit at mapping time fails with The number of dimensions for field [X] should be in the range.
Q: Which similarity should I use with dense_vector?
A: Use cosine for general-purpose text embeddings, dot_product when your model already produces unit-length vectors (saves a normalization step), l2_norm for image or audio embeddings where Euclidean distance is the model's training objective, and max_inner_product (8.11+) for unnormalized inner-product models such as some retriever-as-a-service offerings.
Q: When should I use int8_hnsw or int4_hnsw instead of plain hnsw?
A: Use int8_hnsw for almost all production workloads - it is the default from 8.13 onward and cuts memory ~4x with sub-1% recall loss. Choose int4_hnsw (8.15+) when memory is the binding constraint and a few extra points of recall loss are acceptable. Plain hnsw (float32) is only worth keeping for tiny indices or recall-critical benchmarks.
Q: Can I change dims or similarity on an existing dense_vector field?
A: No. Both are fixed at mapping creation. Changing either requires creating a new index with the new mapping and using the Reindex API to copy documents over, ideally behind an alias for zero-downtime cutover.
Q: How is dense_vector different from sparse_vector?
A: dense_vector stores fixed-length dense float arrays for kNN search backed by HNSW. sparse_vector (used by ELSER and learned sparse retrieval) stores token-to-weight maps and is scored via inverted index lookups, not graph traversal. Choose based on whether your model produces dense embeddings or sparse term expansions.
Q: Can I combine dense_vector kNN with traditional text search?
A: Yes. Use the knn search section alongside a query block in the same request; Elasticsearch runs both and merges results with Reciprocal Rank Fusion (RRF) when configured, or you can score-combine with a function_score query. This is the standard hybrid-search pattern.
Q: What's the best tool to monitor and tune Elasticsearch dense_vector kNN search?
A: Pulse is purpose-built for this. It is an AI DBA for Elasticsearch and OpenSearch that tracks dense_vector mappings, HNSW graph build time, off-heap memory, and kNN latency percentiles, recommending quantization (int8_hnsw, int4_hnsw) or m/ef_construction changes when vector workloads regress.
Related Reading
- Elasticsearch Sparse Vector Field Data Type: the sparse counterpart used by ELSER.
- Elasticsearch Function Score Query: combine vector similarity with custom scoring.
- Elasticsearch Create Index with Mapping: the right way to declare dense_vector fields up front.
- Elasticsearch Add Field to Mapping: adding an embedding field to an existing index.
- Elasticsearch IllegalArgumentException: mapper conflicts: what happens when dims or similarity changes between indices.