Skip to main content
Version: 0.13.0

Score ranker processor

The score-ranker-processor is a search phase results processor that runs between the query and fetch phases of search execution. Like the normalization processor, it combines the results of the sub-queries in a hybrid query — but it combines them by rank rather than by score, using Reciprocal Rank Fusion (RRF).

note

The processor name is hyphenated: score-ranker-processor. score_ranker_processor is rejected with Invalid processor type.

Choosing between rank fusion and score normalization

Both processors answer the same question — "these sub-queries returned different documents with incomparable scores, so what is the final order?" — and they answer it differently.

score-ranker-processornormalization-processor
Combines onPosition in each result listScore value, after normalizing each list
Sensitive to score scaleNoYes
Sensitive to score gapsNo — only order survivesYes — a runaway top score dominates
Needs tuning per corpusRarelyOften, via weights and bounds
Good default whenSub-query scores are not comparable (BM25 vs. cosine distance)Sub-query scores are comparable, or you want gap size to matter

The practical difference is that RRF rewards agreement. A document ranked second by both sub-queries can beat a document ranked first by one and last by the other, because RRF never sees how far ahead that first-place score was. The example below shows exactly that happening.

Request fields

FieldData typeRequired/OptionalDescription
combinationObjectOptionalCombination settings. If omitted, the processor behaves as if technique were rrf with default settings.
combination.techniqueStringOptionalOne of rrf, arithmetic_mean, geometric_mean, harmonic_mean, or bayesian. Default is rrf. The normalization-only techniques min_max and l2 are not valid here and are rejected with provided combination technique is not supported.
combination.rank_constantIntegerOptionalThe k in the RRF formula. Must be between 1 and 10000. Default is 60. Lower values make the top few ranks dominate; higher values flatten the contribution of rank position.
combination.parameters.weightsArray of floatsOptionalPer-sub-query weights, in the order the sub-queries appear in the hybrid query. The only key parameters accepts — anything else is rejected with supported parameters are [weights].
tagStringOptionalAn identifier for the processor.
descriptionStringOptionalA description of the processor.
ignore_failureBooleanOptionalWhether the pipeline continues if this processor fails. Default is false.

How the score is calculated

For each document, the processor sums one term per sub-query that returned it:

score = Σ  1 / (rank_constant + rank)

rank is the document's 1-based position within that sub-query's result list. A sub-query that did not return the document contributes nothing. With the default rank_constant of 60, a document ranked first in one list and absent from the other scores 1/61 = 0.01639.

Example

The following example uses a four-document index whose vectors are small enough to read, so the arithmetic can be checked by hand.

Creating the index

PUT /articles
{
"settings": { "index": { "knn": true, "number_of_shards": 1, "number_of_replicas": 0 } },
"mappings": {
"properties": {
"title": { "type": "text" },
"embedding": { "type": "knn_vector", "dimension": 4, "space_type": "l2" }
}
}
}
tip

Create the index before you index documents. A bulk request against a missing index creates it with a dynamic mapping, which types embedding as a plain float array rather than a knn_vector, and the k-NN sub-query then fails to match anything.

Indexing the documents

POST /_bulk?refresh=true
{"index":{"_index":"articles","_id":"1"}}
{"title":"Once-daily dosing and adherence in hypertension","embedding":[0.0,0.0,0.9,0.1]}
{"index":{"_index":"articles","_id":"2"}}
{"title":"Cardioselective agents in post-infarction management","embedding":[0.9,0.1,0.0,0.0]}
{"index":{"_index":"articles","_id":"3"}}
{"title":"Adherence patterns in long-term combination therapy","embedding":[0.7,0.3,0.1,0.0]}
{"index":{"_index":"articles","_id":"4"}}
{"title":"Peripheral oedema in calcium channel therapy","embedding":[0.1,0.2,0.8,0.1]}

What each sub-query returns on its own

The lexical sub-query matches only two documents:

RankDocumentScore
11 Once-daily dosing and adherence in hypertension1.83924
23 Adherence patterns in long-term combination therapy0.67200

The vector sub-query returns all four, in a different order:

RankDocumentScore
12 Cardioselective agents in post-infarction management1.00000
23 Adherence patterns in long-term combination therapy0.91743
34 Peripheral oedema in calcium channel therapy0.43478
41 Once-daily dosing and adherence in hypertension0.37879

Note that document 3 is second in both lists and first in neither.

Creating the search pipeline

PUT /_search/pipeline/rrf-pipeline
{
"description": "Reciprocal rank fusion for hybrid search",
"phase_results_processors": [
{
"score-ranker-processor": {
"combination": {
"technique": "rrf",
"rank_constant": 60
}
}
}
]
}

Using the search pipeline

GET /articles/_search?search_pipeline=rrf-pipeline
{
"query": {
"hybrid": {
"queries": [
{ "match": { "title": "adherence dosing" } },
{ "knn": { "embedding": { "vector": [0.9, 0.1, 0.0, 0.0], "k": 4 } } }
]
}
}
}

The fused order:

RankDocumentScoreWhere it came from
13 Adherence patterns in long-term combination therapy0.032261/62 + 1/62 — second in both lists
21 Once-daily dosing and adherence in hypertension0.032021/61 + 1/64 — first lexically, last by vector
32 Cardioselective agents in post-infarction management0.016391/61 — first by vector, no lexical match
44 Peripheral oedema in calcium channel therapy0.015871/63 — third by vector, no lexical match

Document 3 wins without having led either sub-query. It is the only document both sub-queries agree is near the top, and rank fusion rewards that agreement. Document 1's commanding lexical score of 1.83924 buys it nothing beyond first place in that one list.

The same query with score normalization

Running the identical query through a normalization-processor instead produces a different winner:

PUT /_search/pipeline/norm-pipeline
{
"phase_results_processors": [
{
"normalization-processor": {
"normalization": { "technique": "min_max" },
"combination": { "technique": "arithmetic_mean" }
}
}
]
}
RankDocumentScore
11 Once-daily dosing and adherence in hypertension0.50050
22 Cardioselective agents in post-infarction management0.50000
33 Adherence patterns in long-term combination therapy0.43404
44 Peripheral oedema in calcium channel therapy0.04507

Here the two documents that led a list finish first and second, and the consensus document 3 drops to third. Neither ordering is correct in the abstract — pick the processor whose behavior matches what you want relevance to mean for your corpus.

Weighting the sub-queries

weights scales each sub-query's contribution, in the order the sub-queries appear in the hybrid query:

PUT /_search/pipeline/rrf-weighted
{
"phase_results_processors": [
{
"score-ranker-processor": {
"combination": {
"technique": "rrf",
"rank_constant": 60,
"parameters": { "weights": [0.2, 0.8] }
}
}
}
]
}

Weights scale the fused scores but change the order only when they are large enough to overcome a rank difference. In the example above, weighting the vector sub-query at 0.8 leaves the order unchanged and simply rescales the scores. Verify the effect on your own data rather than assuming a weight change has reordered anything.

Next steps