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

janhoy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-orbit.git


The following commit(s) were added to refs/heads/main by this push:
     new 870e7019 optimize: follow Solr's merge policy by default (#115)
870e7019 is described below

commit 870e7019e7e5a46e8f054e49e4f52a2f18d64e30
Author: Jan Høydahl <[email protected]>
AuthorDate: Fri Oct 16 15:40:43 2026 +0200

    optimize: follow Solr's merge policy by default (#115)
---
 solrorbit/worker_coordinator/runner.py | 24 ++++++++++++++++---
 tests/unit/solr/test_runner.py         | 42 ++++++++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+), 3 deletions(-)

diff --git a/solrorbit/worker_coordinator/runner.py 
b/solrorbit/worker_coordinator/runner.py
index 6bf075e8..73d65605 100644
--- a/solrorbit/worker_coordinator/runner.py
+++ b/solrorbit/worker_coordinator/runner.py
@@ -1333,16 +1333,34 @@ class SolrOptimize(SolrRunner):
     Force-merge Solr segments (optimize).
 
     Params:
-      - ``collection``, ``max-segments`` (int, default 1)
+      - ``collection``
+      - ``max-segments`` (int, optional). Omitted by default.
+
+    **The default is Solr's own, which is no longer "one segment".** Modern
+    Solr honours the merge policy's ``maxSegmentSizeMB`` when ``maxSegments``
+    is not given, merging down to as few segments as that ceiling allows
+    rather than to a single one. Forcing ``maxSegments=1`` instead produces an
+    index shape that no production system would ever hold -- a single
+    enormous segment -- and costs a full rewrite of the entire index to get
+    there, which on a 165M-document corpus is the longest single operation in
+    a benchmark run by a wide margin.
+
+    Passing the parameter explicitly still forces the old behaviour, for a
+    workload that wants exactly one segment and is willing to pay for it.
     """
 
     async def __call__(self, client, params):
         collection = _get_collection(params)
         sc = client
-        max_segments = params.get("max-segments", 1)
+        max_segments = params.get("max-segments")
+
+        # Only forward the argument when the workload actually asked for it:
+        # pysolr sends a bare `<optimize />` when it is absent, which is what
+        # lets Solr apply maxSegmentSizeMB.
+        kwargs = {} if max_segments in (None, "") else {"maxSegments": 
int(max_segments)}
 
         start = time.perf_counter()
-        await _run_in_executor(sc.optimize, collection, 
maxSegments=max_segments)
+        await _run_in_executor(sc.optimize, collection, **kwargs)
         elapsed = time.perf_counter() - start
 
         return {"weight": 1, "unit": "ops", "took": elapsed}
diff --git a/tests/unit/solr/test_runner.py b/tests/unit/solr/test_runner.py
index 7800b592..352ab808 100644
--- a/tests/unit/solr/test_runner.py
+++ b/tests/unit/solr/test_runner.py
@@ -24,6 +24,7 @@ from unittest.mock import MagicMock
 from solrorbit.worker_coordinator.runner import (
     _translate_ndjson_batch,
     SolrBulkIndex,
+    SolrOptimize,
     SolrSearch,
     SolrCreateCollection,
     SolrDeleteCollection,
@@ -344,6 +345,47 @@ class TestSolrDeleteCollection(unittest.TestCase):
         _run(runner(mock_sc, params))
 
 
+class TestSolrOptimize(unittest.TestCase):
+    """Optimize follows Solr's own default, which is no longer one segment.
+
+    Modern Solr honours the merge policy's maxSegmentSizeMB when maxSegments
+    is not given. Forcing maxSegments=1 produces an index shape no production
+    system would hold, and pays a full rewrite of the index to reach it --
+    which on a real corpus is the longest single operation in a run.
+    """
+
+    def test_no_max_segments_is_sent_by_default(self):
+        client = MagicMock()
+        _run(SolrOptimize()(client, {"collection": "c"}))
+        _args, kwargs = client.optimize.call_args
+        assert "maxSegments" not in kwargs
+
+    def test_an_explicit_value_is_honoured(self):
+        client = MagicMock()
+        _run(SolrOptimize()(client, {"collection": "c", "max-segments": 1}))
+        _args, kwargs = client.optimize.call_args
+        assert kwargs["maxSegments"] == 1
+
+    def test_a_string_value_is_coerced(self):
+        # Workload params arrive as JSON and a template can render a string.
+        client = MagicMock()
+        _run(SolrOptimize()(client, {"collection": "c", "max-segments": "4"}))
+        _args, kwargs = client.optimize.call_args
+        assert kwargs["maxSegments"] == 4
+
+    def test_an_empty_value_means_unset(self):
+        client = MagicMock()
+        _run(SolrOptimize()(client, {"collection": "c", "max-segments": ""}))
+        _args, kwargs = client.optimize.call_args
+        assert "maxSegments" not in kwargs
+
+    def test_it_reports_how_long_the_merge_took(self):
+        client = MagicMock()
+        result = _run(SolrOptimize()(client, {"collection": "c"}))
+        assert result["weight"] == 1 and result["unit"] == "ops"
+        assert result["took"] >= 0
+
+
 class TestRunnerRegistrationSmoke(unittest.TestCase):
     """
     Verify that Solr runners work end-to-end through the MultiClientRunner 
wrapper.

Reply via email to