Elasticsearch dense_vector Field Type: Dimensions, Similarity, and HNSW Index Options

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

  1. Setting dims to a value that doesn't match the embedding model. Mismatched vectors are rejected at index time with vector length must be [X], but was [Y].
  2. Using dot_product without normalizing vectors. The scoring assumption breaks and recall degrades silently.
  3. Leaving the default hnsw (float32) when memory is tight. int8_hnsw is the 8.13+ default precisely because it cuts memory ~4x with negligible recall loss; switching is a mapping change plus reindex.
  4. Querying with script_score on a large indexed field. If index: true, use the knn query or knn search section instead; script_score forces an exhaustive scan.
  5. 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 hnsw after 8.13 when int8_hnsw should be the default and cuts memory ~4x)
  • Mappings that are unsafe for your workload (e.g. dot_product similarity without normalized vectors, dims that does not match the embedding model and gets silently rejected, script_score exact kNN on a large indexed field instead of the knn query)
  • The downstream operational impact: HNSW graph build time per segment, off-heap memory pressure (num_vectors * dims * 4 bytes 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.

Connect your cluster.

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.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.