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.