skip to content

In an Elasticsearch mapping, how do norms and index_options change BM25 scoring on a field?

level: middleimportance: should knowfreq 42%

answer

  1. One stores length, one stores what postings hold
  2. Turning one off makes b pointless
  3. The other has four rungs, each a superset
  4. Phrase queries need the third rung
  5. Disabling is easy, restoring means reindexing

basics

~20 s

Norms store each document's field length, so disabling them removes BM25's length normalisation and makes the b parameter irrelevant. index_options decides whether frequencies and positions are indexed; without frequencies BM25 cannot reward repeated terms, and without positions phrase queries stop working.

solid answer

~40 s

Both are mapping parameters that decide what scoring data exists on disk. **norms** hold the per-document field length that BM25 needs for length normalisation. Setting `"norms": false` saves roughly a byte per document per field but flattens scoring: long and short fields become interchangeable and the `b` parameter has nothing to work with. It can be disabled on a live field with the update mapping API, and existing norms disappear as segments merge — but it cannot be re-enabled without reindexing. **index_options** is a ladder: `docs`, `freqs`, `positions`, `offsets`. `text` fields default to `positions`; `keyword` fields default to `docs`. Drop to `freqs` and phrase queries fail; drop to `docs` and term frequencies are not stored at all, so ten occurrences score like one. Trim these only on fields you never rank on.

code

json · 7 lines
json
{
  "properties": {
    "title":    { "type": "text" },
    "log_line": { "type": "text", "norms": false, "index_options": "freqs" },
    "status":   { "type": "keyword" }
  }
}

go deeper

for a junior

Recognise norms and index_options as mapping parameters on a field, and know that they control what scoring information Elasticsearch stores at index time.

for a middle

Explain that norms hold field length for BM25's length normalisation and that index_options decides whether frequencies and positions exist, then connect each to the b and k1 parameters and to phrase queries.

for a senior

Weigh the storage saving against the relevance and query capability you give up, and remember these are index-time decisions: disabling norms is one-way and undoing it means a reindex.

for a principal

Treat these as mapping-design policy for high-volume indices — decide up front which fields are ranked on and which are filter-only, and encode that in index templates rather than discovering it during a relevance incident.

## Norms: the length half of BM25 BM25's denominator contains `(1 - b + b * dl / avgdl)`, where `dl` is the length of this field in this document. That length is not recomputed at query time — it is written at index time into a per-document structure called **norms** (a single lossily-encoded byte per document per field). Norms are why a three-word title matching "kotlin" outranks a 5,000-word body matching it once. Setting `"norms": false` in the field mapping stops writing them. The consequences: - **Length normalisation disappears.** Every matching field is treated as if it were of average length, so the `b` parameter no longer has any effect on that field. Scoring reduces to term frequency and idf. - **You save space and heap.** One byte per document per field sounds trivial, but on an index with hundreds of fields and billions of documents it is not, and norms are read during scoring. - **It is a one-way door.** Norms can be disabled on an existing field with the update mapping API; existing norms are dropped lazily as segments are merged. Turning them back on only affects newly indexed documents, so restoring length normalisation for the whole corpus means reindexing. The standard candidates for disabling norms are fields you filter or aggregate on but never rank by — status flags, identifiers, log metadata. Note that `keyword` fields have norms off by default already, which is one reason a `term` query on a keyword field is driven almost entirely by idf. ## index_options: the frequency and position ladder `index_options` controls what the postings list stores for each term, and each rung is a superset of the one below: - **`docs`** — only which documents contain the term. Term frequency is unavailable, so BM25 treats every occurrence count as one: a document mentioning the term twenty times scores exactly like one mentioning it once. This is the default for `keyword` fields. - **`freqs`** — adds the number of occurrences per document, so BM25's tf component becomes meaningful and `k1` starts to matter. - **`positions`** — adds token positions. This is the default for `text` fields, and it is what phrase queries (`match_phrase`, `span` queries, phrase-aware `multi_match` modes) require. Without positions those queries cannot run on the field. - **`offsets`** — adds character offsets, used by the unified highlighter to highlight without re-analysing the field. Each rung costs disk and indexing time. Dropping a rarely-searched `text` field from `positions` to `freqs` is a real saving on a large index, but only if nothing ever issues a phrase query against it — and "nothing ever" tends to be a promise the product team has not made. ## How the two interact with BM25 Put side by side, the two parameters remove different halves of BM25: | Setting | What BM25 loses | |---|---| | `norms: false` | field-length normalisation; `b` has no effect | | `index_options: docs` | term frequency; `k1` has no effect, repeats do not help | | both | scoring collapses to idf alone | That last row is worth remembering: a `text` field with norms off and `index_options: docs` scores essentially like a boolean-with-idf match. If that is what you wanted, saying so via the `boolean` similarity is clearer, cheaper and reversible. ## Diagnosing it after the fact When relevance looks strangely flat — long and short documents interleaved, repeated terms not helping — check the mapping before touching the query. The explain output helps too: if the tf node reports the same value for documents with obviously different field lengths, norms are not there. Because the effects of these parameters are baked in at index time, the fix is often a reindex, which is why they deserve thought when the mapping is designed rather than after a relevance complaint. ## The sensible default Leave both alone unless you have a measured storage problem. The defaults — norms on and `positions` for `text`, norms off and `docs` for `keyword` — are already the right shape for the intended use of each type. Trim them on the fields you can name and justify: high-volume log fields, denormalised copies used only for filtering, and fields that exist purely as aggregation keys.

  • You disabled norms on a live field and now want length normalisation back. What does that take?
    A reindex. Update mapping can turn norms off, and the existing ones are discarded as segments merge, but turning the parameter back on only affects documents indexed after the change. Restoring correct length normalisation across the whole corpus means reindexing into an index whose mapping has norms enabled from the start.
  • Why do keyword fields have norms disabled by default?
    Because a keyword field holds one whole untokenised value, so field length carries no relevance signal worth a byte per document. The consequence is that a scored term query on a keyword field is driven almost entirely by idf — rarer values score higher — which surprises people who expected every exact match to score alike.
  • What happens to a match_phrase query on a field mapped with index_options set to freqs?
    It cannot run: phrase matching needs token positions, and freqs stops one rung below positions. Elasticsearch rejects the query for that field rather than silently returning wrong results. That is why dropping a text field below the positions default is only safe when you can guarantee no phrase-aware query ever targets it.

saying these in an interview costs you the question

  • Thinks norms store term frequencies
  • Believes norms can be re-enabled with an update mapping call
  • Says index_options only affects storage, never scoring
  • Assumes text and keyword share the same index_options default
  • Drops a text field to freqs while phrase queries still target it

context