Spell checking, suggestions & similar documents
|
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. |
Several Solr features go beyond returning the documents that literally match q: correcting a
misspelled query, completing it as the user types, listing the raw terms a field actually holds,
finding documents similar to a given one, and re-scoring a candidate set with something more
expensive — up to a trained machine-learning model — than the base query. This page covers the
SpellCheck and Suggester search components, the Terms component, MoreLikeThis, Query
Re-Ranking, and Learning To Rank. All of them sit downstream of ordinary querying and scoring;
see Relevance & scoring for the score they start from and
Function queries for the function syntax several of them embed.
SpellCheck component
The SpellCheck search component suggests corrections for a misspelled query, drawing candidates
from the terms actually indexed in a field rather than a fixed dictionary. It is added to a request
handler in solrconfig.xml alongside one or more named spellchecker implementations, then
triggered per request with spellcheck=true. See
Spell Checking.
curl "http://localhost:8983/solr/techproducts/select" \
--data-urlencode 'q=name:aple' \
--data-urlencode 'spellcheck=true' \
--data-urlencode 'spellcheck.q=aple' \
--data-urlencode 'spellcheck.collate=true' \
--data-urlencode 'spellcheck.count=5' \
--data-urlencode 'wt=json'
DirectSolrSpellChecker
DirectSolrSpellChecker is the default and recommended implementation: it compares query terms
against the main index’s term dictionary directly, with no separate sidecar index to build or keep
in sync, so suggestions are always as fresh as the index itself. Its main tuning knobs are
maxEdits (the maximum Damerau-Levenshtein edit distance considered, 1 or 2), minPrefix (how
many leading characters must match exactly, which both narrows candidates and speeds the lookup),
and maxQueryFrequency (a document-frequency ceiling, absolute or a fraction of the index, above
which a term is assumed already correct and not corrected against).
WordBreakSolrSpellChecker
WordBreakSolrSpellChecker handles a different class of typo: words run together or split apart,
by combining adjacent query terms and/or breaking a single term into two. combineWords and
breakWords (both true by default) toggle each direction; maxChanges caps how many word breaks
or combinations one suggestion may apply, and breakSuggestionTieBreaker (MAX_FREQ, MIN_FREQ,
MAX_FREQ_PROD) picks which candidate wins when several break the same way. It is normally
registered alongside DirectSolrSpellChecker under the same request handler so both run together.
Collation
A per-token correction list is often not directly usable — the caller wants one corrected query to
re-run, not a menu of independent suggestions. spellcheck.collate=true asks Solr to assemble the
individual token corrections back into one query string that is verified to actually return hits
before being returned; spellcheck.maxCollationTries bounds how many candidate collations are
tried, and spellcheck.collateExtendedResults=true returns, per collation, which original term each
correction replaced. spellcheck.alternativeTermCount also asks for corrections on terms that
already match something, useful for a "Showing results for X. Search instead for Y?" experience
even when the original query was not a hard miss. spellcheck.build=true (re)builds the underlying
dictionary; for DirectSolrSpellChecker that build step is cheap since it reads the live index
rather than materializing a copy.
Suggester component
The Suggester search component is a separate, purpose-built autocomplete feature: it serves
prefix (and, with the right implementation, infix) completions from an in-memory structure built
from a dictionary — a field’s values, a query’s results, or a plain word list — rather than
scoring a query against the index at request time. Configure one or more named suggesters under a
suggest search component in solrconfig.xml, each naming a lookupImpl and a dictionaryImpl,
then query it via the /suggest handler. See
Suggester.
Lookup implementations
lookupImpl chooses the in-memory data structure and, with it, what kind of match the suggester
can serve:
lookupImpl |
Behavior |
|---|---|
|
An automaton (finite state transducer) built over the dictionary. Slowest to build and prefix-only, but the smallest memory footprint of the lookups here — a reasonable default when the dictionary is large and rebuilt infrequently. |
|
Extends the (non-FST-named but FST-based) |
|
Matches the query against tokens anywhere in the suggestion, not only its start, by indexing the dictionary into a small internal Lucene index. Supports weighted entries, |
|
The same infix matching as |
|
Predicts the next likely token from an n-gram language model trained on the dictionary text and the tokens already typed, rather than completing the current partial token — closer to predictive text than classic autocomplete. |
Building and querying the suggester
A suggester’s dictionary must be built before it can answer anything, either explicitly with
suggest.build=true, on a schedule via buildOnStartup / buildOnCommit in its solrconfig.xml
definition, or by calling suggest.reload=true to restore a previously built structure from disk
without recomputing it.
# Build the dictionary once (or after enough new content to warrant a refresh).
curl "http://localhost:8983/solr/techproducts/suggest" \
--data-urlencode 'suggest.build=true' \
--data-urlencode 'suggest.dictionary=mySuggester'
# Query it as the user types.
curl "http://localhost:8983/solr/techproducts/suggest" \
--data-urlencode 'suggest.dictionary=mySuggester' \
--data-urlencode 'suggest.q=elec' \
--data-urlencode 'suggest.count=5' \
--data-urlencode 'wt=json'
suggest.dictionary may repeat to query several suggesters in one call and merge their results;
suggest.cfq supplies a context-filter value for a suggester whose dictionary declared a
contextField, scoping suggestions to, say, only one product category. Because the suggester is a
separate structure from the main index, it is a much lower-latency path than running the equivalent
prefix query through the standard request handler — the tradeoff is a build step to keep it in sync.
An analyzer-based alternative — an edge_ngram token filter feeding a plain text field, matched
with an ordinary query at request time — is covered in
Text analysis; it needs no separate build step and composes
with normal filtering/faceting, at the cost of enlarging the index and the flexibility the dedicated
lookups above trade away.
Terms component
The Terms component lists the raw terms actually stored for a field, each with its document
frequency, read directly from the index’s term dictionary — no query, scoring, or analysis of an
input string involved. It is useful for populating a facet-value picker, building a custom
autocomplete without a Suggester, or simply inspecting what a field’s analyzer produced.
terms.fl names the field (repeatable); terms.prefix and terms.regex restrict which terms come
back; terms.limit (default 10) and terms.mincount / terms.maxcount bound how many and which
terms qualify; terms.sort chooses index (lexicographic) or count (by frequency, the default);
and terms.list looks up the document frequency of a specific comma-delimited set of terms instead
of enumerating. See
Terms Component.
curl "http://localhost:8983/solr/techproducts/terms" \
--data-urlencode 'terms.fl=cat' \
--data-urlencode 'terms.prefix=elec' \
--data-urlencode 'terms.sort=count' \
--data-urlencode 'terms.limit=20' \
--data-urlencode 'wt=json'
Terms are returned exactly as stored in the index — after analysis, for an analyzed field — so
terms.fl against a text_general field yields individual lowercased/stemmed tokens, while
terms.fl against a string/keyword field yields whole untouched values; pick the field
accordingly (see Field types).
MoreLikeThis
MoreLikeThis (MLT) finds documents similar to a source document (or a block of free text) by extracting the source’s most "interesting" terms — weighted by term and document frequency, the same intuition behind BM25 — and running the result as an OR’d query against the same or related documents. Solr exposes it three ways. See MoreLikeThis.
Across all three, the shared tuning parameters are mlt.fl (required — the fields to draw
interesting terms from; fields with termVectors="true" in the schema, see
Schema & fields, are far cheaper to use here since the term
statistics are already stored), mlt.mintf (minimum term frequency in the source document for a
term to be considered, default 2), mlt.mindf (minimum document frequency across the index,
default 5), mlt.maxdf / mlt.maxdfpct (a ceiling excluding overly common terms), mlt.minwl /
mlt.maxwl (word-length bounds), mlt.maxqt (maximum interesting terms used in the generated
query, default 25), and mlt.boost (boost each term by its computed interestingness instead of
treating them equally).
MoreLikeThisHandler
The MoreLikeThisHandler is a dedicated request handler (conventionally mounted at /mlt) that
takes either a document q selects or raw content posted as a stream, and returns similar
documents directly as the main response — the source document’s own match, if any, is not what the
caller wants back.
curl "http://localhost:8983/solr/techproducts/mlt" \
--data-urlencode 'q=id:MA147LL/A' \
--data-urlencode 'mlt.fl=cat,manu,features' \
--data-urlencode 'mlt.mindf=1' \
--data-urlencode 'mlt.mintf=1' \
--data-urlencode 'wt=json'
MoreLikeThis search component
The MoreLikeThisComponent instead attaches to an ordinary /select request (mlt=true) and adds
a moreLikeThis section to the response with, for each hit in the main result list, its own set
of similar documents — the right shape for a "customers who viewed this also viewed…" list
rendered alongside a normal search results page, rather than a single lookup.
curl "http://localhost:8983/solr/techproducts/select" \
--data-urlencode 'q=cat:electronics' \
--data-urlencode 'mlt=true' \
--data-urlencode 'mlt.fl=cat,manu,features' \
--data-urlencode 'mlt.count=3' \
--data-urlencode 'wt=json'
The mlt query parser
\{!mlt qf=<fields>}<id> embeds the same similarity logic as a query clause — the counterpart to
the two request-level forms above, usable anywhere a query fragment can appear (fq, bq, a
sub-query) so "similar to this document" composes with ordinary filtering, boosting, and paging
instead of living in a dedicated response section.
curl "http://localhost:8983/solr/techproducts/select" \
--data-urlencode 'q={!mlt qf=cat,manu,features}MA147LL/A' \
--data-urlencode 'fq=inStock:true' \
--data-urlencode 'wt=json'
Query Re-Ranking
Query Re-Ranking runs the request’s normal q as usual to get a candidate set, then re-scores
only the top N of those candidates with a second, typically more expensive query — rather than
running the expensive query against the whole index. It is invoked with rq=\{!rerank} as an
additional (not replacement) parameter alongside q. See
Query Re-Ranking.
reRankQuery (required) is the query to re-score with — ordinarily a $ parameter reference so
its own local params and syntax do not have to be escaped inside rq itself; reRankDocs (default
200) is how many of the top original-query hits get re-scored, with anything beyond that rank kept
at its original score and position; reRankWeight (default 2.0) multiplies reRankQuery’s score
before it is added to the original score, so a positive `reRankQuery score always increases a
document’s combined score even if its magnitude differs wildly from the base query’s.
curl "http://localhost:8983/solr/techproducts/select" \
--data-urlencode 'q=cat:electronics' \
--data-urlencode 'rq={!rerank reRankQuery=$rqq reRankDocs=1000 reRankWeight=3}' \
--data-urlencode 'rqq={!func}product(popularity,recip(ms(NOW,last_modified),3.16e-11,1,1))' \
--data-urlencode 'wt=json'
reRankQuery here is itself a function query, but any
query can fill the role — a DisMax clause with a different qf, a \{!frange}, or, as covered
next, a Learning To Rank model. In fact \{!ltr} below is implemented as its own reranking
QParserPlugin and can be used directly as rq without wrapping it in \{!rerank}.
Learning To Rank
Learning To Rank (LTR) replaces (or augments) a hand-tuned scoring formula with a model trained
offline on labelled relevance judgments — clicks, human ratings, conversions — and applied to
re-rank Solr’s candidate results at query time. Solr does not train the model; LTR’s job is
representing features consistently between offline training and online scoring, and evaluating a
trained model efficiently as a reranker. Enabling it requires loading the solr-ltr contrib jar
via <lib> in solrconfig.xml and declaring the ltr query parser and features document
transformer there. See
Learning To Rank.
Feature stores and features
A feature store is a named, versioned collection of feature definitions — each one a value
computed per document per query, such as a stored field’s value, a
function query result, or a query’s match score/count
against the document (SolrFeature). Features within one store must have unique names; the same
name can be reused across different stores for, say, an A/B-tested feature set. Feature definitions
are uploaded as JSON to a REST endpoint, independent of the schema or solrconfig.xml.
curl -X PUT "http://localhost:8983/solr/techproducts/schema/feature-store" \
-H 'Content-Type: application/json' \
-d '[
{ "store": "myFeatureStore", "name": "popularity",
"class": "org.apache.solr.ltr.feature.FieldValueFeature",
"params": { "field": "popularity" } },
{ "store": "myFeatureStore", "name": "priceMatch",
"class": "org.apache.solr.ltr.feature.SolrFeature",
"params": { "q": "{!func}recip(price,1,1000,1000)" } },
{ "store": "myFeatureStore", "name": "originalScore",
"class": "org.apache.solr.ltr.feature.OriginalScoreFeature", "params": {} }
]'
A feature’s params can also reference external feature info (efi.*) supplied at query time — the user’s location, session context, or anything not derivable from the document alone — through
a $\{efi.name} placeholder, letting the same trained model take request-specific signals into
account.
Models
A model is a JSON document naming a feature store plus the trained weights/structure that turn a
feature vector into a single re-rank score, uploaded to the model store the same way. Solr ships
LinearModel (a weighted sum, for models like logistic regression), MultipleAdditiveTreesModel
(gradient-boosted trees, e.g. LambdaMART, expressed as an ensemble of decision trees), and
NeuralNetworkModel; a custom model class can be plugged in for anything else.
curl -X PUT "http://localhost:8983/solr/techproducts/schema/model-store" \
-H 'Content-Type: application/json' \
-d '{
"store": "myFeatureStore",
"name": "myModel",
"class": "org.apache.solr.ltr.model.LinearModel",
"features": [
{ "name": "popularity" },
{ "name": "priceMatch" },
{ "name": "originalScore" }
],
"params": {
"weights": { "popularity": 1.0, "priceMatch": 0.5, "originalScore": 2.0 }
}
}'
The \{!ltr} query and the [features] transformer
Once a model is stored, rq=\{!ltr model=<name> reRankDocs=<n>} re-ranks the top reRankDocs
matches from q using that model — \{!ltr} behaves like \{!rerank} internally (it re-scores a
bounded top-N rather than the whole result set) so the two are not normally combined for the same
request. Any efi.* parameters the model’s features reference are passed alongside it.
curl "http://localhost:8983/solr/techproducts/select" \
--data-urlencode 'q=cat:electronics' \
--data-urlencode 'rq={!ltr model=myModel reRankDocs=100 efi.userRegion=EU}' \
--data-urlencode 'fl=id,name,score,[features store=myFeatureStore]' \
--data-urlencode 'wt=json'
The [features] document transformer (added
to fl in square brackets, like any other transformer) returns each returned document’s raw
feature values instead of, or alongside, the model’s final score — essential for logging training
data for the next offline training run, and for debugging why a model ranked a document where it
did. logAll=true logs every feature in the named store rather than only the ones the active model
uses; format=dense (default) or sparse controls how zero/absent values are represented.
Feature selection, labelling, and model training all happen outside Solr; only the trained model
and its feature definitions come back in. A typical cycle logs features for production traffic with
[features], trains a new model offline against click or judgment data, and uploads the result to
the model store to replace the active model — Solr itself never re-trains anything.
Related pages
-
Relevance & scoring — the base score that Query Re-Ranking and LTR start from and can add to or replace.
-
Function queries — the function syntax feature definitions,
bf/boost, andreRankQueryall build on. -
Text analysis — the
edge_ngram/analyzer-based alternative to the Suggester component for prefix autocomplete. -
Schema & fields —
termVectorsand other field options MoreLikeThis and LTR feature extraction rely on for performance. -
Query parsers — local params syntax (
\{!name …}), which\{!mlt},\{!rerank}, and\{!ltr}all use.