Skip to content

fix: release the clustered partial aggregate table on memory pressure under full group clustering - #26726

Open
viirya wants to merge 4 commits into
apache:mainfrom
viirya:verify-clustered-partial-oom
Open

viirya wants to merge 4 commits into
apache:mainfrom
viirya:verify-clustered-partial-oom

Conversation

@viirya

@viirya viirya commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Which issue does this PR close?

Rationale for this change

#26576 made a fully sorted partial aggregate emit its completed groups when a reservation is refused. The table that remains holds one group but keeps the capacity it grew for the batch, so re-reserving it can still fail with ResourcesExhausted. With 8192 distinct Int64 keys per batch, the table still needs 344 KB, while a table holding only the active group needs 5.6 KB. The issue has the details.

What changes are included in this PR?

  • On a refused reservation, ClusteredPartialAggregateStream now calls take_state_batch under full group clustering too, as it already does under partial group clustering. It emits every state, including the active group, and resets the table, which releases the capacity. The final stage merges the active group's two state rows, as it merges states of the same key from different partitions or from early emission in GroupedHashAggregateStream. A side effect: when the only group in the table is the active one, the stream now emits it instead of returning the error.
  • Updated doc comments.

What is the testing strategy for this PR?

  • clustered_partial_stream::tests::memory_pressure_releases_retained_capacity (new): one and two Int64 keys, 8192 distinct keys per batch, 100 KiB GreedyMemoryPool. It fails on main with ResourcesExhausted.

  • clustered_partial_stream::tests::completed_groups_under_memory_pressure (updated): key 256 spans both batches, so its state is now split into two rows. The test checks for the extra row and compares the states merged by key with the unlimited run.

  • memory_limit::clustered_partial_oom::sorted_partial_aggregate_completes_under_memory_pressure (new, core_integration): runs a sorted GROUP BY from SQL with 4 partitions. It asserts that the plan uses a Partial aggregate with group_clustering_mode=Full, and compares the result with an unlimited run. The pool limits only ClusteredPartialAggregateStream reservations. Under a shared limit, the non-spilling merge and final aggregate need about the same memory and fail at the same limits, so a shared limit cannot single out this stream. It fails on main with ResourcesExhausted.

  • aggregate_memory_call_sequence::full_clustering_refusal_is_an_error (updated snapshot): with a limit too small for even one group, the partial stream now emits that group's state and finishes instead of returning the refusal. The final and single cases are unchanged.

Are there any user-facing changes?

No API changes. Under memory pressure, a fully sorted partial aggregate can now emit two state rows for the active group, which the final stage merges.

This pull request and its description were written by Isaac.

viirya and others added 3 commits October 24, 2026 11:49
…acity

After the completed groups are emitted under memory pressure with full
group clustering, only one group remains, but the table keeps the
capacity it grew for the whole batch (the single-key hash table, the
clustered new_groups scratch and group_indices). Re-reserving that
retained size fails in a 100 KiB pool for 8192 distinct keys per batch.

This test fails on purpose; it demonstrates the issue and is not a fix.

Co-authored-by: Isaac <no-reply@databricks.com>
Run a sorted GROUP BY end to end with a pool that limits only the
clustered partial aggregate streams, since the downstream merge and
final aggregate cannot spill and a shared limit cannot single out the
partial stream. With 8192 distinct keys per batch the retained table
needs ~344 KB (one key) or ~131 KB (two keys) after the completed groups
are emitted, and a 100 KiB limit fails.

This test fails on purpose; it demonstrates the issue and is not a fix.

Co-authored-by: Isaac <no-reply@databricks.com>
… under full group clustering

On a refused reservation under full group clustering, the stream emitted
only the completed groups. The remaining table holds one group but keeps
the capacity it grew for the batch (the single-key hash table, the
clustered new_groups scratch and group_indices), so re-reserving it could
still fail with ResourcesExhausted.

Use take_state_batch as partial group clustering already does: emit every
state, including the active group, and reset the table. The final stage
merges the split active group, as it merges states of the same key from
different partitions.

Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions github-actions Bot added core Core DataFusion crate physical-plan Changes to the physical-plan crate labels Oct 24, 2026
@viirya
viirya requested a review from comphead October 24, 2026 22:34
Under full group clustering, the partial stream now emits the state of
the one incomplete group on a refusal and finishes, instead of returning
the refusal.

Co-authored-by: Isaac <no-reply@databricks.com>
@viirya
viirya requested a review from sunchao October 25, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate physical-plan Changes to the physical-plan crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fully sorted partial aggregate still fails with ResourcesExhausted after emitting completed groups, because the table keeps its capacity

1 participant