1515// specific language governing permissions and limitations
1616// under the License.
1717
18- use std:: fmt:: { self , Display } ;
19- use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
2018use std:: { cmp, sync:: Arc } ;
2119
2220use datafusion:: {
@@ -26,9 +24,7 @@ use datafusion::{
2624use datafusion_catalog:: TableProvider ;
2725use datafusion_common:: ScalarValue ;
2826use datafusion_common:: { error:: Result , utils:: get_available_parallelism} ;
29- use datafusion_execution:: memory_pool:: {
30- FairSpillPool , MemoryConsumer , MemoryPool , MemoryReservation , UnboundedMemoryPool ,
31- } ;
27+ use datafusion_execution:: memory_pool:: { FairSpillPool , MemoryPool } ;
3228use datafusion_execution:: runtime_env:: RuntimeEnvBuilder ;
3329use datafusion_expr:: col;
3430use rand:: { Rng , rng} ;
@@ -45,68 +41,6 @@ const SPILL_POOL_PEAK_FRACTIONS: [(usize, usize); 4] = [(2, 1), (1, 2), (2, 5),
4541/// small enough to fit under the pool.
4642const SPILL_BATCH_SIZE_CAP : usize = 256 ;
4743
48- /// Unbounded pool that records the peak total reservation.
49- ///
50- /// The baseline runs under it to measure the aggregate's real footprint, which
51- /// sizes the spill pools (see `generate`). Unlike `TrackConsumersPool`, the peak
52- /// survives consumer deregistration, so it is readable after the query.
53- #[ derive( Debug , Default ) ]
54- pub ( crate ) struct PeakRecordingPool {
55- inner : UnboundedMemoryPool ,
56- peak : AtomicUsize ,
57- }
58-
59- impl PeakRecordingPool {
60- /// Peak total reservation seen so far, in bytes.
61- pub ( crate ) fn peak ( & self ) -> usize {
62- self . peak . load ( Ordering :: Relaxed )
63- }
64-
65- fn record_peak ( & self ) {
66- self . peak
67- . fetch_max ( self . inner . reserved ( ) , Ordering :: Relaxed ) ;
68- }
69- }
70-
71- impl Display for PeakRecordingPool {
72- fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
73- write ! ( f, "PeakRecordingPool" )
74- }
75- }
76-
77- impl MemoryPool for PeakRecordingPool {
78- fn name ( & self ) -> & str {
79- "PeakRecordingPool"
80- }
81-
82- fn register ( & self , consumer : & MemoryConsumer ) {
83- self . inner . register ( consumer) ;
84- }
85-
86- fn unregister ( & self , consumer : & MemoryConsumer ) {
87- self . inner . unregister ( consumer) ;
88- }
89-
90- fn grow ( & self , reservation : & MemoryReservation , additional : usize ) {
91- self . inner . grow ( reservation, additional) ;
92- self . record_peak ( ) ;
93- }
94-
95- fn shrink ( & self , reservation : & MemoryReservation , shrink : usize ) {
96- self . inner . shrink ( reservation, shrink) ;
97- }
98-
99- fn try_grow ( & self , reservation : & MemoryReservation , additional : usize ) -> Result < ( ) > {
100- self . inner . try_grow ( reservation, additional) ?;
101- self . record_peak ( ) ;
102- Ok ( ( ) )
103- }
104-
105- fn reserved ( & self ) -> usize {
106- self . inner . reserved ( )
107- }
108- }
109-
11044/// SessionContext generator
11145///
11246/// During testing, `generate_baseline` will be called firstly to generate a standard [`SessionContext`],
@@ -165,11 +99,10 @@ impl SessionContextGenerator {
16599impl SessionContextGenerator {
166100 /// Generate the `SessionContext` for the baseline run.
167101 ///
168- /// Runs under a `PeakRecordingPool` so the aggregate's peak memory can be
169- /// read after the query (see `generate`, which sizes the spill pools from it).
170- pub fn generate_baseline (
171- & self ,
172- ) -> Result < ( SessionContextWithParams , Arc < PeakRecordingPool > ) > {
102+ /// Runs under the default unbounded pool, so it never spills (it is the
103+ /// oracle). The caller reads the aggregate's peak from the plan metrics of
104+ /// this run (see `run_sql_capturing_peak`) to size the spilling pools.
105+ pub fn generate_baseline ( & self ) -> Result < SessionContextWithParams > {
173106 let schema = self . dataset . batches [ 0 ] . schema ( ) ;
174107 let batches = self . dataset . batches . clone ( ) ;
175108 let provider = MemTable :: try_new ( schema, vec ! [ batches] ) ?;
@@ -181,23 +114,19 @@ impl SessionContextGenerator {
181114 let skip_partial_params = SkipPartialParams :: ensure_not_trigger ( ) ;
182115 let enable_migration_aggregate = false ;
183116
184- // Records peak usage; unbounded, so the baseline (the oracle) never spills.
185- let tracker = Arc :: new ( PeakRecordingPool :: default ( ) ) ;
186-
187117 let builder = GeneratedSessionContextBuilder {
188118 batch_size,
189119 target_partitions,
190120 skip_partial_params,
191121 enable_migration_aggregate,
192122 sort_hint : false ,
193- memory_pool : Some ( Arc :: clone ( & tracker) as Arc < dyn MemoryPool > ) ,
194- // Baseline: never spill.
123+ memory_pool : None ,
195124 memory_limit : None ,
196125 table_name : self . table_name . clone ( ) ,
197126 table_provider : Arc :: new ( provider) ,
198127 } ;
199128
200- Ok ( ( builder. build ( ) ? , tracker ) )
129+ builder. build ( )
201130 }
202131
203132 /// Randomly generate a session context.
@@ -229,6 +158,9 @@ impl SessionContextGenerator {
229158 rng. random_range ( 1 ..=self . max_batch_size )
230159 } ;
231160
161+ // Single partition when spilling. `FairSpillPool` splits across the per-partition aggregate consumers,
162+ // so with many partitions each share is too small and hits `ResourcesExhausted` before it can spill. Multi
163+ // partition stays covered by the unbounded rounds.
232164 // Single partition when spilling. `FairSpillPool` splits across the per-partition aggregate consumers,
233165 // so with many partitions each share is too small and hits `ResourcesExhausted` before it can spill. Multi
234166 // partition stays covered by the unbounded rounds.
@@ -417,7 +349,9 @@ mod test {
417349 use arrow:: util:: pretty:: pretty_format_batches;
418350 use datafusion_common:: DataFusionError ;
419351
420- use crate :: fuzz_cases:: aggregation_fuzzer:: check_equality_of_batches;
352+ use crate :: fuzz_cases:: aggregation_fuzzer:: {
353+ check_equality_of_batches, run_sql_capturing_peak,
354+ } ;
421355
422356 use super :: * ;
423357
@@ -473,20 +407,14 @@ mod test {
473407 let ctx_generator = SessionContextGenerator :: new ( Arc :: new ( dataset) , "fuzz_table" ) ;
474408
475409 let query = "select b, count(a) from fuzz_table group by b" ;
476- let ( baseline_wrapped_ctx, tracker ) = ctx_generator. generate_baseline ( ) . unwrap ( ) ;
410+ let baseline_wrapped_ctx = ctx_generator. generate_baseline ( ) . unwrap ( ) ;
477411
478- // Run the baseline first to record the peak, then size the spilling
412+ // Run the baseline first to capture the peak, then size the spilling
479413 // contexts from it.
480- let base_result = baseline_wrapped_ctx
481- . ctx
482- . sql ( query)
483- . await
484- . unwrap ( )
485- . collect ( )
486- . await
487- . unwrap ( ) ;
488-
489- let agg_peak = tracker. peak ( ) ;
414+ let ( base_result, agg_peak) =
415+ run_sql_capturing_peak ( query, & baseline_wrapped_ctx. ctx )
416+ . await
417+ . unwrap ( ) ;
490418
491419 let mut random_wrapped_ctxs = Vec :: with_capacity ( 8 ) ;
492420 for _ in 0 ..8 {
@@ -536,17 +464,10 @@ mod test {
536464 let ctx_generator = SessionContextGenerator :: new ( Arc :: new ( dataset) , "fuzz_table" ) ;
537465 let query = "select k, count(*) from fuzz_table group by k" ;
538466
539- // Run the baseline to record the peak that sizes the spill pools.
540- let ( baseline, tracker) = ctx_generator. generate_baseline ( ) . unwrap ( ) ;
541- baseline
542- . ctx
543- . sql ( query)
544- . await
545- . unwrap ( )
546- . collect ( )
547- . await
548- . unwrap ( ) ;
549- let agg_peak = tracker. peak ( ) ;
467+ // Run the baseline to capture the peak that sizes the spill pools.
468+ let baseline = ctx_generator. generate_baseline ( ) . unwrap ( ) ;
469+ let ( _baseline_result, agg_peak) =
470+ run_sql_capturing_peak ( query, & baseline. ctx ) . await . unwrap ( ) ;
550471 assert ! ( agg_peak > 0 , "baseline should have reserved memory" ) ;
551472
552473 // Generate contexts until a bounded one actually spills. Spilling is a
0 commit comments