@@ -246,6 +246,9 @@ fn starts_buckets(groups: usize, rows: usize, threshold: usize) -> bool {
246246 && groups as f64 >= BARELY_REDUCING * rows as f64 )
247247}
248248
249+ /// Additional Partial flush trigger in allocated bytes, checked after a batch.
250+ const PARTIAL_FLUSH_BYTES : usize = 2 * 1024 * 1024 ;
251+
249252/// Number of flushed groups whose hashes are kept to detect recurring groups.
250253const FLUSH_SAMPLE_SIZE : usize = 1024 ;
251254
@@ -296,8 +299,9 @@ struct PartialTableFlush {
296299}
297300
298301impl PartialTableFlush {
299- fn should_flush ( & self , num_groups : usize ) -> bool {
300- !self . disabled && num_groups >= self . threshold
302+ fn should_flush ( & self , num_groups : usize , allocated_bytes : usize ) -> bool {
303+ !self . disabled
304+ && ( num_groups >= self . threshold || allocated_bytes >= PARTIAL_FLUSH_BYTES )
301305 }
302306
303307 /// Records the flush of `state`, the emitted groups and their states.
@@ -602,7 +606,8 @@ impl PartialHashAggregateStream {
602606 // -------------------------------------------------
603607 // Step 4: Larger-than-memory execution (early emit)
604608 // -------------------------------------------------
605- let resize_result = self . reservation . try_resize ( hash_table. memory_size ( ) ) ;
609+ let allocated_bytes = hash_table. memory_size ( ) ;
610+ let resize_result = self . reservation . try_resize ( allocated_bytes) ;
606611 match resize_result {
607612 Ok ( ( ) ) => { }
608613 Err ( DataFusionError :: ResourcesExhausted ( _) ) => {
@@ -616,7 +621,7 @@ impl PartialHashAggregateStream {
616621 // `PartialTableFlush`)
617622 // -----------------------------------------------------------
618623 let table_full = self . table_flush . as_ref ( ) . is_some_and ( |table_flush| {
619- table_flush. should_flush ( hash_table. building_group_count ( ) )
624+ table_flush. should_flush ( hash_table. building_group_count ( ) , allocated_bytes )
620625 } ) ;
621626 if table_full {
622627 return Ok ( HandleInputResult :: TableFull ) ;
@@ -1710,6 +1715,20 @@ mod tests {
17101715 Ok ( ( ) )
17111716 }
17121717
1718+ #[ tokio:: test]
1719+ async fn partial_hash_aggregate_flushes_at_byte_threshold ( ) -> Result < ( ) > {
1720+ // Stay below the group threshold so only the byte threshold can flush.
1721+ let keys: Vec < i32 > = ( 0 ..300_000 ) . map ( |row| row / 3 ) . collect ( ) ;
1722+ let ( expected, _, _) = run_partial_hash_aggregate ( keys. clone ( ) , 0 ) . await ?;
1723+ let ( counts, _, flushes) = run_partial_hash_aggregate ( keys, 262_144 ) . await ?;
1724+
1725+ assert_eq ! ( counts, expected) ;
1726+ assert_eq ! ( counts. len( ) , 100_000 ) ;
1727+ assert ! ( counts. values( ) . all( |& count| count == 3 ) ) ;
1728+ assert ! ( flushes > 0 , "the byte threshold triggered a flush" ) ;
1729+ Ok ( ( ) )
1730+ }
1731+
17131732 #[ tokio:: test]
17141733 async fn partial_hash_aggregate_stops_flushing_groups_that_recur ( ) -> Result < ( ) > {
17151734 // The same 30000 keys come around five times: every flushed group
0 commit comments