github.com/grafana/pyroscope@v1.18.0/pkg/metastore/compaction/compactor/compaction_queue_test.go (about) 1 package compactor 2 3 import ( 4 "fmt" 5 "math/rand" 6 "strconv" 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 "github.com/grafana/pyroscope/pkg/metastore/compaction" 13 ) 14 15 func testBlockEntry(id int) blockEntry { return blockEntry{id: strconv.Itoa(id)} } 16 17 func testBlockQueue(cfg Config) *blockQueue { 18 stats := newGlobalQueueStats(len(cfg.Levels)) 19 return newBlockQueue(cfg, nil, stats) 20 } 21 22 func TestBlockQueue_Push(t *testing.T) { 23 q := testBlockQueue(Config{Levels: []LevelConfig{{MaxBlocks: 3}}}) 24 key := compactionKey{tenant: "t", shard: 1} 25 26 result := q.stagedBlocks(key).push(testBlockEntry(1)) 27 require.True(t, result) 28 require.Equal(t, 1, len(q.staged[key].batch.blocks)) 29 assert.Equal(t, testBlockEntry(1), q.staged[key].batch.blocks[0]) 30 31 q.stagedBlocks(key).push(testBlockEntry(2)) 32 q.stagedBlocks(key).push(testBlockEntry(3)) // Staged blocks formed the first batch. 33 assert.Equal(t, 0, len(q.staged[key].batch.blocks)) 34 assert.Equal(t, []blockEntry{testBlockEntry(1), testBlockEntry(2), testBlockEntry(3)}, q.head.blocks) 35 36 q.stagedBlocks(key).push(testBlockEntry(4)) 37 q.stagedBlocks(key).push(testBlockEntry(5)) 38 assert.Equal(t, 2, len(q.staged[key].batch.blocks)) 39 40 remove(q, key, "1", "2") // Remove the first batch. 41 assert.Equal(t, []blockEntry{zeroBlockEntry, zeroBlockEntry, testBlockEntry(3)}, q.head.blocks) 42 remove(q, key, "3") 43 assert.Nil(t, q.head) 44 45 q.stagedBlocks(key).push(testBlockEntry(6)) // Complete the second batch. 46 assert.Equal(t, 0, len(q.staged[key].batch.blocks)) 47 48 q.stagedBlocks(key).push(testBlockEntry(7)) 49 assert.Equal(t, []blockEntry{testBlockEntry(4), testBlockEntry(5), testBlockEntry(6)}, q.head.blocks) 50 assert.Equal(t, 1, len(q.staged[key].batch.blocks)) 51 } 52 53 func TestBlockQueue_DuplicateBlock(t *testing.T) { 54 q := testBlockQueue(Config{Levels: []LevelConfig{{MaxBlocks: 3}}}) 55 key := compactionKey{tenant: "t", shard: 1} 56 57 require.True(t, q.stagedBlocks(key).push(testBlockEntry(1))) 58 require.False(t, q.stagedBlocks(key).push(testBlockEntry(1))) 59 60 assert.Equal(t, 1, len(q.staged[key].batch.blocks)) 61 } 62 63 func TestBlockQueue_Remove(t *testing.T) { 64 q := testBlockQueue(Config{Levels: []LevelConfig{{MaxBlocks: 3}}}) 65 key := compactionKey{tenant: "t", shard: 1} 66 q.stagedBlocks(key).push(testBlockEntry(1)) 67 q.stagedBlocks(key).push(testBlockEntry(2)) 68 69 remove(q, key, "1") 70 require.Empty(t, q.staged[key].batch.blocks[0]) 71 72 _, exists := q.staged[key].refs["1"] 73 assert.False(t, exists) 74 75 remove(q, key, "2") 76 require.Nil(t, q.head) 77 require.Nil(t, q.tail) 78 } 79 80 func TestBlockQueue_RemoveNotFound(t *testing.T) { 81 q := testBlockQueue(Config{Levels: []LevelConfig{{MaxBlocks: 3}}}) 82 key := compactionKey{tenant: "t", shard: 1} 83 remove(q, key, "1") 84 q.stagedBlocks(key).push(testBlockEntry(1)) 85 remove(q, key, "2") 86 q.stagedBlocks(key).push(testBlockEntry(2)) 87 q.stagedBlocks(key).push(testBlockEntry(3)) 88 89 assert.Equal(t, []blockEntry{testBlockEntry(1), testBlockEntry(2), testBlockEntry(3)}, q.head.blocks) 90 } 91 92 func TestBlockQueue_Linking(t *testing.T) { 93 q := testBlockQueue(Config{Levels: []LevelConfig{{MaxBlocks: 2}}}) 94 key := compactionKey{tenant: "t", shard: 1} 95 96 q.stagedBlocks(key).push(testBlockEntry(1)) 97 q.stagedBlocks(key).push(testBlockEntry(2)) 98 head, tail := q.head, q.tail 99 require.NotNil(t, head) 100 assert.Equal(t, head, tail) 101 102 q.stagedBlocks(key).push(testBlockEntry(3)) 103 assert.NotNil(t, q.tail) 104 assert.Equal(t, head, q.head) 105 assert.Equal(t, tail, q.tail) 106 assert.Equal(t, []blockEntry{testBlockEntry(1), testBlockEntry(2)}, q.head.blocks) 107 assert.Equal(t, q.tail.blocks, q.head.blocks) 108 109 q.stagedBlocks(key).push(testBlockEntry(4)) 110 assert.NotNil(t, q.tail.prevG) 111 assert.NotNil(t, q.head.nextG) 112 113 q.stagedBlocks(key).push(testBlockEntry(5)) 114 q.stagedBlocks(key).push(testBlockEntry(6)) 115 assert.NotNil(t, q.tail.prevG.prevG) 116 assert.NotNil(t, q.head.nextG.nextG) 117 118 t.Run("iterator does not affect the queue", func(t *testing.T) { 119 expected := []string{"1", "2", "3", "4", "5", "6"} 120 for i := 0; i < 3; i++ { 121 collected := make([]string, 0, len(expected)) 122 iter := newBlockIter() 123 iter.setBatch(q.head) 124 for { 125 b, ok := iter.peek() 126 if !ok { 127 assert.Equal(t, expected, collected) 128 break 129 } 130 collected = append(collected, b) 131 iter.advance() 132 } 133 } 134 }) 135 136 t.Run("remove staging batch from the queue", func(t *testing.T) { 137 q.stagedBlocks(key).push(testBlockEntry(7)) 138 // Block 7 is still staged: test that it can be 139 // removed without affecting the queue. 140 head, tail = q.head, q.tail 141 remove(q, key, "7") 142 assert.Equal(t, head, q.head) 143 assert.Equal(t, tail, q.tail) 144 }) 145 146 t.Run("empty queue", func(t *testing.T) { 147 remove(q, key, "3", "2") 148 remove(q, key, "4", "1") 149 remove(q, key, "6") 150 remove(q, key, "5") 151 assert.Nil(t, q.head) 152 assert.Nil(t, q.tail) 153 }) 154 } 155 156 func TestBlockQueue_EmptyQueue(t *testing.T) { 157 const ( 158 numKeys = 50 159 numBlocksPerKey = 100 160 ) 161 162 q := testBlockQueue(Config{Levels: []LevelConfig{{MaxBlocks: 3}}}) 163 keys := make([]compactionKey, numKeys) 164 for i := 0; i < numKeys; i++ { 165 keys[i] = compactionKey{ 166 tenant: fmt.Sprint(i), 167 shard: uint32(i), 168 } 169 } 170 171 blocks := make(map[compactionKey][]string) 172 for _, key := range keys { 173 for j := 0; j < numBlocksPerKey; j++ { 174 block := testBlockEntry(j) 175 require.True(t, q.stagedBlocks(key).push(block)) 176 blocks[key] = append(blocks[key], block.id) 177 } 178 } 179 180 for key, s := range blocks { 181 rand.Shuffle(len(s), func(i, j int) { 182 s[i], s[j] = s[j], s[i] 183 }) 184 for _, b := range s { 185 staged, ok := q.staged[key] 186 if !ok { 187 return 188 } 189 assert.NotEmpty(t, staged.delete(b)) 190 } 191 } 192 193 for key := range blocks { 194 require.Nil(t, q.staged[key]) 195 } 196 197 assert.Nil(t, q.head) 198 assert.Nil(t, q.tail) 199 } 200 201 func TestBlockQueue_FlushByAge(t *testing.T) { 202 s := Config{ 203 Levels: []LevelConfig{ 204 {MaxBlocks: 3, MaxAge: 1}, 205 {MaxBlocks: 5, MaxAge: 1}, 206 }, 207 } 208 209 c := newCompactionQueue(s, nil) 210 blocks := []compaction.BlockEntry{ 211 {Tenant: "A", Shard: 1, Level: 1, Index: 1, AppendedAt: 5, ID: "1"}, 212 {Tenant: "A", Shard: 1, Level: 1, Index: 2, AppendedAt: 15, ID: "2"}, 213 {Tenant: "A", Shard: 0, Level: 1, Index: 3, AppendedAt: 30, ID: "3"}, 214 {Tenant: "A", Shard: 0, Level: 1, Index: 4, AppendedAt: 35, ID: "4"}, 215 {Tenant: "B", Shard: 0, Level: 1, Index: 5, AppendedAt: 40, ID: "5"}, 216 } 217 for _, e := range blocks { 218 c.push(e) 219 } 220 221 batches := make([]blockEntry, 0, len(blocks)) 222 q := c.blockQueue(1) 223 iter := newBatchIter(q) 224 for { 225 b, ok := iter.next() 226 if !ok { 227 break 228 } 229 batches = append(batches, b.blocks...) 230 } 231 232 expected := []blockEntry{{"1", 1}, {"2", 2}, {"3", 3}, {"4", 4}} 233 // "5" remains staged as we need another push to evict it. 234 assert.Equal(t, expected, batches) 235 236 // We have 3 compaction queues (staging batches): 237 // A/1/1, A/0/1, B/0/1. 238 assert.Equal(t, 3, q.updates.Len()) 239 240 staged := q.stagedBlocks(compactionKey{tenant: "B", shard: 0, level: 1}) 241 staged.delete("5") 242 // B/0/1 compaction queue is removed. 243 assert.Equal(t, 2, q.updates.Len()) 244 245 staged = q.stagedBlocks(compactionKey{tenant: "A", shard: 0, level: 1}) 246 staged.delete("4") 247 // A/0/1 is still here. 248 assert.Equal(t, 2, q.updates.Len()) 249 staged.delete("3") 250 // A/0/1 compaction queue is removed. 251 assert.Equal(t, 1, q.updates.Len()) 252 253 // A/1/1 compaction queue is removed. 254 staged = q.stagedBlocks(compactionKey{tenant: "A", shard: 1, level: 1}) 255 assert.NotEmpty(t, staged.delete("1")) 256 assert.Equal(t, 1, q.updates.Len()) 257 assert.NotEmpty(t, staged.delete("2")) 258 assert.Equal(t, 0, staged.queue.updates.Len()) 259 } 260 261 func TestBlockQueue_BatchIterator(t *testing.T) { 262 q := testBlockQueue(Config{Levels: []LevelConfig{{MaxBlocks: 3}}}) 263 keys := []compactionKey{ 264 {tenant: "t-1", shard: 1}, 265 {tenant: "t-2", shard: 2}, 266 } 267 268 for j := 0; j < 20; j++ { 269 q.stagedBlocks(keys[j%len(keys)]).push(testBlockEntry(j)) 270 } 271 272 iter := newBatchIter(q) 273 for _, expected := range []struct { 274 key compactionKey 275 blocks []string 276 }{ 277 {key: keys[0], blocks: []string{"0", "2", "4"}}, 278 {key: keys[1], blocks: []string{"1", "3", "5"}}, 279 {key: keys[0], blocks: []string{"6", "8", "10"}}, 280 {key: keys[1], blocks: []string{"7", "9", "11"}}, 281 {key: keys[0], blocks: []string{"12", "14", "16"}}, 282 {key: keys[1], blocks: []string{"13", "15", "17"}}, 283 } { 284 b, ok := iter.next() 285 require.True(t, ok) 286 assert.Equal(t, expected.key, b.staged.key) 287 actual := make([]string, len(b.blocks)) 288 for i := range b.blocks { 289 actual[i] = b.blocks[i].id 290 } 291 assert.Equal(t, expected.blocks, actual) 292 } 293 294 _, ok := iter.next() 295 assert.False(t, ok) 296 } 297 298 func remove(q *blockQueue, key compactionKey, block ...string) { 299 staged, ok := q.staged[key] 300 if !ok { 301 return 302 } 303 for _, b := range block { 304 staged.delete(b) 305 } 306 }