Skip to content

Index Sorting: the full picture (SOLR-15390/13681/12239/17310/12230) — for review, happy to split#4648

Open
serhiy-bzhezytskyy wants to merge 9 commits into
apache:mainfrom
serhiy-bzhezytskyy:index-sorting-all-in-one
Open

Index Sorting: the full picture (SOLR-15390/13681/12239/17310/12230) — for review, happy to split#4648
serhiy-bzhezytskyy wants to merge 9 commits into
apache:mainfrom
serhiy-bzhezytskyy:index-sorting-all-in-one

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Contributor

Follows up on the dev@ thread "State of Lucene Index Sorting support in Solr".

Purpose: this PR exists to make the whole shape of the index-sorting work reviewable in one place — as I offered on the list ("open a PR with all changes to see the full picture"). It is not a request to merge all of this as one lump. I'm very happy to split it into per-ticket PRs, and to fold the config piece into Christine's existing work rather than land my own — see the note at the bottom.

The picture (from David's "State of Lucene Index Sorting" report)

Index sorting works today only through the indirect SortingMergePolicyFactory (a merge policy that does no merging, existing only to carry a Sort to IndexWriterConfig.setIndexSort()). This branch works through the open pieces end to end, in dependency order:

commit ticket what
SOLR-15390 15390 use native TopFieldCollector early termination for segmentTerminateEarly; remove the deprecated EarlyTerminatingSortingCollector (Tomas said "feel free to take it")
SOLR-13681 13681 direct <indexSort> config in <indexConfig>, replacing the merge-policy indirection
SOLR-12239 12239 RESORTINDEX core-admin action to re-sort an existing index (also up standalone as #4644)
SOLR-17310 17310 <segmentSort> to configure Lucene's leaf sorter
SOLR-12230 12230 deprecate SortingMergePolicy now that <indexSort> replaces it

Credit (important — please read)

(I stripped Co-authored-by trailers from the commits deliberately — I didn't want to attribute code to Christine or Wei publicly without asking them first. Crediting them here in prose instead; happy to add trailers back if they'd prefer.)

Open design questions (for the list / reviewers)

  1. Config shape: <indexSort> (within-segment) and <segmentSort> (between-segment) as separate <indexConfig> elements, vs. a unified <indexSorters> element. My reading of precedent leans separate (Lucene keeps setIndexSort/setLeafSorter distinct; ES/OpenSearch expose only the within-segment axis) — but no strong opinion.
  2. segmentTerminateEarly: actually deprecate the param (15390's literal ask), or just modernize internals? This branch modernizes internals; the param-deprecation is left as a separate user-facing decision.

Testing

Unit + SolrCloud (~40 tests) pass locally, incl. TestSegmentSorting (testSegmentTerminateEarly, testSegmentTerminateEarlyWithIndexSort, testAtomicUpdateOfSegmentSortField) and TestEarlyTerminatingQueries. Compiles clean.

What I'm asking

Direction and collaboration — especially with @cpoerschke on 13681, since that's the keystone and her active work. If the shape looks right, I'll break this into reviewable per-ticket PRs (15390 first — it's self-contained). No rush.

…tTerminateEarly

Solr carried its own @deprecated copy of EarlyTerminatingSortingCollector to
implement the segmentTerminateEarly query parameter. Lucene's TopFieldCollector
now performs the same per-segment early termination natively when the search
sort is a prefix of the index sort, and exposes it via isEarlyTerminated().

buildAndRunCollectorChain now inspects the TopFieldCollector after the search
instead of wrapping it, and buildTopDocsCollector lowers the total-hits
threshold to the page size on the segmentTerminateEarly path so termination can
fire. The segmentTerminatedEarly response header is reported exactly as before
(absent when not requested, false when the sort/merge-policy combination is not
eligible, true when a segment terminated early). The deprecated collector is
removed.
Adds an <indexSort> element to <indexConfig> that sets Lucene's index sort
directly, instead of the indirect route through a SortingMergePolicyFactory
(a merge policy that does no merging and only carries a Sort). The value is a
standard sort spec over docValues fields.

When both an <indexSort> and a SortingMergePolicy sort are configured, the
<indexSort> takes precedence; a mismatch is logged. The segmentTerminateEarly
eligibility check now consults the configured index sort, falling back to the
merge policy sort. Existing nested-document handling (the parent field for
child-doc schemas) applies to the directly configured sort as well.

Enabling index sorting on a collection with existing unsorted segments still
requires re-sorting them (see the RESORTINDEX core admin action); it is not
applied on reload automatically.
…index

Enabling index sorting on a collection created without it currently fails on core
reload with an IndexWriter.validateIndexSort error, forcing a delete + reindex from
source. This adds a RESORTINDEX core-admin action that re-sorts the existing index
into the requested sort order in place, using the LUCENE-9484 mechanism: each segment
reader is wrapped in a SortingCodecReader and merged into a fresh sort-configured
IndexWriter via addIndexes(CodecReader...), and the result is swapped in with
modifyIndexProps (as RestoreCore/replication do), then the writer and searcher are
reopened.

Notes:
- addIndexes does not auto-sort unsorted readers (LUCENE-8505 removed that), so the
  explicit SortingCodecReader wrap is what performs the merge-based re-sort.
- Not supported in SolrCloud mode.
- Indexes containing child/nested documents are rejected (re-sorting would break the
  parent-child blocks), matching UPGRADEINDEX's restriction.
- On a failed swap the original index.properties is restored (RestoreCore's rollback).

The target sort is given by the sort request parameter using the usual Solr sort
syntax; the fields must have docValues.
Exposes Lucene IndexWriterConfig.setLeafSorter via a new <segmentSort> element
in <indexConfig>, so a collection can visit its segments (leaf readers) in
creation-time order at search time. This is between-segment ordering, separate
from index sorting (within-segment document order) and independent of merging;
the main use is early termination on time-based indexes by searching the most
recently created segments first.

Supported values are TIME_ASC and TIME_DESC; the default (NONE) installs no
leaf sorter. The value is parsed and validated when the config is loaded.
Segment creation time is read from Lucene's per-segment timestamp diagnostic;
a leaf that does not resolve to a SegmentReader is ordered last rather than
failing the search.

Builds on the approach in the earlier PR apache#2477 by Wei Wang.
With index sorting now configurable directly via <indexSort> in <indexConfig>
(SOLR-13681), wrapping a merge policy solely to carry a Sort to the index
writer is no longer needed. Deprecate both SortingMergePolicy and its
SortingMergePolicyFactory, pointing users to <indexSort>.

The classes still function (and are still used internally by index re-sorting,
so remaining references are marked @SuppressWarnings("deprecation")); this
only signals the configuration approach that should replace them.
- RESORTINDEX now falls back to the configured <indexSort> (SOLR-13681),
  not only a SortingMergePolicy, so the migration workflow (configure
  <indexSort>, then RESORTINDEX with no sort param) works end to end.
- RESORTINDEX sets the core codec and schema similarity on the re-sort
  writer, so addIndexes no longer rewrites a per-field index with Lucene
  defaults; and it cleans up the old index dir on success / the orphaned
  resort dir on rollback.
- <indexSort> fails fast with a clear error naming the element when the
  configured spec is not a usable index sort.
- Tests: invalid <indexSort>; <indexSort> wins over a SortingMergePolicy
  sort; RESORTINDEX using a configured <indexSort>; a deterministic
  segmentTerminateEarly run against <indexSort>; SegmentTimeLeafSorter
  tolerates a non-SegmentReader leaf (sorts last).
- Docs: RESORTINDEX points at <indexSort> instead of the deprecated
  SortingMergePolicyFactory; a Major Changes in Solr 11 upgrade note for
  the deprecation; and a note distinguishing <indexSort> from <segmentSort>.
…lues field

<segmentSort> now accepts either a time preset (TIME_ASC/TIME_DESC, by segment
creation time) or a field spec ("<field> asc|desc") that orders segments by
the per-segment min (asc) or max (desc) of a single-valued numeric docValues
field. Lucene's setLeafSorter takes an arbitrary Comparator<LeafReader>, so
this is not a Lucene limitation -- it broadens what the config exposes.

SegmentSort becomes a small parsed spec (NONE/TIME/FIELD); SegmentTimeLeafSorter
is renamed to SegmentLeafSorter and builds the comparator for both forms, with
the per-segment key memoized on the segment core cache key (the comparator runs
O(n log n) times per reader open) and unknown keys sorting last. Field sorts are
validated at config load (must be an existing single-valued numeric docValues
field). Comparing the raw docValues long is order-correct for all numeric types
because Solr stores float/double docValues in sortable-bits form.

Tests: field-sort comparator build + spec round-trip; rejection of unknown,
non-numeric, non-docValues, and multi-valued fields; garbage spec parsing.
Docs updated for both forms.
… multi-value legacy segments

Single-valued FLOAT/DOUBLE docValues are stored by Solr as raw
Float.floatToIntBits/Double.doubleToLongBits (NOT sortable bits), so the
previous raw-long min/max ordered negative values incorrectly. Normalize each
value to an order-preserving long via toSortableLong (int/long/date pass
through; float/double are decoded then re-encoded to sortable bits).

Also, for a legacy segment where the field was multi-valued, take the last
(largest) value for a desc/max scan rather than only the first.

Adds a regression test (two all-negative segments where raw floatToIntBits
order diverges from numeric order) proven to fail without the fix.
- RESORTINDEX request-body schema now documents the <indexSort>-first
  fallback (matching resolveTargetSort and the ref-guide), and the two API
  model classes get class javadoc.
- Fix a half-updated TODO wording in SolrIndexSearcher.
- Tests: <segmentSort> naming a nonexistent field fails when the writer
  config is built; int field ordering both directions against a live core
  searcher; <indexSort> and <segmentSort> configured together.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant