[BugFix] Restore dedup pushdown when combined with WHERE clause (#5482)#5488
Conversation
…search-project#5482) Run PPLSimplifyDedupRule before FilterMergeRule in the HEP optimizer so the bucket-non-null filter PPL emits for dedup is matched as-is. With the previous order, an upstream user where filter sat adjacent to the bucket-non-null filter; FilterMergeRule fired first and merged them into a conjunction that no longer satisfied PPLSimplifyDedupRule's operand predicate, defeating dedup pushdown to the shard. Use sequential addRuleInstance phases for explicit ordering rather than addRuleCollection, which is documented as non-deterministic in firing order. Adds two regression tests in CalcitePPLDedupTest: one that asserts LogicalDedup is produced under the fixed order, and one that pins the buggy behavior under the swapped order. Signed-off-by: ryan-gh-bot <ryan-gh-bot@users.noreply.github.com>
PR Reviewer Guide 🔍(Review updated until commit 38fc6c3)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 38fc6c3 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 95e2aeb
Suggestions up to commit a66ad5b
Suggestions up to commit f67458f
Suggestions up to commit 0bf8f7c
Suggestions up to commit 19f506d
|
RyanL1997
left a comment
There was a problem hiding this comment.
Hi @ryan-gh-bot , thanks for the change, left some reviews.
…search-project#5488) Per maintainer review feedback, the regression-test JavaDoc for testDedupAfterWhereProducesLogicalDedup mentioned the originating issue URL. The remaining JavaDoc paragraphs already describe the bug shape and the rule-ordering invariant, so the explicit issue link is unnecessary noise. Signed-off-by: ryan-gh-bot <ryan-gh-bot@users.noreply.github.com>
|
Persistent review updated to latest commit 19f506d |
|
@ryan-gh-bot the rule-reordering fix works but treats a symptom rather than the root cause. The actual brittleness is in Please revise to take option 1 from the issue instead:
Please sanity-check that the loosened operand can't accidentally match user-written |
|
Working on |
…oject#5488) Address review feedback on opensearch-project#5488: extend mayBeFilterFromBucketNonNull to accept the merged conjunction shape FilterMergeRule produces, so PPLSimplifyDedupRule fires regardless of whether FilterMergeRule has already merged the user where clause into the bucket-non-null filter. PPLSimplifyDedupRule.apply now splits the bottom filter into IS_NOT_NULL conjuncts on partition keys (absorbed into LogicalDedup semantics) and any remaining conjuncts (preserved as a separate filter below the new LogicalDedup), so a user predicate that was folded in is no longer dropped. With the operand predicate order-independent, the HEP rule order is no longer a load-bearing invariant. Revert the addRuleCollection -> addRuleInstance change in CalciteToolsHelper.HEP_PROGRAM that the previous patch introduced. Replace the regression test that pinned the buggy rule order with one that asserts the user-visible contract: with where preceding dedup, a LogicalDedup is produced and the user predicate is preserved regardless of which order FilterMergeRule and PPLSimplifyDedupRule fire. Signed-off-by: ryan-gh-bot <ryan-gh-bot@users.noreply.github.com>
|
Done — pushed |
|
Persistent review updated to latest commit 0bf8f7c |
I'm weighing the pros and cons of these two options:
|
penghuo
left a comment
There was a problem hiding this comment.
- Add end-to-end test asserts dedup is actually pushed down when where precedes dedup.
- Refactor PR description to match the implementation.
Per @penghuo review: PlanUtils.java - Revert mayBeFilterFromBucketNonNull to the original ternary form; drop the early-return refactor (no behavior change, just cleaner). - Drop the !rexCall.getOperands().isEmpty() guard before .get(0): IS NOT NULL is always unary in Calcite, so the check is dead. - Trim the JavaDoc to the essentials (un-merged vs merged-AND shape; concrete partition-key match happens in PPLSimplifyDedupRule#apply). - Promote isNotNullOnRef from package-private to public so the dedup rule can reuse it from a different package. PPLSimplifyDedupRule.java - isNotNullOnPartitionKey now delegates the IS NOT NULL($ref) structural check to PlanUtils.isNotNullOnRef and adds the partition-key index check on top. CalciteExplainIT.java - Add testDedupAfterWherePushDown: an end-to-end regression that runs the shape `... | where <pred> | dedup <field>` and asserts (a) LogicalDedup appears in the explain output (PPLSimplifyDedupRule fired even after FilterMergeRule had a chance to merge the two filters), and (b) EnumerableWindow does NOT appear (the in-memory ROW_NUMBER fallback the bug caused is gone). Signed-off-by: Jialiang Liang <jiallian@amazon.com>
|
Persistent review updated to latest commit ae86ece |
|
Hi @penghuo, thanks for the feedback:
added
PR description refactored — now describes the actual implementation (loosen predicate at source, not reorder HEP rules) instead of the bot's earlier reorder approach. |
|
Walked the axes against
Net: this PR covers the |
|
Checking on the CI failures |
7725e0b to
f67458f
Compare
|
Persistent review updated to latest commit 7725e0b |
|
Persistent review updated to latest commit f67458f |
PPLSimplifyDedupRule correctly produces Dedup -> Filter(user where) -> Scan when a `where` precedes `dedup`. The Filter between Dedup and Scan blocks DedupPushdownRule's strict Dedup -> Project -> Scan operand chain, so Volcano falls back to PPLDedupConvertRule and the plan ends up with an in-memory ROW_NUMBER window instead of the pushed-down composite + top_hits aggregation. Add a WITH_FILTER operand variant to DedupPushdownRule that matches Dedup -> Filter -> Scan, pushes the filter into the scan, then runs the standard apply() on the resulting Dedup -> Project -> Scan shape. Signed-off-by: Jialiang Liang <jiallian@amazon.com>
f67458f to
a66ad5b
Compare
|
Persistent review updated to latest commit a66ad5b |
pushDownFilter returns a Filter (not a CalciteLogicalIndexScan) when the predicate analyzer can only partially push the condition. The previous cast would have thrown ClassCastException in that case. Use an instanceof-pattern check so the rule bails out cleanly and leaves the plan untouched, letting other rules handle the residual. Also drop a stale issue-link reference from a test comment. Signed-off-by: Jialiang Liang <jiallian@amazon.com>
|
Persistent review updated to latest commit 95e2aeb |
|
The current CI failure is unrelated. The failure is: |
Reflowed the JavaDoc on testDedupAfterWhereProducesLogicalDedupWithProductionHepProgram to match Google Java Format's preferred line break, fixing the spotlessJavaCheck violation that failed the unit-test matrix on CI. Signed-off-by: Jialiang Liang <jiallian@amazon.com>
|
Persistent review updated to latest commit 38fc6c3 |
Description
PPL
dedupis initially compiled into aROW_NUMBER() OVER (PARTITION BY field)window pattern with anIS NOT NULL(field)bucket-non-null filter directly above the scan.PPLSimplifyDedupRulefolds that pattern into aLogicalDedupwhichDedupPushdownRulethen rewrites into acomposite+top_hitsaggregation against the shard.The HEP program registers
FilterMergeRule.DEFAULTandPPLSimplifyDedupRule.DEDUP_SIMPLIFY_RULEviaaddRuleCollection. When a userwhereprecedesdedup, the user's filter sits adjacent to the bucket-non-null filter;FilterMergeRulecan merge them intoAND(IS NOT NULL(field), <user predicate>). The previousmayBeFilterFromBucketNonNulloperand predicate required every conjunct of the AND to beIS NOT NULL($ref)(allMatch), so the merged shape failed the match.PPLSimplifyDedupRulenever fired, noLogicalDedupwas produced, andDedupPushdownRulehad nothing to match — dedup fell through to the in-memoryROW_NUMBERwindow form on the coordinator, which exceeds timeouts on large indices.Fix
Loosen the operand predicate at the source rather than reordering HEP rules.
PlanUtils.mayBeFilterFromBucketNonNullnow accepts the un-mergedIS NOT NULL($ref)shape or anyANDwhose conjuncts contain at least oneIS NOT NULL($ref)(anyMatchinstead ofallMatch). The predicate is order-independent — the simplify rule fires whether or notFilterMergeRuleran first.PPLSimplifyDedupRule.applysplits the matched filter's conjuncts into (a) theIS NOT NULLon a partition key (absorbed byLogicalDedup) and (b) everything else (emitted as a separate filter belowLogicalDedup), so any user predicate folded into the AND is preserved verbatim. A defensive bail-out: if noIS NOT NULLon a partition key is present in the matched filter, the rule returns without transforming.PlanUtils.isNotNullOnRefis promoted topublicsoPPLSimplifyDedupRule.isNotNullOnPartitionKeycan reuse it.Verification
CalcitePPLDedupTest.testDedupAfterWhereProducesLogicalDedupruns the rule sequence (PPLSimplifyDedupRule, FilterMergeRule) on awhere+dedupplan and asserts thatLogicalDedupis produced regardless of rule order. The user predicate appears as a separate filter belowLogicalDedup, ready for downstream pushdown:CalcitePPLDedupTest.testDedupAfterWhereProducesLogicalDedupRegardlessOfRuleOrderexercises the swapped order (FilterMergeRule first) and asserts the same result, pinning the user-visible contract rather than any rule-ordering invariant.CalciteExplainIT.testDedupAfterWherePushDown(new) — end-to-end against a live cluster: runssource=opensearch-sql_test_index_account | where age > 25 | dedup genderand assertsLogicalDedupis present in the explain output andEnumerableWindowis absent (i.e., dedup is actually pushed down and the in-memory window fallback the bug caused is gone).All existing
CalcitePPLDedupTestcases continue to pass.Related Issues
Resolves #5482
Check List
--signoffor-s.