github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/index/compaction/plan_test.go (about) 1 // Copyright (c) 2018 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package compaction 22 23 import ( 24 "fmt" 25 "sort" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/dbnode/storage/index/segments" 30 31 "github.com/stretchr/testify/require" 32 ) 33 34 func TestDefaultOptsValidate(t *testing.T) { 35 require.NoError(t, DefaultOptions.Validate()) 36 } 37 38 func TestSingleMutableCompaction(t *testing.T) { 39 opts := DefaultOptions 40 candidates := []Segment{ 41 Segment{ 42 Age: (opts.MutableCompactionAgeThreshold + time.Second), 43 Size: 10, 44 Type: segments.MutableType, 45 }, 46 } 47 plan, err := NewPlan(candidates, opts) 48 require.NoError(t, err) 49 requirePlansEqual(t, &Plan{ 50 Tasks: []Task{ 51 Task{ 52 Segments: candidates, 53 }, 54 }, 55 OrderBy: opts.OrderBy, 56 }, plan) 57 } 58 59 func TestReportNewMutableSegmentUnused(t *testing.T) { 60 opts := DefaultOptions 61 var ( 62 s1 = Segment{ 63 Age: (opts.MutableCompactionAgeThreshold - time.Second), 64 Size: 10, 65 Type: segments.MutableType, 66 } 67 s2 = Segment{ 68 Age: (opts.MutableCompactionAgeThreshold + time.Second), 69 Size: 10, 70 Type: segments.MutableType, 71 } 72 ) 73 candidates := []Segment{s1, s2} 74 plan, err := NewPlan(candidates, opts) 75 require.NoError(t, err) 76 requirePlansEqual(t, &Plan{ 77 Tasks: []Task{ 78 Task{Segments: []Segment{s1, s2}}, 79 }, 80 OrderBy: opts.OrderBy, 81 }, plan) 82 } 83 84 func TestMarkUnusedSegmentsSingleTier(t *testing.T) { 85 opts := testOptions() 86 var ( 87 s1 = Segment{ 88 Age: (opts.MutableCompactionAgeThreshold + time.Second), 89 Size: 10, 90 Type: segments.MutableType, 91 } 92 s2 = Segment{ 93 Size: 60, 94 Type: segments.FSTType, 95 } 96 s3 = Segment{ 97 Size: 61, 98 Type: segments.FSTType, 99 } 100 ) 101 candidates := []Segment{s1, s3, s2} 102 plan, err := NewPlan(candidates, opts) 103 require.NoError(t, err) 104 require.Equal(t, &Plan{ 105 UnusedSegments: []Segment{s3}, 106 Tasks: []Task{ 107 Task{Segments: []Segment{s1, s2}}, 108 }, 109 OrderBy: opts.OrderBy, 110 }, plan) 111 } 112 113 func TestDontCompactSegmentTooLarge(t *testing.T) { 114 opts := testOptions() 115 sort.Sort(ByMinSize(opts.Levels)) 116 maxBucketSize := opts.Levels[len(opts.Levels)-1].MaxSizeExclusive 117 var ( 118 s1 = Segment{ 119 Age: (opts.MutableCompactionAgeThreshold + time.Second), 120 Size: maxBucketSize + 1, 121 Type: segments.MutableType, 122 } 123 s2 = Segment{ 124 Size: maxBucketSize + 1, 125 Type: segments.FSTType, 126 } 127 s3 = Segment{ 128 Age: (opts.MutableCompactionAgeThreshold + time.Second), 129 Size: 61, 130 Type: segments.MutableType, 131 } 132 s4 = Segment{ 133 Size: 128, 134 Type: segments.FSTType, 135 } 136 ) 137 candidates := []Segment{s1, s2, s3, s4} 138 plan, err := NewPlan(candidates, opts) 139 require.NoError(t, err) 140 require.Equal(t, &Plan{ 141 UnusedSegments: []Segment{s2, s4}, // s2 is too large to be compacted, s4 is by itself in the second tier 142 Tasks: []Task{ 143 Task{Segments: []Segment{s3}}, // s3 is small enough to be compacted 144 Task{Segments: []Segment{s1}}, // s1 should be compacted regardless of size because its mutable 145 }, 146 OrderBy: opts.OrderBy, 147 }, plan) 148 } 149 150 func TestPlanOrderByMutableAge(t *testing.T) { 151 var ( 152 s1 = Segment{ 153 Age: 10, 154 Size: 1, 155 Type: segments.MutableType, 156 } 157 s2 = Segment{ 158 Age: 10, 159 Size: 10, 160 Type: segments.MutableType, 161 } 162 s3 = Segment{ 163 Age: 100, 164 Size: 10, 165 Type: segments.MutableType, 166 } 167 ) 168 p := &Plan{ 169 Tasks: []Task{ 170 Task{Segments: []Segment{s1, s2}}, 171 Task{Segments: []Segment{s3}}, 172 }, 173 OrderBy: TasksOrderedByOldestMutableAndSize, 174 } 175 sort.Sort(p) 176 requirePlansEqual(t, &Plan{ 177 Tasks: []Task{ 178 Task{Segments: []Segment{s3}}, 179 Task{Segments: []Segment{s1, s2}}, 180 }, 181 OrderBy: TasksOrderedByOldestMutableAndSize, 182 }, p) 183 } 184 func TestPlanOrderByNumMutable(t *testing.T) { 185 var ( 186 s1 = Segment{ 187 Age: 5, 188 Size: 1, 189 Type: segments.MutableType, 190 } 191 s2 = Segment{ 192 Age: 5, 193 Size: 10, 194 Type: segments.MutableType, 195 } 196 s3 = Segment{ 197 Age: 10, 198 Size: 10, 199 Type: segments.MutableType, 200 } 201 ) 202 p := &Plan{ 203 Tasks: []Task{ 204 Task{Segments: []Segment{s3}}, 205 Task{Segments: []Segment{s1, s2}}, 206 }, 207 OrderBy: TasksOrderedByOldestMutableAndSize, 208 } 209 sort.Sort(p) 210 requirePlansEqual(t, &Plan{ 211 Tasks: []Task{ 212 Task{Segments: []Segment{s1, s2}}, 213 Task{Segments: []Segment{s3}}, 214 }, 215 OrderBy: TasksOrderedByOldestMutableAndSize, 216 }, p) 217 } 218 219 func TestPlanOrderByMutableCumulativeSize(t *testing.T) { 220 var ( 221 s1 = Segment{ 222 Age: 10, 223 Size: 10, 224 Type: segments.MutableType, 225 } 226 s2 = Segment{ 227 Age: 10, 228 Size: 2, 229 Type: segments.MutableType, 230 } 231 s3 = Segment{ 232 Age: 15, 233 Size: 10, 234 Type: segments.MutableType, 235 } 236 s4 = Segment{ 237 Age: 5, 238 Size: 6, 239 Type: segments.MutableType, 240 } 241 ) 242 p := &Plan{ 243 Tasks: []Task{ 244 Task{Segments: []Segment{s3, s4}}, 245 Task{Segments: []Segment{s1, s2}}, 246 }, 247 OrderBy: TasksOrderedByOldestMutableAndSize, 248 } 249 sort.Sort(p) 250 requirePlansEqual(t, &Plan{ 251 Tasks: []Task{ 252 Task{Segments: []Segment{s1, s2}}, 253 Task{Segments: []Segment{s3, s4}}, 254 }, 255 OrderBy: TasksOrderedByOldestMutableAndSize, 256 }, p) 257 } 258 259 func requirePlansEqual(t *testing.T, expected, observed *Plan) { 260 if expected == nil { 261 require.Nil(t, observed) 262 return 263 } 264 require.Equal(t, len(expected.UnusedSegments), len(observed.UnusedSegments), 265 fmt.Sprintf("exp [%+v]\nobs[%+v]", expected.UnusedSegments, observed.UnusedSegments)) 266 for i := range expected.UnusedSegments { 267 require.Equal(t, expected.UnusedSegments[i], observed.UnusedSegments[i], i) 268 } 269 require.Equal(t, len(expected.Tasks), len(observed.Tasks), 270 fmt.Sprintf("exp [%+v]\nobs[%+v]", expected.Tasks, observed.Tasks)) 271 for i := range expected.Tasks { 272 require.Equal(t, expected.Tasks[i], observed.Tasks[i]) 273 } 274 require.Equal(t, expected.OrderBy, observed.OrderBy) 275 } 276 277 func testOptions() PlannerOptions { 278 opts := DefaultOptions 279 opts.Levels = []Level{ // i.e. tiers for compaction [0, 64), [64, 524), [524, 4000) 280 Level{ 281 MinSizeInclusive: 0, 282 MaxSizeExclusive: 64, 283 }, 284 Level{ 285 MinSizeInclusive: 64, 286 MaxSizeExclusive: 524, 287 }, 288 Level{ 289 MinSizeInclusive: 524, 290 MaxSizeExclusive: 4000, 291 }, 292 } 293 return opts 294 }