Dense vector search

This section documents the current Solr line (10.0; 9.10.x the maintained 9.x branch), written and verified against the Apache Solr Reference Guide. No specific patch version is pinned. Some capabilities (the Solr Operator on Kubernetes, the package-manager ecosystem, Learning To Rank model training, and expert plugin development) are linked, not documented in depth.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Solr searches embeddings with DenseVectorField — a fixed-length float vector indexed into an HNSW graph — and the knn query parser, Solr’s counterpart to Elasticsearch’s dense_vector and kNN. This page covers declaring the field type, the knn query parser’s topK and filtering behaviour, using knn as a second-pass re-ranker over a first-pass lexical query, combining lexical and vector signals in one request, and where an embedding model fits in. DenseVectorField itself is introduced alongside Solr’s other field types on Field types; `knn’s place among Solr’s other query parsers — including local-params syntax — is on Query parsers.

DenseVectorField mapping

Dimensionality and similarity function

vectorDimension is required and fixes the vector length — every value written to the field must have exactly that many components, or indexing fails. similarityFunction picks the distance metric used both to build the HNSW graph and to score knn matches: euclidean is the default (l2 distance), dot_product assumes unit-length vectors and is the cheapest to compute, and cosine normalises for you when vectors are not already unit length. knnAlgorithm defaults to hnsw; a cagra_hnsw build target is also available where GPU-accelerated index construction is configured.

<!-- in schema.xml / managed-schema -->
<fieldType name="knn_vector" class="solr.DenseVectorField"
           vectorDimension="384"
           similarityFunction="cosine"
           knnAlgorithm="hnsw"
           hnswM="16"
           hnswEfConstruction="100"/>

<field name="embedding" type="knn_vector" indexed="true" stored="true"/>

HNSW tuning: hnswM and hnswEfConstruction

hnswM (default 16) is the maximum number of edges each node keeps in the HNSW graph — a higher value improves recall at the cost of a larger index and slower builds. hnswEfConstruction (default 100) is the size of the candidate list explored while inserting each vector during indexing; raising it builds a better-connected graph at the cost of slower indexing, but has no effect on query-time latency. Both are set once, in the fieldType, and apply to every field that uses it.

curl -X POST "http://localhost:8983/solr/products/update?commit=true" \
  -H 'Content-Type: application/json' \
  -d '[
    {
      "id": "1",
      "name": "wireless noise-cancelling headphones",
      "embedding": [0.12, -0.98, 0.33, "...384 floats..."]
    }
  ]'

index="false" (or omitting indexed) stores a vector without building a graph entry for it, which means it can no longer be found by knn — there is no brute-force fallback query on the field the way Elasticsearch’s script_score provides for an unindexed dense_vector. Full attribute reference: Dense Vector Search.

The knn query parser

\{!knn f=<denseVectorField> topK=<k>}[v1,v2,…​] returns the topK documents whose stored vector is closest to the query vector, scored by the field’s configured similarityFunction. f and the vector are required; topK defaults to 10.

curl --get "http://localhost:8983/solr/products/select" \
  --data-urlencode 'q={!knn f=embedding topK=10}[0.11,-0.95,0.30,"...384 floats..."]' \
  --data-urlencode 'wt=json'

Pre-filtering vs. post-filtering

Any fq present on the same request becomes an implicit pre-filter: Solr traverses the HNSW graph restricted to the documents the filters allow, so topK results still come back even when the filter is highly selective. includeTags/excludeTags control which fq clauses feed that pre-filter, and an explicit preFilter local param can be supplied instead of relying on the ambient fq list. This is the opposite of a post-filter — applying fq (or a wrapping bool/fq clause) to an already computed knn result set only thins it out after the fact, so a selective filter can return fewer than topK documents, or none. Prefer pre-filtering whenever the filter is expected to exclude a large share of the collection; it plays the same role as the filter inside Elasticsearch’s knn option.

curl --get "http://localhost:8983/solr/products/select" \
  --data-urlencode 'q={!knn f=embedding topK=10}[0.11,-0.95,0.30,"...384 floats..."]' \
  --data-urlencode 'fq=in_stock:true' \
  --data-urlencode 'fq=price:[* TO 300]' \
  --data-urlencode 'wt=json'

Full parameter list, including the advanced early-termination and ACORN filtered-search tunables: Dense Vector Search.

Using knn as a re-ranker

Rather than searching by vector alone, \{!rerank} lets a cheap first-pass query (typically the usual BM25 q) retrieve a candidate set, then re-scores its top reRankDocs with a second, costlier query — here, a \{!knn} clause — blended in by reRankWeight. This keeps the first pass fast over the whole collection while spending the vector comparison only on documents that already look promising lexically.

curl --get "http://localhost:8983/solr/products/select" \
  --data-urlencode 'q=name:headphones' \
  --data-urlencode 'rq={!rerank reRankQuery=$rqq reRankDocs=200 reRankWeight=2}' \
  --data-urlencode 'rqq={!knn f=embedding topK=200}[0.11,-0.95,0.30,"...384 floats..."]' \
  --data-urlencode 'wt=json'

The full \{!rerank}/ReRank mechanism — including Learning To Rank models, which build on the same two-pass shape — is covered on Spell checking, suggestions & similar documents.

Solr has no dedicated rank-fusion retriever comparable to Elasticsearch’s rrf; combining a lexical clause and a knn clause into one ranking means composing them in the same query tree. The standard/Lucene query parser accepts a knn clause as an OR operand alongside an ordinary field query, so both contribute to a single score:

curl --get "http://localhost:8983/solr/products/select" \
  --data-urlencode 'q=(name:headphones OR {!knn f=embedding topK=20}[0.11,-0.95,0.30,"...384 floats..."])' \
  --data-urlencode 'wt=json'

Because a BM25 term score and a knn similarity score are not on the same scale, an OR combination like this weights whichever side happens to produce larger numbers — it is a starting point, not a calibrated blend. The bool query parser’s should clauses give more control over which side dominates by letting each clause reference a separately-tuned sub-query, and wrapping the whole thing in \{!rerank} (above) is often a better fit when the lexical signal should decide the candidate set and the vector signal should only refine the top of it. See Query parsers for the bool and knn parsers' full local-params syntax.

Integrating an embedding model

Indexing still expects a pre-computed vector — there is no ingest-time processor that turns text into an embedding for you the way Elasticsearch’s semantic_text field does, so an external embedding step (an application-side call to an embedding service, or an update request processor you write) has to run before the document reaches Solr.

At query time, an experimental knn_text_to_vector parser closes part of that gap: it embeds the query text itself, using a model registered in /schema/text-to-vector-model-store (Solr’s model integration is built on LangChain4j), so a plain-text query string can drive a knn search without the caller computing a vector at all.

curl --get "http://localhost:8983/solr/products/select" \
  --data-urlencode 'q={!knn_text_to_vector model=my-embedding-model f=embedding topK=10}headphones that block background noise' \
  --data-urlencode 'wt=json'

model and f are required; topK defaults to 10, same as knn. Because this parser only embeds the query side, indexed documents must already carry vectors produced by the same model — mixing embeddings from two different models in one field silently degrades similarity scores, since the vectors are no longer comparable. Setup and current limitations: Dense Vector Search.

Where to go next