Skip to content

Conversation

@jainankitk
Copy link
Contributor

@jainankitk jainankitk commented Dec 17, 2025

Description

This PR allows skiplist based collection using collectRange and allows even sub aggregation to take advantage of collectRange

Related Issues

Related #20031

Check List

  • Functionality includes testing.
  • API changes companion pull request created, if applicable.
  • Public documentation issue/PR created, if applicable.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Summary by CodeRabbit

  • New Features

    • Added an experimental bucket-aware range collection API for aggregations.
  • Bug Fixes

    • Fixed per-bucket accuracy for histogram, terms, range and metrics aggregations during range collection.
  • Performance

    • Improved range-based collection paths to be more efficient and monomorphic for bucketed aggregations.
  • Documentation

    • Updated changelog to document the new range-collection capability.

✏️ Tip: You can customize this high-level summary in your review settings.

@github-actions
Copy link
Contributor

❌ Gradle check result for 3edda3b: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@coderabbitai
Copy link

coderabbitai bot commented Dec 17, 2025

Walkthrough

Introduces a bucket-aware range-collection API: collectRange(int min, int max, long bucket) and updates many aggregators to accept and forward the new bucket parameter; several metrics aggregators also switch range-based updates to use per-bucket state instead of a fixed bucket index. (≤50 words)

Changes

Cohort / File(s) Summary
Base API
server/src/main/java/org/opensearch/search/aggregations/LeafBucketCollector.java
Add public void collectRange(int min, int max, long bucket) (@ExperimentalApi) and a convenience overload collectRange(int min, int max) that delegates with bucket = 0; propagate bucket to collect(docId, bucket) in the range loop.
Histogram / Skiplist
server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/HistogramSkiplistLeafCollector.java
Replace old stream-based collect path with collectRange(int min, int max, long bucket) implementation(s); implement skiplist-aware range iteration and per-doc fallback while using the provided bucket.
Histogram Aggregators
server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/...
AutoDateHistogramAggregator.java, DateHistogramAggregator.java, NumericHistogramAggregator.java
Update anonymous LeafBucketCollector overrides to collectRange(int min, int max, long bucket) and forward bucket via super.collectRange(min, max, bucket).
Range / Filter Aggregators
server/src/main/java/org/opensearch/search/aggregations/bucket/range/RangeAggregator.java, .../filter/FilterAggregator.java, .../filter/FiltersAggregator.java
Change anonymous collectRange overrides to accept long bucket and forward it to superclass.
Terms Aggregators
server/src/main/java/org/opensearch/search/aggregations/bucket/terms/...
GlobalOrdinalsStringTermsAggregator.java, LongRareTermsAggregator.java, NumericTermsAggregator.java
Multiple anonymous LeafBucketCollector overrides updated to collectRange(int min, int max, long bucket) and forward the bucket parameter to super.
Metrics Aggregators (per-bucket state)
server/src/main/java/org/opensearch/search/aggregations/metrics/...
AvgAggregator.java, SumAggregator.java, StatsAggregator.java, MaxAggregator.java, MinAggregator.java, ExtendedStatsAggregator.java
collectRange overrides now accept long bucket; range-based logic updated to initialize and update per-bucket arrays (counts, sums, compensations, mins, maxes, Kahan state) using the provided bucket index instead of hardcoded index 0.
Composite / Matrix / Misc.
server/src/main/java/org/opensearch/search/aggregations/CompositeAggregator.java, modules/aggs-matrix-stats/.../MatrixStatsAggregator.java
Anonymous LeafBucketCollector overrides updated to new collectRange(..., long bucket) signature and forward bucket to super.collectRange.
Docs/Changelog
CHANGELOG.md
Add Unreleased changelog entry documenting skiplist-based collection via collectRange.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Areas requiring extra attention:

  • LeafBucketCollector.java: verify API semantics, @ExperimentalApi usage, and backward compatibility via the convenience overload.
  • Histogram skiplist logic in HistogramSkiplistLeafCollector.java: correctness of skip/advance logic and upToExclusive handling.
  • Metrics aggregators (Avg, Sum, Stats, Max, Min, ExtendedStats): ensure all per-bucket reads/writes (counts, sums, compensations, mins, maxes, Kahan state) use the provided bucket consistently and are correctly initialized.

Possibly related PRs

Suggested labels

enhancement, Search:Aggregations, Search:Performance, v3.4.0, backport 3.4

Suggested reviewers

  • msfroh
  • reta
  • mch2
  • sachinpkale
  • shwetathareja
  • andrross
  • dbwiddis
  • ashking94
  • Bukhtawar
  • owaiskazi19
  • saratvemulapalli
  • cwperks
  • kotwanikunal
  • sohami
  • jed326
  • anasalkouz
  • Rishikesh1159
  • CEHENKLE
  • gbbafna

Poem

🐰
With buckets passed through every range,
I hop and tally, small and strange;
Each sum and min now knows its bin,
Per-bucket counts let insights in.
Hop on — aggregations spring!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely describes the main change: enabling skiplist-based collection using the collectRange mechanism.
Description check ✅ Passed The PR description explains the change and references a related issue, though some template checkboxes remain unchecked without explanation.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/main/java/org/opensearch/search/aggregations/metrics/StatsAggregator.java (1)

157-179: Fix critical logic errors in bucket-aware range collection.

Two critical issues in the collectRange implementation:

  1. Line 162: Loop condition uses maximum (the stats variable tracking max value) instead of max (the doc ID range parameter). This causes incorrect iteration bounds.
  2. Line 165: Count increment hardcoded to bucket 0 instead of using the bucket parameter, breaking per-bucket accounting.

Apply this diff to fix both issues:

 public void collectRange(int min, int max, long bucket) throws IOException {
     growStats(bucket);

     double minimum = mins.get(bucket);
     double maximum = maxes.get(bucket);
-    for (int doc = min; doc < maximum; doc++) {
+    for (int doc = min; doc < max; doc++) {
         if (values.advanceExact(doc)) {
             final int valuesCount = values.docValueCount();
-            counts.increment(0, valuesCount);
+            counts.increment(bucket, valuesCount);

             for (int i = 0; i < valuesCount; i++) {
                 double value = values.nextValue();
                 kahanSummation.add(value);
                 minimum = Math.min(minimum, value);
                 maximum = Math.max(maximum, value);
             }
         }
     }
     sums.set(bucket, kahanSummation.value());
     compensations.set(bucket, kahanSummation.delta());
     mins.set(bucket, minimum);
     maxes.set(bucket, maximum);
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 799fb9b and 3edda3b.

📒 Files selected for processing (19)
  • modules/aggs-matrix-stats/src/main/java/org/opensearch/search/aggregations/matrix/stats/MatrixStatsAggregator.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/LeafBucketCollector.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/HistogramSkiplistLeafCollector.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/composite/CompositeAggregator.java (3 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/filter/FilterAggregator.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/filter/FiltersAggregator.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/AutoDateHistogramAggregator.java (2 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/DateHistogramAggregator.java (2 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/NumericHistogramAggregator.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/range/RangeAggregator.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/terms/GlobalOrdinalsStringTermsAggregator.java (7 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/terms/LongRareTermsAggregator.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/bucket/terms/NumericTermsAggregator.java (2 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/metrics/AvgAggregator.java (2 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/metrics/ExtendedStatsAggregator.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/metrics/MaxAggregator.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/metrics/MinAggregator.java (1 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/metrics/StatsAggregator.java (2 hunks)
  • server/src/main/java/org/opensearch/search/aggregations/metrics/SumAggregator.java (1 hunks)
🔇 Additional comments (24)
server/src/main/java/org/opensearch/search/aggregations/metrics/MaxAggregator.java (1)

184-193: LGTM - Per-bucket range collection correctly implemented.

The collectRange method now properly accepts a bucket parameter and uses it consistently for growMaxes(), reading the initial max, and storing the final result. This aligns with the bucket-aware collection pattern introduced across the codebase.

server/src/main/java/org/opensearch/search/aggregations/metrics/MinAggregator.java (1)

184-193: LGTM - Per-bucket range collection correctly implemented.

The implementation mirrors the MaxAggregator pattern, correctly using the bucket parameter for growMins(), reading the initial min, and storing the final result.

server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/AutoDateHistogramAggregator.java (2)

457-459: LGTM - Correct delegation pattern for bucket-aware range collection.

The FromSingle inner class properly overrides collectRange with the bucket parameter and delegates to super.collectRange(min, max, bucket), enabling monomorphic call optimization.


751-753: LGTM - Consistent with FromSingle implementation.

The FromMany inner class follows the same delegation pattern, maintaining consistency across both implementations.

server/src/main/java/org/opensearch/search/aggregations/LeafBucketCollector.java (2)

134-137: LGTM - Good backward-compatible convenience overload.

The zero-argument bucket overload delegates to collectRange(min, max, 0), maintaining backward compatibility for callers that don't need bucket-specific collection.


151-158: LGTM - Well-designed experimental API.

The new collectRange(int min, int max, long bucket) method is appropriately marked with @ExperimentalApi. The default implementation correctly passes the bucket parameter to collect(docId, bucket), and the Javadoc clearly documents the expected behavior. The comment about overriding for monomorphic calls is helpful for implementers.

server/src/main/java/org/opensearch/search/aggregations/bucket/composite/CompositeAggregator.java (3)

647-649: LGTM - Proper bucket propagation in getLeafCollector.

The anonymous LeafBucketCollector correctly delegates to super.collectRange(min, max, bucket).


686-688: LGTM - Proper bucket propagation in getFirstPassCollector.

Consistent with the other collectors in this file.


755-757: LGTM - Proper bucket propagation in getSecondPassCollector.

All three anonymous LeafBucketCollector implementations in this file now consistently override collectRange with the bucket parameter and delegate appropriately.

server/src/main/java/org/opensearch/search/aggregations/bucket/terms/LongRareTermsAggregator.java (1)

126-129: LGTM!

The collectRange method signature is correctly updated to include the long bucket parameter and properly delegates to super.collectRange(min, max, bucket), aligning with the bucket-aware range collection API.

server/src/main/java/org/opensearch/search/aggregations/bucket/terms/NumericTermsAggregator.java (2)

167-170: LGTM!

The collectRange method is correctly updated to accept and forward the long bucket parameter to the superclass implementation.


735-738: LGTM!

The collectRange in the wrapped collector correctly propagates the bucket parameter, maintaining consistency with the bucket-aware range collection pattern.

server/src/main/java/org/opensearch/search/aggregations/metrics/ExtendedStatsAggregator.java (1)

172-175: LGTM!

The collectRange correctly accepts and forwards the long bucket parameter. Since the superclass implementation will iterate over the range and call collect(doc, bucket), the per-bucket statistics (counts, sums, mins, maxes, etc.) will be correctly updated for the specified bucket.

server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/NumericHistogramAggregator.java (1)

147-150: LGTM!

The collectRange method correctly accepts the long bucket parameter and delegates to the superclass implementation, consistent with the bucket-aware range collection pattern across histogram aggregators.

server/src/main/java/org/opensearch/search/aggregations/bucket/HistogramSkiplistLeafCollector.java (1)

216-246: Well-implemented bucket-aware range collection with skiplist optimization.

The implementation correctly:

  1. Advances the skipper if needed before processing the range
  2. Caps upToExclusive at max to avoid over-collecting
  3. Uses the optimized path when all docs in the range map to the same bucket (upToSameBucket), incrementing doc count in bulk and propagating range collection to sub-collectors
  4. Falls back to per-doc collection otherwise
  5. Correctly uses upToBucketIndex for sub-collector calls in the same-bucket path (the actual histogram bucket ordinal) while using the original bucket parameter for the fallback path
server/src/main/java/org/opensearch/search/aggregations/bucket/filter/FilterAggregator.java (1)

92-95: LGTM!

The collectRange override correctly propagates the bucket parameter to super.collectRange(min, max, bucket), aligning with the bucket-aware range collection pattern introduced across the codebase.

server/src/main/java/org/opensearch/search/aggregations/bucket/filter/FiltersAggregator.java (1)

201-204: LGTM!

The collectRange override correctly propagates the bucket parameter to super.collectRange(min, max, bucket), consistent with the bucket-aware range collection pattern.

server/src/main/java/org/opensearch/search/aggregations/bucket/range/RangeAggregator.java (1)

367-370: LGTM!

The collectRange override correctly propagates the bucket parameter to the superclass, enabling bucket-aware range collection for the range aggregator.

server/src/main/java/org/opensearch/search/aggregations/bucket/histogram/DateHistogramAggregator.java (2)

257-260: LGTM!

The collectRange override in the single-valued collector correctly propagates the bucket parameter.


290-293: LGTM!

The collectRange override in the multi-valued collector correctly propagates the bucket parameter, consistent with the single-valued path.

modules/aggs-matrix-stats/src/main/java/org/opensearch/search/aggregations/matrix/stats/MatrixStatsAggregator.java (1)

123-126: LGTM!

The collectRange override correctly propagates the bucket parameter, enabling bucket-aware range collection for the matrix stats aggregator.

server/src/main/java/org/opensearch/search/aggregations/metrics/AvgAggregator.java (1)

168-184: LGTM!

The collectRange implementation correctly uses the bucket parameter for all per-bucket state operations:

  • setKahanSummation(bucket) initializes arrays and Kahan summation state for the specific bucket
  • counts.increment(bucket, count) updates the correct bucket's count
  • sums.set(bucket, ...) and compensations.set(bucket, ...) store results in the correct bucket

This is consistent with the collect(int doc, long bucket) and collect(DocIdStream, long bucket) methods in the same class.

server/src/main/java/org/opensearch/search/aggregations/metrics/SumAggregator.java (1)

152-163: LGTM! Bucket-aware range collection implemented correctly.

The collectRange method now properly accepts and uses the bucket parameter for per-bucket state management. Kahan summation is correctly initialized from the bucket's existing state, and results are stored back to the correct bucket index.

server/src/main/java/org/opensearch/search/aggregations/bucket/terms/GlobalOrdinalsStringTermsAggregator.java (1)

284-286: LGTM! Bucket parameter properly propagated through collector hierarchy.

All collectRange overrides correctly forward the bucket parameter to their respective superclass implementations. This systematic propagation ensures the bucket context is maintained throughout the collection chain for both single-valued and multi-valued ordinal paths, as well as the LowCardinality and SignificantTermsResults variants.

Also applies to: 308-310, 338-340, 365-367, 603-605, 630-632, 1246-1248

Signed-off-by: Ankit Jain <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3edda3b and e9d3c28.

📒 Files selected for processing (1)
  • CHANGELOG.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
CHANGELOG.md

[grammar] ~15-~15: Use a hyphen to join words.
Context: ...penSearch/pull/20241)) - Enable skiplist based collection using collectRange ([#2...

(QB_NEW_EN_HYPHEN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: gradle-check
  • GitHub Check: assemble (25, ubuntu-24.04-arm)
  • GitHub Check: assemble (25, windows-latest)
  • GitHub Check: assemble (21, ubuntu-24.04-arm)
  • GitHub Check: assemble (25, ubuntu-latest)
  • GitHub Check: assemble (21, windows-latest)
  • GitHub Check: assemble (21, ubuntu-latest)
  • GitHub Check: precommit (21, windows-latest)
  • GitHub Check: Analyze (java)
  • GitHub Check: precommit (25, macos-15-intel)
  • GitHub Check: precommit (25, ubuntu-24.04-arm)
  • GitHub Check: precommit (25, macos-15)
  • GitHub Check: precommit (21, windows-2025, true)
  • GitHub Check: precommit (21, macos-15)
  • GitHub Check: precommit (25, windows-latest)
  • GitHub Check: precommit (21, ubuntu-latest)
  • GitHub Check: precommit (25, ubuntu-latest)
  • GitHub Check: precommit (21, macos-15-intel)
  • GitHub Check: precommit (21, ubuntu-24.04-arm)
  • GitHub Check: detect-breaking-change

- Handle custom metadata files in subdirectory-store ([#20157](https://github.com/opensearch-project/OpenSearch/pull/20157))
- Add support for missing proto fields in GRPC FunctionScore and Highlight ([#20169](https://github.com/opensearch-project/OpenSearch/pull/20169))
- Ensure all modules are included in INTEG_TEST testcluster distribution ([#20241](https://github.com/opensearch-project/OpenSearch/pull/20241))
- Enable skiplist based collection using collectRange ([#20268](https://github.com/opensearch-project/OpenSearch/pull/20268))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add hyphen to compound adjective.

Per the static analysis hint, compound adjectives should be hyphenated when they precede a noun: "skiplist-based collection" instead of "skiplist based collection".

Apply this diff:

-- Enable skiplist based collection using collectRange ([#20268](https://github.com/opensearch-project/OpenSearch/pull/20268))
+- Enable skiplist-based collection using collectRange ([#20268](https://github.com/opensearch-project/OpenSearch/pull/20268))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Enable skiplist based collection using collectRange ([#20268](https://github.com/opensearch-project/OpenSearch/pull/20268))
- Enable skiplist-based collection using collectRange ([#20268](https://github.com/opensearch-project/OpenSearch/pull/20268))
🧰 Tools
🪛 LanguageTool

[grammar] ~15-~15: Use a hyphen to join words.
Context: ...penSearch/pull/20241)) - Enable skiplist based collection using collectRange ([#2...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
In CHANGELOG.md around line 15, the compound adjective "skiplist based
collection" needs a hyphen; change it to "skiplist-based collection" while
preserving the rest of the line (including the PR link and parentheses) exactly
as-is.

@jainankitk
Copy link
Contributor Author

{"run-benchmark-test": "id_3"}

@github-actions
Copy link
Contributor

❌ Gradle check result for e9d3c28: FAILURE

Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change?

@jainankitk
Copy link
Contributor Author

Flaky test failure:

[Test Result](https://build.ci.opensearch.org/job/gradle-check/68884/testReport/) (2 failures / +2)

    [org.opensearch.remotestore.RestoreShallowSnapshotV2IT.testContinuousIndexing {p0={"opensearch.experimental.feature.writable_warm_index.enabled":"true"}}](https://build.ci.opensearch.org/job/gradle-check/68884/testReport/junit/org.opensearch.remotestore/RestoreShallowSnapshotV2IT/testContinuousIndexing__p0___opensearch_experimental_feature_writable_warm_index_enabled___true___/)

Retrying gradle check

@github-actions
Copy link
Contributor

❕ Gradle check result for e9d3c28: UNSTABLE

Please review all flaky tests that succeeded after retry and create an issue if one does not already exist to track the flaky failure.

@codecov
Copy link

codecov bot commented Dec 17, 2025

Codecov Report

❌ Patch coverage is 8.06452% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.25%. Comparing base (930ae74) to head (e9d3c28).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...gations/bucket/HistogramSkiplistLeafCollector.java 0.00% 16 Missing ⚠️
...ket/terms/GlobalOrdinalsStringTermsAggregator.java 0.00% 7 Missing ⚠️
...h/search/aggregations/metrics/StatsAggregator.java 0.00% 7 Missing ⚠️
...rch/search/aggregations/metrics/AvgAggregator.java 0.00% 4 Missing ⚠️
...egations/bucket/composite/CompositeAggregator.java 0.00% 3 Missing ⚠️
...rch/search/aggregations/metrics/MaxAggregator.java 0.00% 3 Missing ⚠️
...rch/search/aggregations/metrics/MinAggregator.java 0.00% 3 Missing ⚠️
...rch/search/aggregations/metrics/SumAggregator.java 0.00% 3 Missing ⚠️
.../bucket/histogram/AutoDateHistogramAggregator.java 0.00% 2 Missing ⚠️
...ions/bucket/histogram/DateHistogramAggregator.java 0.00% 2 Missing ⚠️
... and 6 more
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #20268      +/-   ##
============================================
+ Coverage     73.20%   73.25%   +0.05%     
- Complexity    71745    71786      +41     
============================================
  Files          5795     5795              
  Lines        328304   328314      +10     
  Branches      47283    47284       +1     
============================================
+ Hits         240334   240522     +188     
+ Misses        68663    68505     -158     
+ Partials      19307    19287      -20     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant