Add Hybrid Cardinality collector to prioritize Ordinals Collector#19524
Add Hybrid Cardinality collector to prioritize Ordinals Collector#19524sandeshkr419 merged 4 commits intoopensearch-project:mainfrom
Conversation
|
❌ Gradle check result for a2f5dd7: null 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? |
|
❌ Gradle check result for 41a9e69: 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? |
|
❌ Gradle check result for c142ac4: null 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? |
|
❌ Gradle check result for 88989f3: 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? |
88989f3 to
c142ac4
Compare
|
❌ Gradle check result for c142ac4: 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? |
c142ac4 to
06ce5c3
Compare
|
❌ Gradle check result for 06ce5c3: null 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? |
06ce5c3 to
fc328a2
Compare
|
❌ Gradle check result for fc328a2: 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? |
|
@sandeshkr419 - Can you help get this change merged and backported for 3.4? This is important performance optimizations that many customers should be able to benefit from. |
fc246ca to
aabab15
Compare
|
❌ Gradle check result for aabab15: 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? |
|
❌ Gradle check result for aabab15: 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? |
aabab15 to
d2730aa
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
server/src/test/java/org/opensearch/search/aggregations/metrics/CardinalityAggregatorTests.java (1)
750-778: Consider adding assertion for active collector type.These tests verify the overall functionality but only check that the collector is a
HybridCollectorwithout verifying whether it stayed withOrdinalsCollectoror switched. For better coverage, consider adding an assertion similar to the other tests:- }, collector -> { assertTrue(collector instanceof HybridCollector); }, fieldType, true, 1024L * 1024L); + }, collector -> { + assertTrue(collector instanceof HybridCollector); + assertTrue(((HybridCollector) collector).getActiveCollector() instanceof OrdinalsCollector); + }, fieldType, true, 1024L * 1024L);server/src/main/java/org/opensearch/search/aggregations/metrics/CardinalityAggregator.java (1)
944-947: Verify close() handles the ordinalsCollector reference after switch.After switching to DirectCollector,
ordinalsCollectorholds a reference to an already-closed collector. The currentclose()implementation only closesactiveCollector, which is correct. However, if the class is refactored in the future, someone might accidentally try to closeordinalsCollectoragain.Consider nullifying the reference or adding a comment for clarity:
private void switchToDirectCollector() throws IOException { // Post collect all the already computed data from OrdinalsCollector ordinalsCollector.postCollect(); ordinalsCollector.close(); logger.debug("Hybrid collector switching to DirectCollector due to memory threshold exceeded"); + // Note: ordinalsCollector is now closed; only activeCollector should be used hereafter // Create DirectCollector with computed data activeCollector = new DirectCollector(counts, hashValues); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
CHANGELOG.md(1 hunks)rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/170_cardinality_metric.yml(2 hunks)rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/170_cardinality_metric_unsigned.yml(2 hunks)server/src/main/java/org/opensearch/search/aggregations/metrics/CardinalityAggregator.java(9 hunks)server/src/test/java/org/opensearch/search/aggregations/metrics/CardinalityAggregatorTests.java(2 hunks)test/framework/src/main/java/org/opensearch/search/aggregations/AggregatorTestCase.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- CHANGELOG.md
- rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/170_cardinality_metric.yml
- rest-api-spec/src/main/resources/rest-api-spec/test/search.aggregation/170_cardinality_metric_unsigned.yml
- test/framework/src/main/java/org/opensearch/search/aggregations/AggregatorTestCase.java
🔇 Additional comments (10)
server/src/test/java/org/opensearch/search/aggregations/metrics/CardinalityAggregatorTests.java (5)
72-73: LGTM!Imports for the new inner classes
HybridCollectorandOrdinalsCollectorare correctly added to support the new test assertions.
639-701: LGTM! Well-structured test helper method.The
testAggregationHybridCollectorhelper correctly sets up aCardinalityAggregationContextwith configurablehybridCollectorEnabledflag andmemoryThreshold, allowing flexible testing of hybrid collector behavior under different configurations.
703-730: Good test coverage for enabled/disabled states.These tests correctly verify that:
- When enabled, a
HybridCollectorwrappingOrdinalsCollectoris used- When disabled,
OrdinalsCollectoris used directly (without hybrid wrapper)
732-748: Test correctly verifies memory threshold switching.Using an extremely low threshold of
1byte ensures the switch fromOrdinalsCollectortoDirectCollectoris triggered. The HLL++ tolerance of ±10 for 1000 values is appropriate.
854-862: Good verification of the singleton exception optimization.This test properly validates the performance optimization: the singleton pattern and suppressed stack trace generation ensure minimal overhead when the exception is used for control flow.
server/src/main/java/org/opensearch/search/aggregations/metrics/CardinalityAggregator.java (5)
94-108: Well-designed control flow exception.The singleton pattern with disabled stack trace generation (
writableStackTrace: false) is the correct optimization for exceptions used as control flow signals. This avoids the ~100x overhead of stack trace capture in hot paths.
618-682: Memory monitoring design is efficient.The implementation correctly:
- Only accumulates memory when new buckets are created (not on every collect call)
- Uses a flag (
memoryMonitoringEnabled) to avoid overhead when monitoring is disabled- Throws the singleton exception immediately after threshold breach
One observation: the
BitArrayis allocated before the threshold check (lines 669-670). This is acceptable since the memory would be reclaimed during the subsequentclose()call inswitchToDirectCollector().
866-927: HybridCollector correctly handles the exception-based switching pattern.The try-catch-retry pattern in each collect method is correct. The exception is only thrown from
getBitArray()when creating new buckets, which occurs:
- Before
stream.forEach()incollect(DocIdStream, long)- Before the loop in
collectRange(int, int)- At the start of each doc in
collect(int, long)This ensures no partial processing issues when switching collectors.
929-947: Resource management in switchToDirectCollector is correct.The method properly:
- Calls
postCollect()to flush accumulated ordinal data to the HyperLogLog++ counts- Closes the OrdinalsCollector to release memory
- Creates the DirectCollector to continue collection
One note: after switching,
ordinalsCollectorstill holds a reference to the closed collector, but this is fine sinceclose()only releasesactiveCollector, andswitchToDirectCollectorcan only be called once (DirectCollector doesn't throwMemoryLimitExceededException).
110-130: Settings are well-configured, but verify proper registration in ClusterSettings.Using
Setting.memorySizeSettingcorrectly supports both percentage-based (e.g., "1%") and absolute (e.g., "10mb") threshold values. TheNodeScopeandDynamicproperties allow runtime configuration changes. However, ensure that bothCARDINALITY_AGGREGATION_HYBRID_COLLECTOR_ENABLEDandCARDINALITY_AGGREGATION_HYBRID_COLLECTOR_MEMORY_THRESHOLDare properly registered in the ClusterSettings configuration.
server/src/main/java/org/opensearch/search/aggregations/metrics/CardinalityAggregator.java
Show resolved
Hide resolved
…9524) * Add Hybrid Cardinality collector to prioritize Ordinals Collector Current cardinality aggregator logic selects DirectCollector over OrdinalsCollector when relative memory overhead due to OrdinalsCollector (compared to DirectCollector) is higher. Because of this relative memory consumption logic, DirectCollector is selected for high cardinality aggregation queries. DirectCollector is slower compared to OrdinalsCollector. This default selection leads to higher search latency even when Opensearch process have available memory to use ordinals collector for faster query performance. There is no way to figure out memory requirement for nested aggregation because number of buckets are dynamically created as we traverse through all the matching document ids. To overcome this limitation, this change create a hybrid collector which will first use Ordinals Collector and will switch to DirectCollector if memory usage for Ordinals Collector Increase beyond certain threshold. When Hybrid collector switch from Ordinals Collector to Direct Collector, it will utilize already computed aggregation data from Ordinals Collector so that we do not have to rebuild aggregation result using Direct Collector. Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Address PR comments Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Address PR comments Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Fix UTs Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> --------- Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> (cherry picked from commit 710d02f) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…9524) (#20208) * Add Hybrid Cardinality collector to prioritize Ordinals Collector Current cardinality aggregator logic selects DirectCollector over OrdinalsCollector when relative memory overhead due to OrdinalsCollector (compared to DirectCollector) is higher. Because of this relative memory consumption logic, DirectCollector is selected for high cardinality aggregation queries. DirectCollector is slower compared to OrdinalsCollector. This default selection leads to higher search latency even when Opensearch process have available memory to use ordinals collector for faster query performance. There is no way to figure out memory requirement for nested aggregation because number of buckets are dynamically created as we traverse through all the matching document ids. To overcome this limitation, this change create a hybrid collector which will first use Ordinals Collector and will switch to DirectCollector if memory usage for Ordinals Collector Increase beyond certain threshold. When Hybrid collector switch from Ordinals Collector to Direct Collector, it will utilize already computed aggregation data from Ordinals Collector so that we do not have to rebuild aggregation result using Direct Collector. * Address PR comments * Address PR comments * Fix UTs --------- (cherry picked from commit 710d02f) Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…ensearch-project#19524) * Add Hybrid Cardinality collector to prioritize Ordinals Collector Current cardinality aggregator logic selects DirectCollector over OrdinalsCollector when relative memory overhead due to OrdinalsCollector (compared to DirectCollector) is higher. Because of this relative memory consumption logic, DirectCollector is selected for high cardinality aggregation queries. DirectCollector is slower compared to OrdinalsCollector. This default selection leads to higher search latency even when Opensearch process have available memory to use ordinals collector for faster query performance. There is no way to figure out memory requirement for nested aggregation because number of buckets are dynamically created as we traverse through all the matching document ids. To overcome this limitation, this change create a hybrid collector which will first use Ordinals Collector and will switch to DirectCollector if memory usage for Ordinals Collector Increase beyond certain threshold. When Hybrid collector switch from Ordinals Collector to Direct Collector, it will utilize already computed aggregation data from Ordinals Collector so that we do not have to rebuild aggregation result using Direct Collector. Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Address PR comments Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Address PR comments Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Fix UTs Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> --------- Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com>
…ensearch-project#19524) * Add Hybrid Cardinality collector to prioritize Ordinals Collector Current cardinality aggregator logic selects DirectCollector over OrdinalsCollector when relative memory overhead due to OrdinalsCollector (compared to DirectCollector) is higher. Because of this relative memory consumption logic, DirectCollector is selected for high cardinality aggregation queries. DirectCollector is slower compared to OrdinalsCollector. This default selection leads to higher search latency even when Opensearch process have available memory to use ordinals collector for faster query performance. There is no way to figure out memory requirement for nested aggregation because number of buckets are dynamically created as we traverse through all the matching document ids. To overcome this limitation, this change create a hybrid collector which will first use Ordinals Collector and will switch to DirectCollector if memory usage for Ordinals Collector Increase beyond certain threshold. When Hybrid collector switch from Ordinals Collector to Direct Collector, it will utilize already computed aggregation data from Ordinals Collector so that we do not have to rebuild aggregation result using Direct Collector. Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Address PR comments Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Address PR comments Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Fix UTs Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> --------- Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com>
…ensearch-project#19524) * Add Hybrid Cardinality collector to prioritize Ordinals Collector Current cardinality aggregator logic selects DirectCollector over OrdinalsCollector when relative memory overhead due to OrdinalsCollector (compared to DirectCollector) is higher. Because of this relative memory consumption logic, DirectCollector is selected for high cardinality aggregation queries. DirectCollector is slower compared to OrdinalsCollector. This default selection leads to higher search latency even when Opensearch process have available memory to use ordinals collector for faster query performance. There is no way to figure out memory requirement for nested aggregation because number of buckets are dynamically created as we traverse through all the matching document ids. To overcome this limitation, this change create a hybrid collector which will first use Ordinals Collector and will switch to DirectCollector if memory usage for Ordinals Collector Increase beyond certain threshold. When Hybrid collector switch from Ordinals Collector to Direct Collector, it will utilize already computed aggregation data from Ordinals Collector so that we do not have to rebuild aggregation result using Direct Collector. Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Address PR comments Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Address PR comments Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> * Fix UTs Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com> --------- Signed-off-by: Anand Pravinbhai Patel <anapat@amazon.com>
Description
Current cardinality aggregator logic selects DirectCollector over OrdinalsCollector when relative memory overhead due to OrdinalsCollector (compared to DirectCollector) is higher. Because of this relative memory consumption logic, DirectCollector is selected for high cardinality aggregation queries. DirectCollector is slower compared to OrdinalsCollector. This default selection leads to higher search latency even when Opensearch process have available memory to use ordinals collector for faster query performance.
There is no way to figure out memory requirement for nested aggregation because number of buckets are dynamically created as we traverse through all the matching document ids. To overcome this limitation, this change create a hybrid collector which will first use Ordinals Collector and will switch to DirectCollector if memory usage for Ordinals Collector Increase beyond certain threshold. When Hybrid collector switch from Ordinals Collector to Direct Collector, it will utilize already computed aggregation data from Ordinals Collector so that we do not have to rebuild aggregation result using Direct Collector.
Signed-off-by: Anand Pravinbhai Patel anapat@amazon.com
Related Issues
Resolves #19260
Check List
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
✏️ Tip: You can customize this high-level summary in your review settings.