Repository navigation
feat: add grouping equivalence properties - #24698
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #24698 +/- ##
==========================================
- Coverage 82.45% 82.44% -0.01%
==========================================
Files 1140 1141 +1
Lines 436434 437085 +651
Branches 436434 437085 +651
==========================================
+ Hits 359840 360355 +515
- Misses 54840 54879 +39
- Partials 21754 21851 +97 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
b2ceb1a to
6bd697f
Compare
1e7d83a to
72d66b3
Compare
fa946cb to
db683f4
Compare
|
Hey @jayzhan211 , this is ready for another look when you get the chance 🙇 |
| self.oeq_cache.clear(); | ||
| } | ||
|
|
||
| /// Clears grouping information invalidated by an operation that changes |
There was a problem hiding this comment.
@xavlee thanks for the updates, both earlier points look good now. One remaining gap: an operator that reorders rows and calls only clear_orderings() still forwards its input's groupings. That's the same thing AggregateExec hit in the previous revision, and it's the pattern custom operators downstream already follow. Could clear_orderings() also clear groupings? Then every operator that already drops orderings stays correct without having to know about groupings:
pub fn clear_orderings(&mut self) {
self.oeq_class.clear();
self.oeq_cache.clear();
+ self.clear_groupings();
} eq_properties.clear_orderings();
- if input_partitions > 1 {
- eq_properties.clear_groupings();
- }
eq_properties.clear_per_partition_constants();I ran this locally. The only test that changes is the single-partition half of grouping_is_cleared_only_when_partitions_are_merged, which now expects the grouping to be cleared, the same as orderings. With this change aggregate_clears_explicit_grouping no longer exercises the explicit clear in AggregateExec, so I'd add a clustered case too. It passes with the explicit clear and fails without it:
#[test]
fn clustered_aggregate_clears_explicit_grouping() -> Result<()> {
let schema = Arc::new(Schema::new(vec![
Field::new("a", DataType::Int32, true),
Field::new("c", DataType::Int32, true),
Field::new("value", DataType::Int64, true),
]));
let a = col("a", &schema)?;
let c = col("c", &schema)?;
let input = TestMemoryExec::try_new(&[vec![]], Arc::clone(&schema), None)?
.try_with_sort_information(vec![
[PhysicalSortExpr::new_default(Arc::clone(&a))].into(),
])?
.try_with_grouping_information(vec![vec![Arc::clone(&c)]])?;
let aggregate = AggregateExec::try_new(
AggregateMode::Single,
PhysicalGroupBy::new_single(vec![
(a, "group_a".to_string()),
(c, "group_c".to_string()),
]),
vec![Arc::new(
AggregateExprBuilder::new(sum_udaf(), vec![col("value", &schema)?])
.schema(Arc::clone(&schema))
.alias("SUM(value)")
.build()?,
)],
vec![None],
Arc::new(input),
schema,
)?;
assert!(matches!(
aggregate.group_clustering_mode(),
GroupClusteringMode::Partial(_)
));
let properties = aggregate.properties().equivalence_properties();
assert!(!properties.oeq_class().is_empty());
assert!(properties.geq_class().is_empty());
assert!(!properties.grouping_satisfy([col("group_c", &aggregate.schema())?])?);
Ok(())
}If you'd rather keep clear_orderings() as it is, could you note in its doc comment and in add_groupings that operators which reorder rows must also call clear_groupings()? A follow-up PR is fine for either option.
There was a problem hiding this comment.
Thanks @jayzhan211. Assuming we have no existing operators that would change ordering but somehow maintain groupings, the conservative route to clear groupings in clear orderings makes sense to me.
I added this as a follow-up PR here: #26852
2c5ab1f to
59f1596
Compare
|
Hi @jayzhan211, I came across another finding- we may also want to propagate grouping properties through UnionExec. Currently, we construct a new EquivalenceProperties and only add back constants and orderings. However, since UnionExec maintains isolated partitions, grouping properties should also be maintained. On the other hand, InterleaveExec should not propagate groupings since it merges partitions, potentially breaking contiguity. Please let me know if this makes sense, and if I should add this as part of this PR or in another followup 🙇 |
|
@mhilton is this a PR you think would help us at Influx? |
| // explicit grouping assertions do not necessarily survive even when | ||
| // the requested ordering is already satisfied. Groupings implied by | ||
| // the output ordering remain discoverable through `oeq_class`. | ||
| self.clear_groupings(); |
There was a problem hiding this comment.
It might be that the ordering maintains the grouping, if every column in the grouping is included in the ordering, and therefore the groupings could filtered rather than just cleared. I suspect however that this is unnecessary as ordering is the stronger signal and will be checked first.
There was a problem hiding this comment.
Thanks for the review @mhilton. That's the intent here 🙂 grouping implied by the resulting ordering remains discoverable when determining a ClusteringMode, so we don't need to retain the grouping assertions here.
59f1596 to
aaae646
Compare
|
@xavlee Yes, that makes sense. + // Each union output partition is one input partition, so a grouping holds
+ // when every input satisfies it, explicitly or through an ordering.
+ let mut groupings = vec![];
+ for grouping in lhs.geq_class().iter().chain(rhs.geq_class().iter()) {
+ if lhs.grouping_satisfy(grouping.iter().cloned())?
+ && rhs.grouping_satisfy(grouping.iter().cloned())?
+ {
+ groupings.push(grouping.clone());
+ }
+ }
+
let mut eq_properties = EquivalenceProperties::new(lhs.schema);
eq_properties.add_constants(constants)?;
eq_properties.add_orderings(orderings);
+ eq_properties.add_groupings(groupings);Please add tests for these cases: grouped on |
aaae646 to
6f84735
Compare
|
@jayzhan211 I updated the PR with the union change/tests. Please re-review when you get the chance 🙇 |
|
Thanks @xavlee @gene-bordegaray @mhilton 🚀 ! |
Backport the Apache DataFusion apache#24438 stack onto DataDog branch-55: - apache#24737: unsorted contiguous-group regression coverage - apache#24697: group-clustering terminology and aggregate APIs - apache#24698: grouping equivalence properties and propagation - apache#24497: incremental aggregation over explicitly grouped input - apache#26852: invalidate grouping assertions when clearing orderings Adapt the changes to DF55's existing aggregate streams. Single-stage clustered aggregation continues to use GroupedHashAggregateStream; partial and final aggregation retain the existing specialized streams. Do not import the unrelated DF56 aggregate-stream migration. Include the constant-key, grouping-set, and PartialReduce guards. Extend blocked-input tests across single/final and migrated/legacy execution, and retain sort-option and metadata-invalidation coverage. Update DF55 plan snapshots, the wrapped memory-limit error assertion, and the API upgrade guide. Validation: 503 SQL test files passed, 3347 focused expression/plan unit tests passed, and all-targets/all-features clippy passed. Extended tests exposed a dictionary sort-fuzzer failure also reproduced on the unchanged branch-55 base (root seed 1793724857; dataset 15872832093508421016; query 12465732596605393044; config 11717754459111916353). Upstream-Commit: 59d74ca Upstream-Commit: d93699c Upstream-Commit: 320a7d2 Upstream-Commit: 64a5955 Upstream-Commit: 3c3898e Upstream-Commit: 86f93b6 Co-authored-by: xavlee <52435309+xavlee@users.noreply.github.com>
Backport apache#24698 Backport grouping metadata, projection and union propagation, and explicit invalidation at aggregate and partition boundaries. Update the DF55 FFI debug expectation for the additional metadata fields. Upstream-Commit: 320a7d2
|
epic |
Which issue does this PR relate to?
Rationale for this change
Aggregation can recognize completed groups when all rows with the same grouping-key tuple occur in one contiguous run within a partition. Sorted input provides this guarantee, and the runs can also appear in an arbitrary order:
This PR represents that guarantee in
EquivalenceProperties, allowing execution-plan producers to declare grouping tuples and consumers to check whether an input satisfies a grouping requirement.What changes are included in this PR?
Semantics and propagation
A grouping assertion describes a complete expression tuple within every output partition.
[a, b]and[b, a]identify the same groups. The guarantee applies to the complete pair; a guarantee for a subset such as[a]requires its own proof.Grouping assertions are correctness guarantees supplied by producers. DataFusion trusts the asserted row layout. Producers can declare derived tuples such as
[bhandle, date_bin(timestamp)]directly.EquivalenceProperties.UnionExecwhen every input satisfies it, explicitly or through ordering.Public API additions
GroupingEquivalenceClassrepresents complete grouping tuples and supports construction, insertion, lookup, clearing, schema rewriting, iteration, and display.EquivalenceProperties::{geq_class, normalized_geq_class, add_grouping, add_groupings, clear_groupings, grouping_satisfy}expose grouping metadata and its analysis.TestMemoryExec::try_with_grouping_informationattaches grouping assertions in physical-plan tests and examples.Execution-plan producers attach these assertions through the
EquivalencePropertiesheld byPlanProperties. The follow-up #24497 consumes them to deriveGroupClusteringMode::Fullfor eligible aggregates.Stack
What is the testing strategy for this PR?
Tests cover complete-tuple semantics, equivalent permutations, normalization through equivalences and constants, ordering-derived grouping, complete and incomplete projection mappings, explicitly declared derived expressions, operator boundaries, and UNION propagation with selective tuple retention, mixed grouping/ordering proofs, schema rewrites, and input permutations.
aggregate_clears_explicit_groupingcovers partial and single aggregation with reordered and aliased grouping keys. Both cases fail without the aggregate boundary clear and pass with it.Local validation passed the extended workspace suite, including 2,997 physical-plan tests and 546 SQL logic files, and all-target/all-feature Clippy with warnings denied.
Are there any user-facing changes?
Yes. The additive Rust APIs above let plan producers describe contiguous grouping tuples and let consumers evaluate grouping requirements. The streaming aggregate optimization follows in #24497.
Review this layer
View this PR's diff