55package manifest
66
77import (
8+ "math/rand"
89 "testing"
910
1011 "github.com/cockroachdb/pebble/internal/base"
1112 "github.com/stretchr/testify/require"
1213)
1314
14- func makeTestLevelMetadata (count int ) (LevelMetadata , []* FileMetadata ) {
15- files := make ([]* FileMetadata , count )
16- for i := 0 ; i < count ; i ++ {
17- files [i ] = newItem (key (i ))
15+ // Creates a version with numFiles files in level 6.
16+ func makeTestVersion (numFiles int ) (* Version , []* FileMetadata ) {
17+ files := make ([]* FileMetadata , numFiles )
18+ for i := 0 ; i < numFiles ; i ++ {
19+ // Each file spans 10 keys, e.g. [0->9], [10->19], etc.
20+ files [i ] = (& FileMetadata {}).ExtendPointKeyBounds (
21+ base .DefaultComparer .Compare , key (i * 10 ), key (i * 10 + 9 ),
22+ )
23+ files [i ].InitPhysicalBacking ()
1824 }
1925
20- lm := MakeLevelMetadata (base .DefaultComparer .Compare , 6 , files )
21- return lm , files
22- }
26+ var levelFiles [7 ][]* FileMetadata
27+ levelFiles [6 ] = files
2328
24- // NumFilesAnnotator is an Annotator which computes an annotation value
25- // equal to the number of files included in the annotation.
26- var NumFilesAnnotator = SumAnnotator (func (f * FileMetadata ) (uint64 , bool ) {
27- return 1 , true
28- })
29+ v := NewVersion (base .DefaultComparer , 0 , levelFiles )
30+ return v , files
31+ }
2932
3033func TestNumFilesAnnotator (t * testing.T ) {
3134 const count = 1000
32- lm , _ := makeTestLevelMetadata (0 )
35+ v , _ := makeTestVersion (0 )
3336
3437 for i := 1 ; i <= count ; i ++ {
35- lm .tree .Insert (newItem (key (i )))
36- numFiles := * NumFilesAnnotator .LevelAnnotation (lm )
38+ v . Levels [ 6 ] .tree .Insert (newItem (key (i )))
39+ numFiles := * NumFilesAnnotator .LevelAnnotation (v . Levels [ 6 ] )
3740 require .EqualValues (t , i , numFiles )
3841 }
39-
40- numFiles := * NumFilesAnnotator .LevelAnnotation (lm )
41- require .EqualValues (t , count , numFiles )
42-
43- numFiles = * NumFilesAnnotator .LevelAnnotation (lm )
44- require .EqualValues (t , count , numFiles )
45-
46- lm .tree .Delete (newItem (key (count / 2 )))
47- numFiles = * NumFilesAnnotator .LevelAnnotation (lm )
48- require .EqualValues (t , count - 1 , numFiles )
4942}
5043
5144func BenchmarkNumFilesAnnotator (b * testing.B ) {
52- lm , _ := makeTestLevelMetadata (0 )
45+ v , _ := makeTestVersion (0 )
5346 for i := 1 ; i <= b .N ; i ++ {
54- lm .tree .Insert (newItem (key (i )))
55- numFiles := * NumFilesAnnotator .LevelAnnotation (lm )
47+ v . Levels [ 6 ] .tree .Insert (newItem (key (i )))
48+ numFiles := * NumFilesAnnotator .LevelAnnotation (v . Levels [ 6 ] )
5649 require .EqualValues (b , uint64 (i ), numFiles )
5750 }
5851}
@@ -70,12 +63,115 @@ func TestPickFileAggregator(t *testing.T) {
7063 },
7164 }
7265
73- lm , files := makeTestLevelMetadata (1 )
66+ v , files := makeTestVersion (1 )
7467
7568 for i := 1 ; i <= count ; i ++ {
76- lm .tree .Insert (newItem (key (i )))
77- pickedFile := a .LevelAnnotation (lm )
69+ v . Levels [ 6 ] .tree .Insert (newItem (key (i )))
70+ pickedFile := a .LevelAnnotation (v . Levels [ 6 ] )
7871 // The picked file should always be the one with the smallest key.
7972 require .Same (t , files [0 ], pickedFile )
8073 }
8174}
75+
76+ func bounds (i int , j int , exclusive bool ) base.UserKeyBounds {
77+ b := base .UserKeyBoundsEndExclusiveIf (key (i ).UserKey , key (j ).UserKey , exclusive )
78+ return b
79+ }
80+
81+ func randomBounds (rng * rand.Rand , count int ) base.UserKeyBounds {
82+ first := rng .Intn (count )
83+ second := rng .Intn (count )
84+ exclusive := rng .Intn (2 ) == 0
85+ return bounds (min (first , second ), max (first , second ), exclusive )
86+ }
87+
88+ func requireMatchOverlaps (t * testing.T , v * Version , bounds base.UserKeyBounds ) {
89+ overlaps := v .Overlaps (6 , bounds )
90+ numFiles := * NumFilesAnnotator .LevelRangeAnnotation (v .Levels [6 ], bounds )
91+ require .EqualValues (t , overlaps .length , numFiles )
92+ }
93+
94+ func TestNumFilesRangeAnnotationEmptyRanges (t * testing.T ) {
95+ const count = 5_000
96+ v , files := makeTestVersion (count )
97+
98+ // Delete files containing key ranges [0, 999] and [24_000, 25_999].
99+ for i := 0 ; i < 100 ; i ++ {
100+ v .Levels [6 ].tree .Delete (files [i ])
101+ }
102+ for i := 2400 ; i < 2600 ; i ++ {
103+ v .Levels [6 ].tree .Delete (files [i ])
104+ }
105+
106+ // Ranges that are completely empty.
107+ requireMatchOverlaps (t , v , bounds (1 , 999 , false ))
108+ requireMatchOverlaps (t , v , bounds (0 , 1000 , true ))
109+ requireMatchOverlaps (t , v , bounds (50_000 , 60_000 , false ))
110+ requireMatchOverlaps (t , v , bounds (24_500 , 25_500 , false ))
111+ requireMatchOverlaps (t , v , bounds (24_000 , 26_000 , true ))
112+
113+ // Partial overlaps with empty ranges.
114+ requireMatchOverlaps (t , v , bounds (0 , 1000 , false ))
115+ requireMatchOverlaps (t , v , bounds (20 , 1001 , true ))
116+ requireMatchOverlaps (t , v , bounds (20 , 1010 , true ))
117+ requireMatchOverlaps (t , v , bounds (23_000 , 27_000 , true ))
118+ requireMatchOverlaps (t , v , bounds (25_000 , 40_000 , false ))
119+ requireMatchOverlaps (t , v , bounds (25_500 , 26_001 , true ))
120+
121+ // Ranges which only spans a single table.
122+ requireMatchOverlaps (t , v , bounds (45_000 , 45_000 , true ))
123+ requireMatchOverlaps (t , v , bounds (30_000 , 30_001 , true ))
124+ requireMatchOverlaps (t , v , bounds (23_000 , 23_000 , false ))
125+ }
126+
127+ func TestNumFilesRangeAnnotationRandomized (t * testing.T ) {
128+ const count = 10_000
129+ const numIterations = 10_000
130+
131+ v , _ := makeTestVersion (count )
132+
133+ rng := rand .New (rand .NewSource (int64 (0 )))
134+ for i := 0 ; i < numIterations ; i ++ {
135+ requireMatchOverlaps (t , v , randomBounds (rng , count * 11 ))
136+ }
137+ }
138+
139+ func BenchmarkNumFilesRangeAnnotation (b * testing.B ) {
140+ const count = 100_000
141+ v , files := makeTestVersion (count )
142+
143+ rng := rand .New (rand .NewSource (int64 (0 )))
144+ b .Run ("annotator" , func (b * testing.B ) {
145+ for i := 0 ; i < b .N ; i ++ {
146+ b := randomBounds (rng , count * 11 )
147+ // Randomly delete and reinsert a file to verify that range
148+ // annotations are still fast despite small mutations.
149+ toDelete := rng .Intn (count )
150+ v .Levels [6 ].tree .Delete (files [toDelete ])
151+
152+ NumFilesAnnotator .LevelRangeAnnotation (v .Levels [6 ], b )
153+
154+ v .Levels [6 ].tree .Insert (files [toDelete ])
155+ }
156+ })
157+
158+ // Also benchmark an equivalent aggregation using version.Overlaps to show
159+ // the difference in performance.
160+ b .Run ("overlaps" , func (b * testing.B ) {
161+ for i := 0 ; i < b .N ; i ++ {
162+ b := randomBounds (rng , count * 11 )
163+ toDelete := rng .Intn (count )
164+ v .Levels [6 ].tree .Delete (files [toDelete ])
165+
166+ overlaps := v .Overlaps (6 , b )
167+ iter := overlaps .Iter ()
168+ numFiles := 0
169+ for f := iter .First (); f != nil ; f = iter .Next () {
170+ numFiles ++
171+ }
172+
173+ v .Levels [6 ].tree .Insert (files [toDelete ])
174+ }
175+ })
176+
177+ }
0 commit comments