Reciprocal Rank Fusion on free Elasticsearch: licensing, workarounds, and the OpenSearch alternative

Jun 03, 20268 min read

Reciprocal Rank Fusion (RRF) on Elasticsearch is gated to the Enterprise tier. On the Basic/free tier, querying with the rrf retriever returns a 403:

{ "error": { "root_cause": [ { "type": "security_exception", "reason": "current license is non-compliant for [Reciprocal Rank Fusion (RRF)]", "license.expired.feature": "Reciprocal Rank Fusion (RRF)" } ], "type": "security_exception", "reason": "current license is non-compliant for [Reciprocal Rank Fusion (RRF)]", "license.expired.feature": "Reciprocal Rank Fusion (RRF)" }, "status": 403 }

This has held since RRF first appeared as a technical preview in 8.8 (May 2023), through the 8.16 GA release (November 2024), and into the 9.x line. The linear retriever added in 8.18 / 9.0 is gated the same way, also at Enterprise. Three practical workarounds exist: implement RRF yourself in application code, combine BM25 and vector results with a normalized linear combination, or move to OpenSearch - which ships RRF, score normalization (including z-score), and late-interaction reranking by a field under Apache 2.0 license.

What RRF actually does

Reciprocal Rank Fusion combines multiple ranked result sets into a single ranking based on document positions rather than raw scores. The formula is:

score(d) = Σ 1 / (k + rank_i(d))

where rank_i(d) is the document's position in the i-th result list and k is a smoothing constant. The original paper from Cormack, Clarke, and Büttcher (SIGIR 2009) used k=60 and noted the choice was not critical. Their Table 1 shows MAP barely moves across k ∈ [10, 100] - only at the extremes (k=0 or k=500) does the score drop meaningfully. Elastic and OpenSearch both default the RRF rank constant to 60, following the original paper's convention.

The reason RRF matters for hybrid search: BM25 scores are unbounded and query/corpus-dependent, while vector similarity scores live on a different scale and distribution depending on the similarity function and embedding model. Naively summing them lets one method dominate purely from scale. RRF discards the scores entirely and works only with ranks, which sidesteps the normalization problem with no tuning required.

What Basic actually gets you, and what it doesn't

The Elastic subscriptions matrix splits hybrid-search-related features across three paid tiers. Knowing what's behind each wall is more useful than the headline "RRF is paid":

FeatureBasic / freePlatinumEnterprise
Vector search (kNN)
Standard, kNN, pinned, rescorer retrievers
Similarity functions for vector fields
Synonym management
ELSER (learned sparse encoder)
Elastic Rerank
Inference API
RRF for hybrid search
Linear, rule, text similarity re-ranker retrievers
Rank Vectors (for MaxSim)
DiskBBQ
Indexing vectors with GPUs
Query Rules
Learning to Rank

Source: https://www.elastic.co/subscriptions

The pattern is clear. Basic gives you the building blocks for client-side hybrid search - kNN runs, BM25 runs, both can be queried separately. Platinum unlocks Elastic-managed inference and ELSER. Enterprise is where the actual modern hybrid search features live: rank fusion, learned sparse rerankers, late interaction, GPU vector indexing, learning to rank.

If you're on Basic and you want hybrid search, you're either reimplementing pieces of Enterprise in application code, switching engines, or starting a trial.

The licensing timeline

The gating has been continuous and has expanded, not relaxed. The August 2024 license change that re-added AGPLv3 to Elasticsearch made the source code open source again but did not change which features the default ELv2 distribution gates behind paid tiers.

VersionDateWhat changed
8.4August 2022First hybrid search support
8.8May 2023RRF added as technical preview, gated
8.14June 2024Retrievers framework introduced
Pre-8.16August 2024Elastic announced AGPLv3 as an additional source-code license option; feature-tier gating in the default distribution unchanged
8.16November 2024RRF and retrievers reach GA, gated to Enterprise
8.18 / 9.0April 2025Linear retriever added, also gated to Enterprise
9.0.1 BasicJune 2025Linear retriever still throws license error in production
Late 2025September 2025Per-retriever weights added to RRF retriever (still Enterprise-gated)

Self-managed deployments can start a 30-day trial that gives access to all subscription features, including Enterprise-tier features, for evaluation. Elastic Cloud trials are 14 days. Elastic also publishes a trial extension form that grants one additional 30-day extension on request.

Workaround 1: Implement RRF in your application

Run BM25 and kNN as two separate queries against Elasticsearch Basic, then fuse the result lists in application code. The fusion logic is roughly seven lines of Python:

from collections import defaultdict def rrf_fusion(rankings: list[list[str]], k: int = 60) -> list[tuple[str, float]]: """Combine multiple ranked lists of document IDs using Reciprocal Rank Fusion.""" scores: dict[str, float] = defaultdict(float) for ranking in rankings: for rank, doc_id in enumerate(ranking, start=1): scores[doc_id] += 1.0 / (k + rank) return sorted(scores.items(), key=lambda x: x[1], reverse=True)

Calling it with a BM25 result list and a kNN result list returns the fused ranking:

bm25_ids = [hit["_id"] for hit in es.search(index=idx, query=match_query)["hits"]["hits"]] knn_ids = [hit["_id"] for hit in es.search(index=idx, knn=knn_query)["hits"]["hits"]] fused = rrf_fusion([bm25_ids, knn_ids]) top_10_ids = [doc_id for doc_id, _ in fused[:10]]

The trade-offs are clear. You pay two round trips instead of one, you lose retrievers-specific features like inner_hits and the unified pagination model, and you have to rehydrate the document _source after fusion - typically with an mget call against the top IDs. In return, you get RRF ranking on the Basic tier with no licensing exposure. In many RAG-style systems embedding generation and kNN latency dominate, but the actual breakdown depends on cache state, candidate window size, and deployment topology - measure for your workload before assuming the extra round trip is free.

Workaround 2: Linear combination with manual normalization

The Basic tier still allows two separate queries fused with a weighted sum, as long as the math happens in your application rather than through the gated linear retriever. Min-max normalize both score sets per query - using the min and max from each result list as the range - then combine with a weight α:

from collections import defaultdict def minmax_normalize(score: float, lo: float, hi: float) -> float: rng = hi - lo return 1.0 if rng == 0 else (score - lo) / rng def linear_fusion(bm25_hits: list[dict], knn_hits: list[dict], alpha: float = 0.5): scores: dict[str, float] = defaultdict(float) if bm25_hits: s = [h["_score"] for h in bm25_hits] lo, hi = min(s), max(s) for h in bm25_hits: scores[h["_id"]] += alpha * minmax_normalize(h["_score"], lo, hi) if knn_hits: s = [h["_score"] for h in knn_hits] lo, hi = min(s), max(s) for h in knn_hits: scores[h["_id"]] += (1 - alpha) * minmax_normalize(h["_score"], lo, hi) return sorted(scores.items(), key=lambda x: x[1], reverse=True)

When you have labeled query data, calibrated linear can beat RRF. Elastic's research on ELSER + BM25 across BEIR reports that around 40 annotated queries are enough for linear combination to start outperforming RRF, and with 300 calibration queries the optimized linear combination achieved a 6% NDCG@10 improvement over ELSER alone - compared to RRF's 1.4% improvement over the same baseline. Without calibration data, RRF is the safer default - it requires no tuning and is far less sensitive to score distribution mismatches.

Workaround 3: Switch to OpenSearch

OpenSearch ships hybrid search and RRF under Apache 2.0 with no feature-tier licensing. The native hybrid query and normalization-processor (min_max, L2) landed in 2.10 (September 25, 2023). Native RRF landed in 2.19 (February 11, 2025). The 3.x line has continued to invest heavily in vector and hybrid search since then.

VersionDateHybrid search additions
2.10September 25, 2023First hybrid query, normalization-processor (min_max, L2)
2.19February 11, 2025Native RRF (score-ranker-processor), pagination support, hybrid_score_explanation
3.0May 6, 2025z-score normalization, lower bound for min-max, inner hits in hybrid, GPU acceleration for vector index builds (experimental)
3.1June 24, 2025GPU acceleration for vector index builds GA, hybrid query performance improvements (up to 65% latency reduction)
3.3October 14, 2025Up to 20% faster hybrid for lexical subqueries, lateInteractionScore for reranking by a field using externally hosted ColBERT/ColPali models

The trend continued after 3.3: OpenSearch 3.4, released in December 2025, added further vector-search investment such as k-NN memory-optimized search warmup, native FP16 vector scoring, and JDK 25 support. OpenSearch 3.5 followed in February 2026, and 3.6.0 - the project's first long-term support release - was published April 7, 2026. Refer to the OpenSearch version history and downloads page for the current state.

The score-ranker-processor documentation also shows that custom subquery weights are supported via the parameters.weights array on RRF - useful when you want to weight the BM25 and vector legs differently rather than treat them equally. Elasticsearch added per-retriever weights to its RRF retriever in late 2025 as well, but because the RRF retriever itself is Enterprise-gated, this doesn't change the Basic-tier workaround story.

The mapping to Elasticsearch's paid tiers is striking. Several broadly comparable capabilities that are Enterprise-only in Elasticsearch's built-in implementation are open-source in OpenSearch:

CapabilityElasticsearch tierOpenSearch tier
RRF rank fusionEnterpriseApache 2.0 (since 2.19)
Built-in weighted score fusionEnterprise (linear retriever)Apache 2.0 score-based hybrid normalization/combination via search pipelines (since 2.10)
Multiple normalization methods (min-max, L2, z-score)EnterpriseApache 2.0 (z-score in 3.0)
Late-interaction reranking by a field (ColBERT/ColPali workflows)Enterprise (Rank Vectors)Apache 2.0 (lateInteractionScore in 3.3)
GPU acceleration for vector index buildsEnterpriseApache 2.0 (3.0 experimental, 3.1 GA)
Learning to RankEnterpriseApache 2.0 (LTR plugin)

If the project is greenfield and does not depend on Elastic-specific features such as ELSER, Elastic Rerank, ES|QL, or the managed Inference API integrations, OpenSearch is the cleanest path. The migration cost from an existing Elasticsearch deployment is non-trivial - index format compatibility, client library differences, and Kibana versus OpenSearch Dashboards differences all add up - but the functional gap between the free tiers has generally widened for hybrid and vector-search use cases.

When paying for Enterprise makes sense

The trial-then-pay path is reasonable when the team needs text_similarity_reranker for semantic reranking with hosted models, the calibrated linear retriever, MaxSim with rank vectors for ColBERT-style retrieval, GPU vector indexing for billion-scale corpora, learning-to-rank pipelines, or production support contracts. None of this comes for free, and reimplementing it is more expensive than the license for organizations with substantial search infrastructure. For teams whose only blocker is RRF specifically, the workarounds above usually win on cost and are straightforward to implement for simple two-leg hybrid retrieval.

What most teams actually need

Most teams that hit the RRF license error don't actually need RRF specifically - they need hybrid search to work. Manual linear combination with normalized scores is often sufficient for a first production hybrid-search implementation on any Elasticsearch version. The core RRF scoring loop is another twenty lines of code on top of that - production deployments will additionally want pagination, deduplication, an mget rehydration step, tie-breaking, and observability around the fused ranking. And for projects starting fresh in 2026, the open-source answer keeps getting better with every OpenSearch release.

Frequently Asked Questions

Is RRF free in Elasticsearch?

No, Platinum or Enterprise only. Basic returns a 403 license error.

What does the license error look like?

HTTP 403 with security_exception and reason "current license is non-compliant for [Reciprocal Rank Fusion (RRF)]".

Did the AGPLv3 relicense in 2024 unlock RRF?

No. Source is open; feature gating in the default distribution is unchanged.

When did RRF reach GA?

Elasticsearch 8.16, November 8, 2024.

Is the linear retriever free?

No, gated identically to RRF since 8.18 / 9.0.

Cheapest fix for RRF on Basic?

Run BM25 and kNN separately, fuse ranks in app code (~7 lines of Python).

What's the formula?

score(d) = Σ 1 / (k + rank_i(d)), with k=60 by default.

Why k=60?

Cormack et al. (2009) showed results are stable for k between 10 and 100; 60 became the convention.

Does OpenSearch have RRF for free?

Yes, Apache 2.0, no tier gating. Native RRF shipped in OpenSearch 2.19 (February 2025).

RRF or weighted linear combination?

RRF if you have no labeled queries. Calibrated linear wins past ~40 annotated queries (Elastic's BEIR research).

How long is the Platinum trial?

30 days self-managed, 14 days on Elastic Cloud, with one 30-day extension available on request.

Worth migrating to OpenSearch just for RRF?

No, if you only need RRF — implement it in app code. Yes, if you're greenfield and don't need ELSER, ES|QL, or semantic reranking.

Does the app-code workaround hurt latency?

Adds one round trip, but embedding generation and kNN dominate latency anyway.

Why not just sum BM25 and cosine scores?

Different scales — BM25 is unbounded, cosine sits in [-1, 1]. BM25 dominates unless you normalize.

What do you lose vs the native rrf retriever?

inner_hits, unified pagination, and built-in _source hydration (use mget to rehydrate).

What is Reciprocal Rank Fusion (RRF)?

RRF combines multiple ranked search result lists into a single ranking using document positions instead of raw scores.

Why is RRF useful for hybrid search?

It avoids score normalization problems by relying on ranks rather than incompatible BM25 and vector similarity scores.

Is RRF available in Elasticsearch Basic?

No. RRF is restricted to the Enterprise subscription tier and returns a license error on Basic.

Can I implement RRF myself?

Yes. You can run separate BM25 and vector queries and fuse the results in application code.

What is the default RRF rank constant?

Elasticsearch and OpenSearch both use a default rank constant of 60, matching the original research paper.

Why do I get a 403 error when using the rrf retriever?

The error indicates your Elasticsearch license does not include the Enterprise-only RRF feature.

My BM25 scores dominate vector scores. How can I fix this?

Normalize both score sets before combining them, or use RRF which avoids score scaling issues entirely.

I need hybrid search but can't justify Enterprise pricing. What are my options?

Implement RRF yourself, use normalized linear score fusion, or migrate to OpenSearch.

Why does hybrid search quality seem inconsistent across queries?

BM25 and vector scores vary significantly between queries, making naive score combination unreliable.

When should I consider OpenSearch instead of Elasticsearch?

OpenSearch is worth evaluating when you need open-source hybrid search features such as RRF, score normalization, or reranking without Enterprise licensing costs.

RELATED POSTS
Michał Miler
Michał Miler
Senior Software Engineer

How to Deploy Payload CMS on AWS Amplify with MongoDB Atlas for Free

May 27, 202611 min read
Article image
Michał Miler
Michał Miler
Senior Software Engineer

Keep Your Next.js App Warm on AWS Amplify Using Lambda + EventBridge

May 25, 20265 min read
Article image
Bartłomiej Gałęzowski
Bartłomiej Gałęzowski
Senior Software Engineer

Dynamic Email Domain Validation in Keycloak with a Custom Authenticator

May 20, 20266 min read
Article image