@@ -61,13 +61,33 @@ bucket_definitions:
6161 const report = await getReport ( bucketStorage ) ;
6262
6363 expect ( report . totals . bucketCount ) . toEqual ( 1 ) ;
64- expect ( report . truncated ) . toEqual ( false ) ;
64+ expect ( report . bucketsTruncated ) . toEqual ( false ) ;
65+ expect ( report . definitionsTruncated ) . toEqual ( false ) ;
6566
6667 const stats = report . buckets . find ( ( b ) => b . bucket === bucket ) ! ;
6768 // Three inserts of distinct ids: three operations, three live rows, fully compacted (ratio 1).
68- expect ( stats ) . toMatchObject ( { operations : 3 , rows : 3 , fragmentation : 1 , rowsEstimated : false } ) ;
69+ expect ( stats ) . toMatchObject ( {
70+ operations : 3 ,
71+ rows : 3 ,
72+ fragmentation : 1 ,
73+ rowsEstimated : false ,
74+ suggestedAction : 'none' ,
75+ tables : [ 'test' ]
76+ } ) ;
6977 expect ( stats . operationBytes ) . toBeGreaterThan ( 0 ) ;
7078 expect ( report . totals ) . toMatchObject ( { operations : 3 , estimated : false } ) ;
79+
80+ // The definition rollup aggregates the single bucket. The definition name is the bucket-name prefix.
81+ expect ( report . definitions ) . toHaveLength ( 1 ) ;
82+ expect ( report . definitions [ 0 ] ) . toMatchObject ( {
83+ definition : bucket . split ( '[' ) [ 0 ] ,
84+ bucketCount : 1 ,
85+ operations : 3 ,
86+ rows : 3 ,
87+ fragmentation : 1 ,
88+ suggestedAction : 'none' ,
89+ tables : [ 'test' ]
90+ } ) ;
7191 } ) ;
7292
7393 test ( 'operations exceed live rows after updates, and compaction reduces fragmentation' , async ( ) => {
@@ -162,6 +182,15 @@ bucket_definitions:
162182 expect ( report . buckets . find ( ( b ) => b . bucket === b1 ) ) . toMatchObject ( { operations : 3 , rows : 1 } ) ;
163183 expect ( report . buckets . find ( ( b ) => b . bucket === b2 ) ) . toMatchObject ( { operations : 2 , rows : 2 } ) ;
164184
185+ // Both buckets belong to one definition; the rollup sums them, counting each bucket's rows separately.
186+ expect ( report . definitions ) . toHaveLength ( 1 ) ;
187+ expect ( report . definitions [ 0 ] ) . toMatchObject ( {
188+ definition : b1 . split ( '[' ) [ 0 ] ,
189+ bucketCount : 2 ,
190+ operations : 5 ,
191+ rows : 3
192+ } ) ;
193+
165194 // operationBytes is an aggregated ($toDouble) sum; assert every bucket is non-zero and that the
166195 // per-bucket bytes add up to the instance total.
167196 expect ( report . totals . operationBytes ) . toBeGreaterThan ( 0 ) ;
@@ -204,13 +233,46 @@ bucket_definitions:
204233 const b1 = test_utils . bucketRequest ( content , 'grouped["b1"]' ) . bucket ;
205234
206235 const report = await getReport ( bucketStorage , { limit : 1 } ) ;
207- expect ( report . truncated ) . toEqual ( true ) ;
236+ expect ( report . bucketsTruncated ) . toEqual ( true ) ;
208237 expect ( report . buckets . map ( ( b ) => b . bucket ) ) . toEqual ( [ b1 ] ) ;
209238 // Totals still cover every bucket, not just the truncated list.
210239 expect ( report . totals . bucketCount ) . toEqual ( 2 ) ;
211240 expect ( report . totals ) . toMatchObject ( { operations : 3 , estimated : false } ) ;
212241 } ) ;
213242
243+ test ( 'caps the definition rollup and flags the truncation' , async ( ) => {
244+ // Two definitions past the rollup cap; a single row lands in every definition's global bucket.
245+ const definitionCount = storage . BUCKET_REPORT_DEFINITION_LIMIT + 2 ;
246+ const manyDefinitions =
247+ 'bucket_definitions:\n' +
248+ Array . from ( { length : definitionCount } , ( _ , i ) => ` def${ i } :\n data: [select * from test]\n` ) . join ( '' ) ;
249+
250+ await using factory = await generateStorageFactory ( ) ;
251+ const { stream } = await test_utils . deploySyncRules (
252+ factory ,
253+ updateSyncRulesFromYaml ( manyDefinitions , { storageVersion } )
254+ ) ;
255+ const bucketStorage = factory . getInstance ( stream ) ;
256+
257+ await using writer = await bucketStorage . createWriter ( test_utils . BATCH_OPTIONS ) ;
258+ const testTable = await test_utils . resolveTestTable ( writer , 'test' , [ 'id' ] , config ) ;
259+ await writer . markAllSnapshotDone ( '1/1' ) ;
260+ await writer . save ( {
261+ sourceTable : testTable ,
262+ tag : storage . SaveOperationTag . INSERT ,
263+ after : { id : 't1' } ,
264+ afterReplicaId : test_utils . rid ( 't1' )
265+ } ) ;
266+ await writer . commit ( '1/1' ) ;
267+ await writer . flush ( ) ;
268+
269+ const report = await getReport ( bucketStorage ) ;
270+ expect ( report . totals . bucketCount ) . toEqual ( definitionCount ) ;
271+ expect ( report . bucketsTruncated ) . toEqual ( false ) ;
272+ expect ( report . definitions ) . toHaveLength ( storage . BUCKET_REPORT_DEFINITION_LIMIT ) ;
273+ expect ( report . definitionsTruncated ) . toEqual ( true ) ;
274+ } ) ;
275+
214276 test ( 'samples the row count for a bucket above the sampling threshold' , async ( ) => {
215277 await using factory = await generateStorageFactory ( ) ;
216278 const { stream, content } = await test_utils . deploySyncRules (
@@ -265,5 +327,21 @@ bucket_definitions:
265327 expect ( stats . rows ) . toBeLessThanOrEqual ( 55 ) ;
266328 // Fragmentation is operations / rows, so a heavily updated bucket reads well above 1.
267329 expect ( stats . fragmentation ) . toBeGreaterThan ( 10 ) ;
330+ // The history is un-compacted superseded churn, which a compact reclaims.
331+ expect ( stats . suggestedAction ) . toEqual ( 'compact' ) ;
332+ // The sampled history names the tables a defragment would touch.
333+ expect ( stats . tables ) . toEqual ( [ 'test' ] ) ;
334+
335+ // The definition rollup samples the same history at definition grain.
336+ expect ( report . definitions ) . toHaveLength ( 1 ) ;
337+ const defStats = report . definitions [ 0 ] ;
338+ expect ( defStats ) . toMatchObject ( {
339+ bucketCount : 1 ,
340+ operations : stats . operations ,
341+ suggestedAction : 'compact' ,
342+ tables : [ 'test' ]
343+ } ) ;
344+ expect ( defStats . rows ) . toBeGreaterThanOrEqual ( 45 ) ;
345+ expect ( defStats . rows ) . toBeLessThanOrEqual ( 55 ) ;
268346 } ) ;
269347}
0 commit comments