You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ds...@apache.org on 2026/06/30 14:23:23 UTC

(solr) branch branch_9x updated (9c2a5afb8d7 -> f7b0ec018b6)

This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a change to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


    from 9c2a5afb8d7 Bump Solr 9x to latest opennlp 1.9.5 (#4555)
     new a4c7aa08e9b SOLR-18100: Improve Combined Query Feature documentation (#4144)
     new f7b0ec018b6 SOLR-18290: combined-query / RRF : support shards.rows (#4546)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../SOLR-17319-added_combined_query_RRF.yml        |   3 +
 .../handler/component/CombinedQueryComponent.java  |   3 +-
 .../component/CombinedQuerySolrCloudTest.java      | 141 +++++++++++----------
 .../DistributedCombinedQueryComponentTest.java     |  21 ++-
 .../query-guide/pages/json-combined-query-dsl.adoc |  97 ++++++++++----
 5 files changed, 167 insertions(+), 98 deletions(-)


(solr) 02/02: SOLR-18290: combined-query / RRF : support shards.rows (#4546)

Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git

commit f7b0ec018b69b58be13cccc51d0b61fdb51f75e0
Author: Sonu Sharma <so...@gmail.com>
AuthorDate: Fri Jun 26 12:24:10 2026 -0400

    SOLR-18290: combined-query / RRF : support shards.rows (#4546)
    
    Enhance combined-query / RRF to support shards.rows param properly -- thereby supporting stable paging results
    
    9x backport changes for Java 11
---
 .../SOLR-17319-added_combined_query_RRF.yml        |   3 +
 .../handler/component/CombinedQueryComponent.java  |   3 +-
 .../component/CombinedQuerySolrCloudTest.java      | 141 +++++++++++----------
 .../DistributedCombinedQueryComponentTest.java     |  21 ++-
 .../query-guide/pages/json-combined-query-dsl.adoc |   5 +
 5 files changed, 99 insertions(+), 74 deletions(-)

diff --git a/changelog/unreleased/SOLR-17319-added_combined_query_RRF.yml b/changelog/unreleased/SOLR-17319-added_combined_query_RRF.yml
index 17d64bf0233..a3f4f6177f8 100644
--- a/changelog/unreleased/SOLR-17319-added_combined_query_RRF.yml
+++ b/changelog/unreleased/SOLR-17319-added_combined_query_RRF.yml
@@ -6,3 +6,6 @@ authors:
 links:
   - name: SOLR-17319
     url: https://issues.apache.org/jira/browse/SOLR-17319
+  - name: SOLR-18290
+    url: https://issues.apache.org/jira/browse/SOLR-18290
+
diff --git a/solr/core/src/java/org/apache/solr/handler/component/CombinedQueryComponent.java b/solr/core/src/java/org/apache/solr/handler/component/CombinedQueryComponent.java
index 699fe8d2fd8..6c76f314649 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/CombinedQueryComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/CombinedQueryComponent.java
@@ -327,13 +327,14 @@ public class CombinedQueryComponent extends QueryComponent implements SolrCoreAw
     // excluded by collapse at the shard level.
     Map<String, Set<Object>> combinedDocIdsPerShard =
         CollectionUtil.newHashMap(sreq.responses.size());
+    int perQueryQueueSize = Math.max(ss.getOffset() + ss.getCount(), rb.shards_rows);
     // TODO: to be parallelized outer loop
     for (int queryIndex = 0; queryIndex < queriesToCombineKeys.length; queryIndex++) {
       int failedShardCount = 0;
       long queryNumFound = 0;
       long queryApproximateTotalHits = 0;
       final ShardDocQueue queuePerQuery =
-          newShardDocQueue(rb.req.getSearcher(), sortFields, ss.getOffset() + ss.getCount());
+          newShardDocQueue(rb.req.getSearcher(), sortFields, perQueryQueueSize);
       for (ShardResponse srsp : sreq.responses) {
         SolrDocumentList docs = null;
         NamedList<?> responseHeader;
diff --git a/solr/core/src/test/org/apache/solr/handler/component/CombinedQuerySolrCloudTest.java b/solr/core/src/test/org/apache/solr/handler/component/CombinedQuerySolrCloudTest.java
index 3c5f56261ec..24772adedf4 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/CombinedQuerySolrCloudTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/CombinedQuerySolrCloudTest.java
@@ -126,16 +126,13 @@ public class CombinedQuerySolrCloudTest extends AbstractFullDistribZkTestBase {
   @Test
   public void testSingleLexicalQuery() throws Exception {
     prepareIndexDocs();
-    QueryResponse rsp =
-        query(
-            CommonParams.JSON,
-            "{\"queries\":"
-                + "{\"lexical1\":{\"lucene\":{\"query\":\"id:2^=10\"}}},"
-                + "\"limit\":5,"
-                + "\"fields\":[\"id\",\"score\",\"title\"],"
-                + "\"params\":{\"combiner\":true,\"combiner.query\":[\"lexical1\"]}}",
-            CommonParams.QT,
-            "/search");
+    String jsonQuery =
+        "{\"queries\": {"
+            + "\"lexical1\": {\"lucene\": {\"query\": \"id:2^=10\"}}},"
+            + "\"limit\": 5,"
+            + "\"fields\": [\"id\", \"score\", \"title\"],"
+            + "\"params\": {\"combiner\": true, \"combiner.query\": [\"lexical1\"]}}";
+    QueryResponse rsp = query(CommonParams.JSON, jsonQuery, CommonParams.QT, "/search");
     assertEquals(1, rsp.getResults().size());
     assertFieldValues(rsp.getResults(), id, "2");
   }
@@ -149,12 +146,12 @@ public class CombinedQuerySolrCloudTest extends AbstractFullDistribZkTestBase {
   public void testMultipleLexicalQuery() throws Exception {
     prepareIndexDocs();
     String jsonQuery =
-        "{\"queries\":"
-            + "{\"lexical1\":{\"lucene\":{\"query\":\"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
-            + "\"lexical2\":{\"lucene\":{\"query\":\"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
-            + "\"limit\":5,"
-            + "\"fields\":[\"id\",\"score\",\"title\"],"
-            + "\"params\":{\"combiner\":true,\"combiner.query\":[\"lexical1\",\"lexical2\"]}}";
+        "{\"queries\": {"
+            + "\"lexical1\": {\"lucene\": {\"query\": \"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
+            + "\"lexical2\": {\"lucene\": {\"query\": \"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
+            + "\"limit\": 5,"
+            + "\"fields\": [\"id\", \"score\", \"title\"],"
+            + "\"params\": {\"combiner\": true, \"combiner.query\": [\"lexical1\", \"lexical2\"]}}";
     QueryResponse rsp = query(CommonParams.JSON, jsonQuery, CommonParams.QT, "/search");
     assertEquals(5, rsp.getResults().size());
     assertFieldValues(rsp.getResults(), id, "5", "7", "2", "6", "3");
@@ -169,12 +166,13 @@ public class CombinedQuerySolrCloudTest extends AbstractFullDistribZkTestBase {
   public void testMultipleQueryWithSort() throws Exception {
     prepareIndexDocs();
     String jsonQuery =
-        "{\"queries\":"
-            + "{\"lexical1\":{\"lucene\":{\"query\":\"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
-            + "\"lexical2\":{\"lucene\":{\"query\":\"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
-            + "\"limit\":5,\"sort\":\"mod3_idv desc, score desc\""
-            + "\"fields\":[\"id\",\"score\",\"title\"],"
-            + "\"params\":{\"combiner\":true,\"combiner.query\":[\"lexical1\",\"lexical2\"]}}";
+        "{\"queries\": {"
+            + "\"lexical1\": {\"lucene\": {\"query\": \"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
+            + "\"lexical2\": {\"lucene\": {\"query\": \"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
+            + "\"limit\": 5,"
+            + "\"sort\": \"mod3_idv desc, score desc\","
+            + "\"fields\": [\"id\", \"score\", \"title\"],"
+            + "\"params\": {\"combiner\": true, \"combiner.query\": [\"lexical1\", \"lexical2\"]}}";
     QueryResponse rsp = query(CommonParams.JSON, jsonQuery, CommonParams.QT, "/search");
     assertEquals(5, rsp.getResults().size());
     assertFieldValues(rsp.getResults(), id, "5", "2", "8", "7", "10");
@@ -188,40 +186,44 @@ public class CombinedQuerySolrCloudTest extends AbstractFullDistribZkTestBase {
   @Test
   public void testHybridQueryWithPagination() throws Exception {
     prepareIndexDocs();
-    QueryResponse rsp =
-        query(
-            CommonParams.JSON,
-            "{\"queries\":"
-                + "{\"lexical1\":{\"lucene\":{\"query\":\"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
-                + "\"lexical2\":{\"lucene\":{\"query\":\"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
-                + "\"fields\":[\"id\",\"score\",\"title\"],"
-                + "\"params\":{\"combiner\":true,\"combiner.query\":[\"lexical1\",\"lexical2\"]}}",
-            CommonParams.QT,
-            "/search");
+    String jsonQueryAll =
+        "{\"queries\": {"
+            + "\"lexical1\": {\"lucene\": {\"query\": \"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
+            + "\"lexical2\": {\"lucene\": {\"query\": \"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
+            + "\"fields\": [\"id\", \"score\", \"title\"],"
+            + "\"params\": {\"combiner\": true, \"combiner.query\": [\"lexical1\", \"lexical2\"]}}";
+    QueryResponse rsp = query(CommonParams.JSON, jsonQueryAll, CommonParams.QT, "/search");
+    // ideal ordering
     assertFieldValues(rsp.getResults(), id, "5", "7", "2", "6", "3", "10", "8");
-    rsp =
-        query(
-            CommonParams.JSON,
-            "{\"queries\":"
-                + "{\"lexical1\":{\"lucene\":{\"query\":\"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
-                + "\"lexical2\":{\"lucene\":{\"query\":\"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
-                + "\"limit\":4,"
-                + "\"fields\":[\"id\",\"score\",\"title\"],"
-                + "\"params\":{\"combiner\":true,\"combiner.query\":[\"lexical1\",\"lexical2\"]}}",
-            CommonParams.QT,
-            "/search");
-    assertFieldValues(rsp.getResults(), id, "5", "7", "2", "6");
-    rsp =
-        query(
-            CommonParams.JSON,
-            "{\"queries\":"
-                + "{\"lexical1\":{\"lucene\":{\"query\":\"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
-                + "\"lexical2\":{\"lucene\":{\"query\":\"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
-                + "\"limit\":4,\"offset\":3,"
-                + "\"fields\":[\"id\",\"score\",\"title\"],"
-                + "\"params\":{\"combiner\":true,\"combiner.query\":[\"lexical1\",\"lexical2\"]}}",
-            CommonParams.QT,
-            "/search");
+    String jsonQueryLimit2 =
+        "{\"queries\": {"
+            + "\"lexical1\": {\"lucene\": {\"query\": \"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
+            + "\"lexical2\": {\"lucene\": {\"query\": \"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
+            + "\"limit\": 2,"
+            + "\"fields\": [\"id\", \"score\", \"title\"],"
+            + "\"params\": {\"combiner\": true, \"combiner.query\": [\"lexical1\", \"lexical2\"]}}";
+    rsp = query(CommonParams.JSON, jsonQueryLimit2, CommonParams.QT, "/search");
+    // assert proper ordering due to presence of shards.rows
+    assertFieldValues(rsp.getResults(), id, "7", "2");
+    String jsonQueryWithShardRows =
+        "{\"queries\": {"
+            + "\"lexical1\": {\"lucene\": {\"query\": \"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
+            + "\"lexical2\": {\"lucene\": {\"query\": \"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
+            + "\"limit\": 2,"
+            + "\"fields\": [\"id\", \"score\", \"title\"],"
+            + "\"params\": {\"combiner\": true, \"combiner.query\": [\"lexical1\", \"lexical2\"], \"shards.rows\": 10}}";
+    rsp = query(CommonParams.JSON, jsonQueryWithShardRows, CommonParams.QT, "/search");
+    assertFieldValues(rsp.getResults(), id, "5", "7");
+    // assert improper ordering due to lack of shards.rows
+    String jsonQueryPage =
+        "{\"queries\": {"
+            + "\"lexical1\": {\"lucene\": {\"query\": \"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
+            + "\"lexical2\": {\"lucene\": {\"query\": \"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
+            + "\"limit\": 4,"
+            + "\"offset\": 3,"
+            + "\"fields\": [\"id\", \"score\", \"title\"],"
+            + "\"params\": {\"combiner\": true, \"combiner.query\": [\"lexical1\", \"lexical2\"]}}";
+    rsp = query(CommonParams.JSON, jsonQueryPage, CommonParams.QT, "/search");
     assertEquals(4, rsp.getResults().size());
     assertFieldValues(rsp.getResults(), id, "6", "3", "10", "8");
   }
@@ -235,12 +237,13 @@ public class CombinedQuerySolrCloudTest extends AbstractFullDistribZkTestBase {
   public void testQueryWithFaceting() throws Exception {
     prepareIndexDocs();
     String jsonQuery =
-        "{\"queries\":"
-            + "{\"lexical\":{\"lucene\":{\"query\":\"id:(2^=2 OR 3^=1 OR 6^=2 OR 5^=1)\"}}},"
-            + "\"limit\":3,\"offset\":1"
-            + "\"fields\":[\"id\",\"score\",\"title\"],"
-            + "\"params\":{\"combiner\":true,\"facet\":true,\"facet.field\":\"mod3_idv\",\"facet.mincount\":1,"
-            + "\"combiner.query\":[\"lexical\"]}}";
+        "{\"queries\": {"
+            + "\"lexical\": {\"lucene\": {\"query\": \"id:(2^=2 OR 3^=1 OR 6^=2 OR 5^=1)\"}}},"
+            + "\"limit\": 3,"
+            + "\"offset\": 1,"
+            + "\"fields\": [\"id\", \"score\", \"title\"],"
+            + "\"params\": {\"combiner\": true, \"facet\": true, \"facet.field\": \"mod3_idv\","
+            + "\"facet.mincount\": 1, \"combiner.query\": [\"lexical\"]}}";
     QueryResponse rsp = query(CommonParams.JSON, jsonQuery, CommonParams.QT, "/search");
     assertEquals(3, rsp.getResults().size());
     assertEquals(4, rsp.getResults().getNumFound());
@@ -256,14 +259,14 @@ public class CombinedQuerySolrCloudTest extends AbstractFullDistribZkTestBase {
   public void testQueriesWithFacetAndHighlights() throws Exception {
     prepareIndexDocs();
     String jsonQuery =
-        "{\"queries\":"
-            + "{\"lexical1\":{\"lucene\":{\"query\":\"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
-            + "\"lexical2\":{\"lucene\":{\"query\":\"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
-            + "\"limit\":4,"
-            + "\"fields\":[\"id\",\"score\",\"title\"],"
-            + "\"params\":{\"combiner\":true,\"facet\":true,\"facet.field\":\"mod3_idv\","
-            + "\"combiner.query\":[\"lexical1\",\"lexical2\"], \"hl\": true,"
-            + "\"hl.fl\": \"title\",\"hl.q\":\"test doc\"}}";
+        "{\"queries\": {"
+            + "\"lexical1\": {\"lucene\": {\"query\": \"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
+            + "\"lexical2\": {\"lucene\": {\"query\": \"id:(8^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
+            + "\"limit\": 4,"
+            + "\"fields\": [\"id\", \"score\", \"title\"],"
+            + "\"params\": {\"combiner\": true, \"facet\": true, \"facet.field\": \"mod3_idv\","
+            + "\"combiner.query\": [\"lexical1\", \"lexical2\"],"
+            + "\"hl\": true, \"hl.fl\": \"title\", \"hl.q\": \"test doc\"}}";
     QueryResponse rsp = query(CommonParams.JSON, jsonQuery, CommonParams.QT, "/search");
     assertEquals(4, rsp.getResults().size());
     assertFieldValues(rsp.getResults(), id, "5", "7", "2", "6");
diff --git a/solr/core/src/test/org/apache/solr/handler/component/DistributedCombinedQueryComponentTest.java b/solr/core/src/test/org/apache/solr/handler/component/DistributedCombinedQueryComponentTest.java
index f45cee60cfc..4303a1eb238 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/DistributedCombinedQueryComponentTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/DistributedCombinedQueryComponentTest.java
@@ -199,16 +199,28 @@ public class DistributedCombinedQueryComponentTest extends BaseDistributedSearch
             + "\"fields\":[\"id\",\"score\",\"title\"],"
             + "\"params\":{\"combiner\":true,\"combiner.query\":[\"lexical1\",\"lexical2\"]}}";
     QueryResponse rsp = query(CommonParams.JSON, jsonQueryAll, CommonParams.QT, "/search");
+    // ideal ordering
     assertFieldValues(rsp.getResults(), id, "5", "7", "2", "6", "3", "10", "4");
-    String jsonQueryLimit4 =
+    String jsonQueryLimit1 =
         "{\"queries\":"
             + "{\"lexical1\":{\"lucene\":{\"query\":\"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
             + "\"lexical2\":{\"lucene\":{\"query\":\"id:(4^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
-            + "\"limit\":4,"
+            + "\"limit\":1,"
             + "\"fields\":[\"id\",\"score\",\"title\"],"
             + "\"params\":{\"combiner\":true,\"combiner.query\":[\"lexical1\",\"lexical2\"]}}";
-    rsp = query(CommonParams.JSON, jsonQueryLimit4, CommonParams.QT, "/search");
-    assertFieldValues(rsp.getResults(), id, "5", "7", "2", "6");
+    rsp = query(CommonParams.JSON, jsonQueryLimit1, CommonParams.QT, "/search");
+    // assert proper ordering due to presence of shards.rows
+    assertFieldValues(rsp.getResults(), id, "7");
+    String jsonQueryWithShardRows =
+        "{\"queries\":"
+            + "{\"lexical1\":{\"lucene\":{\"query\":\"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
+            + "\"lexical2\":{\"lucene\":{\"query\":\"id:(4^=1 OR 5^=3 OR 7^=4 OR 10^=2)\"}}},"
+            + "\"limit\":1,"
+            + "\"fields\":[\"id\",\"score\",\"title\"],"
+            + "\"params\":{\"combiner\":true,\"combiner.query\":[\"lexical1\",\"lexical2\"],\"shards.rows\":10}}";
+    rsp = query(CommonParams.JSON, jsonQueryWithShardRows, CommonParams.QT, "/search");
+    assertFieldValues(rsp.getResults(), id, "5");
+    // assert improper ordering due to lack of shards.rows
     String jsonQueryPage =
         "{\"queries\":"
             + "{\"lexical1\":{\"lucene\":{\"query\":\"id:(2^=4 OR 3^=2 OR 6^=3 OR 5^=1)\"}},"
@@ -281,6 +293,7 @@ public class DistributedCombinedQueryComponentTest extends BaseDistributedSearch
    * @throws Exception if any unexpected error occurs during the test execution.
    */
   @Test
+  @ShardsFixed(num = 2)
   public void testElevatedQueriesWithFacetAndHighlights() throws Exception {
     prepareIndexDocs();
     String jsonQuery =
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/json-combined-query-dsl.adoc b/solr/solr-ref-guide/modules/query-guide/pages/json-combined-query-dsl.adoc
index de62c7a6d1b..61b7aed0b82 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/json-combined-query-dsl.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/json-combined-query-dsl.adoc
@@ -76,6 +76,11 @@ The query structure is similar to JSON Query DSL except for how multiple queries
 `combiner.rrf.k` | Default: `60`::
    The k parameter in the RRF algorithm.
 
+[NOTE]
+====
+Consider setting `shards.rows` to at least your deepest expected `start + rows` to enlarge the per-shard candidate pool used for fusion, giving more consistent ordering across pages.
+====
+
 === Example
 
 Below is an example with sample JSON query payload:


(solr) 01/02: SOLR-18100: Improve Combined Query Feature documentation (#4144)

Posted by ds...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dsmiley pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git

commit a4c7aa08e9bf6f691f695a9d34bcec7b57b313c7
Author: Ilaria Petreti <66...@users.noreply.github.com>
AuthorDate: Mon Mar 16 11:48:48 2026 +0100

    SOLR-18100: Improve Combined Query Feature documentation (#4144)
    
    (cherry picked from commit 3724ebb50a4bf3b07e8e620ef327d4d458cb58da)
---
 .../query-guide/pages/json-combined-query-dsl.adoc | 92 ++++++++++++++++------
 1 file changed, 68 insertions(+), 24 deletions(-)

diff --git a/solr/solr-ref-guide/modules/query-guide/pages/json-combined-query-dsl.adoc b/solr/solr-ref-guide/modules/query-guide/pages/json-combined-query-dsl.adoc
index e5649f0cad2..de62c7a6d1b 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/json-combined-query-dsl.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/json-combined-query-dsl.adoc
@@ -25,6 +25,42 @@ It is extending JSON Query DSL ultimately enabling Hybrid Search.
 This feature is currently unsupported for grouping and Cursors.
 ====
 
+[IMPORTANT]
+====
+This feature works in both Standalone and SolrCloud modes and always performs distributed search execution.
+In Standalone (user-managed) mode, shard URLs must be explicitly allow-listed using the *allowUrls* parameter, otherwise Solr returns HTTP 403. For example:
+
+```
+"-Dsolr.security.allow.urls=http://localhost:8983/solr/"
+```
+
+====
+
+== Configuration Requirements
+
+Combined Query Feature has a separate handler with class `solr.CombinedQuerySearchHandler` which can be configured as below:
+
+```
+<requestHandler name="/search" class="solr.CombinedQuerySearchHandler">
+.....
+</requestHandler>
+```
+
+In addition, the `QueryComponent` has been extended to create a new `CombinedQueryComponent`, which must be declared as a search component:
+```
+<searchComponent class="solr.CombinedQueryComponent" name="combined_query">
+    <int name="maxCombinerQueries">2</int>
+</searchComponent>
+```
+
+
+The Search Component also accepts parameters as below:
+
+`maxCombinerQueries`::
+This parameter can be set to enforce an upper limit on the number of queries defined in `combiner.query`.
+It defaults to `5` if not set.
+
+
 == Query DSL Structure
 The query structure is similar to JSON Query DSL except for how multiple queries are defined along with their parameters.
 
@@ -100,37 +136,45 @@ QueryResponse queryResponse = query.process(solrClient, COLLECTION_NAME);
 ====
 ======
 
-== Search Handler Configuration
-
-Combined Query Feature has a separate handler with class `solr.CombinedQuerySearchHandler` which can be configured as below:
-
-```
-<requestHandler name="/search" class="solr.CombinedQuerySearchHandler">
-.....
-</requestHandler>
-```
-
-The Search Handler also accepts parameters as below:
+== Combiner Algorithm Plugin
 
-`maxCombinerQueries`::
-  This parameter can be set to put upper limit check on the maximum number of queries can be executed defined in `combiner.query`.
-  It defaults to `5` if not set.
+As mentioned xref:json-combined-query-dsl.adoc#query-dsl-structure[above], custom algorithms can be configured to combine the results across multiple queries using a https://solr.apache.org/guide/solr/latest/configuration-guide/solr-plugins.html[Solr plugin].
 
-=== Combiner Algorithm Plugin
+The class to implement the custom logic has to extend `org.apache.solr.handler.component.combine.QueryAndResponseCombiner`, which is an abstract base class that provides a framework for implementing various algorithms used to merge ranked lists and shard documents.
 
-As mentioned xref:json-combined-query-dsl.adoc#query-dsl-structure[above], custom algorithms can be configured to combine the results across multiple queries.
-The Combined Query Search Handler definition takes parameter `combiners` where a custom class can be used to define the algorithm by giving a name and the parameters required.
+The Combined Query Component definition takes the `combiners` parameter, where the custom class can be declared by specifying a name and the custom parameters required by the custom algorithm.
 
-Example of the Search Handler as below:
+Example of the Search Component as below:
 ```
 <searchComponent class="solr.CombinedQueryComponent" name="combined_query">
-        <int name="maxCombinerQueries">2</int>
+    <int name="maxCombinerQueries">2</int>
         <lst name="combiners">
-            <lst name="customAlgorithm">
-                <str name="class">org.apache.solr.search.combine.CustomCombiner</str>
-                <int name="var1">35</int>
-                <str name="var2">customValue</str>
+           <lst name="customAlgorithm">
+                <str name="class">org.apache.solr.handler.component.combine.CustomCombiner</str>
+                <int name="customParam1">35</int>
+                <str name="customParam2">customValue</str>
             </lst>
         </lst>
-    </searchComponent>
+</searchComponent>
 ```
+
+Then, when executing the combined query, the only thing that changes in the JSON query payload is the value specified in the `combiner.algorithm` parameter:
+
+```
+    ...
+    "params": {
+        "combiner": true,
+        "combiner.query": ["lexical1", "vector"],
+        "combiner.algorithm": "customAlgorithm"
+    }
+...
+```
+
+In this case, `customAlgorithm` is specified which is the name defined in the configuration; the RRF-specific parameters do not need to be provided.
+
+== Additional Resources
+
+Blog posts:
+
+* https://sease.io/2026/03/hybrid-search-with-reciprocal-rank-fusion-in-apache-solr.html[Hybrid Search with Reciprocal Rank Fusion in Apache Solr]
+* https://sease.io/2026/03/hybrid-search-using-a-custom-algorithm-in-apache-solr.html[Hybrid Search using a Custom Algorithm in Apache Solr]