Overview
Hybrid search in Fess combines traditional keyword search (BM25) with semantic (vector) search and merges the two result sets with Rank Fusion to produce more accurate, more relevant rankings. Rank Fusion integrates the results of multiple searchers into a single optimized ranking.
In Fess 15.8, semantic search (content chunking + vector search) is provided as a core feature. Once you enable it, the semantic searcher is registered with Rank Fusion automatically. See Semantic Search (Content Chunking + Vector Search) for how to configure it.
The Rank Fusion feature of Fess integrates multiple search results to provide more accurate search results.
What is Rank Fusion
Rank Fusion is a technique that combines results from multiple search algorithms or scoring methods (for example keyword/BM25 and semantic/vector search) to generate a single optimized ranking.
Key benefits:
Combines the strengths of different algorithms
Improves search accuracy
Provides diverse search results
Supported Algorithm
Fess supports the RRF (Reciprocal Rank Fusion) algorithm for Rank Fusion.
RRF (Reciprocal Rank Fusion)
RRF calculates a score by summing the reciprocal of each document’s rank in each search result. When a document is retrieved by multiple searchers, its scores are added together.
Formula:
k: Constant parameter that controls the influence of rank (default: 20)rank(d): Rank of document d in each search result (0-based)Σ: Sum over all searchers in which document d appears
Note
The fusion algorithm is fixed to RRF; there is no setting to switch to a different algorithm. Per-searcher weighting is not supported either: every searcher contributes with the same weight when the scores are summed. rank.fusion.rank_constant is the only setting that lets you adjust the ranking behavior.
Settings
fess_config.properties
Basic configuration:
| Property | Default | Description |
|---|---|---|
rank.fusion.window_size | 200 | Maximum number of results retrieved from each searcher for fusion. Must be >= paging.search.page.max.size × 2 (200 by default); if a smaller value is set, it is automatically raised to this minimum (a WARN log is written at startup). |
rank.fusion.rank_constant | 20 | The constant k in the RRF formula. A larger value reduces the score difference between higher- and lower-ranked results. |
rank.fusion.threads | -1 | Number of threads in the fixed thread pool used to run multiple searchers in parallel. If 0 or less is specified, availableProcessors × 3 / 2 + 1 is used automatically (the calculation uses integer arithmetic, so the fractional part is truncated: for example, 4 cores → 7 and 5 cores → 8). |
rank.fusion.score_field | rf_score | Name of the result-document field used to store the fused score. |
Note
When changes take effect
All four settings above require Fess to be restarted before a change takes effect. Values read from fess_config.properties are cached in the JVM, so editing the file while Fess is running has no effect.
For reference, rank.fusion.window_size is read once at startup and rank.fusion.threads is read when the thread pool is created. The thread pool is created when a searcher other than default (such as the semantic searcher) is registered, so if semantic search is disabled, no thread pool is created at all.
JVM System Properties
The searchers to use are specified as a JVM system property. Add the following to fess.in.sh:
For fess.in.bat, write it as follows:
This property behaves as follows:
It is set as a JVM option, not in
fess_config.properties. Specify the key namerank.fusion.searchersexactly as-is. The prefixed forms that are common for other settings,-Dfess.config.and-Dfess.system.(for example-Dfess.config.rank.fusion.searchers), are not recognized.Instead of a JVM option, you can also enter it on a single line, such as
rank.fusion.searchers=default,semantic_chunk, in the “System Property” field under System > General in the administration UI. Note that a value in that field is applied only when a system property of the same name is not already set. A-Doption therefore takes precedence, and changing a value that has already been applied requires restarting Fess.defaultis the searcher that performs standard keyword search and is always available.A searcher’s name is derived from its implementation class name by removing the trailing
Searcherand decamelizing the remainder into lowercase snake_case (SemanticChunkSearcher→semantic_chunk). The core-integrated semantic searcher (Semantic Search (Content Chunking + Vector Search)) is registered under the namesemantic_chunk.If this property is not specified, all registered searchers are used. If none of the specified names match a registered searcher, only the
defaultsearcher is used. If you use the core-integrated semantic searcher (Semantic Search (Content Chunking + Vector Search)), you normally do not need to set this property at all.Result fusion is performed only when two or more searchers are available. When only one searcher is available, no fusion is performed and normal search results are returned.
Warning
If you previously used the fess-webapp-semantic-search plugin from Fess 15.7 or earlier, you may have been told to set this property to -Drank.fusion.searchers=default,semantic. That plugin registered its searcher under the name semantic, which is a different searcher from the core-integrated searcher’s name, semantic_chunk, introduced in 15.8. If you carry that 15.7-era setting forward into 15.8 as-is, the allowlist never includes semantic_chunk, so the core-integrated semantic search (content chunking + vector search) does not work at all — Fess silently keeps returning ordinary keyword search results (a warning is logged at startup, but the per-request exclusion itself is only logged at DEBUG level). If your configuration specifies default,semantic, either remove this setting or add semantic_chunk to it. See “Migrating from 15.7 or Earlier” in Semantic Search (Content Chunking + Vector Search) for details.
Integration with Hybrid Search
Rank Fusion is particularly effective for hybrid search, which combines keyword search and semantic search. To use semantic search, configure the content chunking feature and then set content_chunker.search.enabled=true.
Warning
The content_chunker.* settings, such as content_chunker.enabled and content_chunker.search.enabled, are system properties, not fess_config.properties settings. Write them in conf/system.properties, or specify them as JVM options such as -Dfess.system.content_chunker.search.enabled=true. Writing them in fess_config.properties has no effect. In addition, content_chunker.search.enabled is evaluated only at startup, so Fess must be restarted after you enable it.
See Semantic Search (Content Chunking + Vector Search) for details.
Verifying Fusion Results
You can check whether Rank Fusion is actually working by looking at the following two fields added to the search results.
| Field | Description |
|---|---|
searcher | An array of the names of the searchers that retrieved the document (for example, ["default", "semantic_chunk"]). If both are present, the document was hit by both keyword search and semantic search. |
rf_score | The fused score calculated by RRF. The field name can be changed with rank.fusion.score_field. |
Both values are added dynamically at search time and are not stored in the index. They are also not included in the /api/v2/search response by default, so to see them, add the following to fess_config.properties and restart Fess:
Note
query.additional.api.response.fields adds entries to the allowlist of fields that may be included in the v2 search API response. Do not add access control fields such as role or virtual_host, because doing so exposes access control information in search API responses.
Impact on Hit Counts
When Rank Fusion runs, the total hit count that is returned is not the count from the main searcher (the default searcher registered first) as-is; it is adjusted as follows:
The adjustment is the number of documents among the top window_size ÷ 2 fused results that were not among the top window_size ÷ 2 results of the main searcher. In other words, the count increases by the number of documents that only semantic search found. As a result, the same query can report a different hit count depending on whether hybrid search is enabled.
Note that this adjustment is not applied when the total hit count of the main searcher is returned as an approximate (lower-bound) value.
Usage Examples
Basic Hybrid Search
Calculate the BM25 score with keyword search
Calculate vector similarity with semantic search
Fuse both results with RRF
Generate the final ranking
Search Flow:
Performance Considerations
Memory Usage
Memory usage increases because multiple search results are retained.
Use
rank.fusion.window_sizeto limit the maximum number of results to fuse. The main searcher (the leadingdefaultsearcher) retrieves up towindow_sizeresults, while each of the other searchers retrieveswindow_size ÷ number of searchersresults (number of searchersis the total including the main searcher, and the division is truncated).For example, with two searchers (
defaultandsemantic_chunk) andwindow_size=200, the main searcher retrieves 200 results and the semantic searcher retrieves 100, so up to 300 documents are retained.
Warning
rank.fusion.window_size cannot go below paging.search.page.max.size × 2. When paging.search.page.max.size has its default value of 100, the lower bound is 200, which is exactly the default value of rank.fusion.window_size. In other words, in the default configuration you cannot set window_size to anything smaller than its default. If you configure a smaller value, a WARN log is written at startup and the value is raised to 200. To lower it in practice, you must first lower paging.search.page.max.size, but that also lowers the maximum number of results per page that clients can request.
Processing Time
Response time increases because multiple searches are executed.
Use
rank.fusion.threadsto set the number of threads for parallel execution.
Note
No timeout is applied to searcher execution. If a searcher never responds, the search request waits until it completes.
Behavior When a Searcher Fails
If a searcher fails with an exception, its results are treated as empty, a WARN log is written, and fusion continues with the results of the remaining searchers. The search request itself does not fail.
Query syntax errors (InvalidQueryException) and paging limit violations (ResultOffsetExceededException) are the exceptions to this rule: they are returned as errors as-is. In addition, on deep pages where fusion is not performed (where start position × 2 is greater than or equal to rank.fusion.window_size), an exception raised by the main searcher is returned as a search request error as-is.
The semantic searcher returns empty results when it cannot connect to the embedding provider or when embedding processing fails. This does not cause an error either; the response contains keyword search results only.
Troubleshooting
Search Results Differ from Expectations
Symptom: Results after Rank Fusion differ from expectations
Checks:
Check the
searcherfield (see “Verifying Fusion Results”). If every document has only["default"], the semantic searcher is not returning any results.Check whether semantic search is being skipped. The semantic searcher returns no results, so only keyword search results are used, for queries that contain search syntax (such as
",:, orAND), as well as for narrowing by label, sort, or facet, for geo searches, and for similar-document searches. See Semantic Search (Content Chunking + Vector Search) for details on the skip conditions.Verify the results of each search type individually
Adjust the
rank.fusion.rank_constantvalueOn deep pages (where
start position × 2is greater than or equal torank.fusion.window_size, which by default means from the 101st result onward), fusion is not performed and only the main searcher is used. If you want fused results on more pages, increaserank.fusion.window_size.
Slow Search
Symptom: Search becomes slow when Rank Fusion is enabled
Solutions:
Adjust
rank.fusion.threads:Reduce
rank.fusion.window_size. Because it cannot go below its lower bound (paging.search.page.max.size × 2), in the default configuration you have to set the following two properties together:Note that this also lowers the maximum number of results that can be requested per page. A restart is required after changing these settings.
Out of Memory
Symptom: OutOfMemoryError occurs
Solutions:
Reduce
rank.fusion.window_sizeusing the same procedure as in “Slow Search”Increase the JVM heap size
Reference
Semantic Search (Content Chunking + Vector Search) - Semantic Search (Content Chunking) Settings
Scripting Overview - Scripting Overview
Search-Related Settings - Advanced Search Settings
AI Search (RAG) and LLM Integration Overview - LLM Integration Guide (Semantic Search)