github.com/grafana/pyroscope@v1.18.0/pkg/compactor/split_merge_job_test.go (about) 1 // SPDX-License-Identifier: AGPL-3.0-only 2 // Provenance-includes-location: https://github.com/grafana/mimir/blob/main/pkg/compactor/split_merge_job_test.go 3 // Provenance-includes-license: Apache-2.0 4 // Provenance-includes-copyright: The Cortex Authors. 5 6 package compactor 7 8 import ( 9 "encoding/json" 10 "testing" 11 12 "github.com/oklog/ulid/v2" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/grafana/pyroscope/pkg/phlaredb/block" 17 sharding "github.com/grafana/pyroscope/pkg/phlaredb/sharding" 18 ) 19 20 func TestJob_conflicts(t *testing.T) { 21 block1 := &block.Meta{ULID: ulid.MustNew(1, nil)} 22 block2 := &block.Meta{ULID: ulid.MustNew(2, nil)} 23 block3 := &block.Meta{ULID: ulid.MustNew(3, nil)} 24 block4 := &block.Meta{ULID: ulid.MustNew(4, nil)} 25 26 copyMeta := func(meta *block.Meta) *block.Meta { 27 encoded, err := json.Marshal(meta) 28 require.NoError(t, err) 29 30 decoded := block.Meta{} 31 require.NoError(t, json.Unmarshal(encoded, &decoded)) 32 33 return &decoded 34 } 35 36 withShardIDLabel := func(meta *block.Meta, shardID string) *block.Meta { 37 meta = copyMeta(meta) 38 meta.Labels = map[string]string{sharding.CompactorShardIDLabel: shardID} 39 return meta 40 } 41 42 tests := map[string]struct { 43 first *job 44 second *job 45 expected bool 46 }{ 47 "should conflict if jobs compact different blocks but with overlapping time ranges and same shard": { 48 first: &job{ 49 stage: stageMerge, 50 shardID: "1_of_2", 51 blocksGroup: blocksGroup{ 52 rangeStart: 10, 53 rangeEnd: 20, 54 blocks: []*block.Meta{withShardIDLabel(block1, "1_of_2"), withShardIDLabel(block2, "1_of_2")}, 55 }, 56 }, 57 second: &job{ 58 stage: stageMerge, 59 shardID: "1_of_2", 60 blocksGroup: blocksGroup{ 61 rangeStart: 15, 62 rangeEnd: 25, 63 blocks: []*block.Meta{withShardIDLabel(block3, "1_of_2"), withShardIDLabel(block4, "1_of_2")}, 64 }, 65 }, 66 expected: true, 67 }, 68 "should NOT conflict if jobs compact different blocks with non-overlapping time ranges and same shard": { 69 first: &job{ 70 stage: stageMerge, 71 shardID: "1_of_2", 72 blocksGroup: blocksGroup{ 73 rangeStart: 10, 74 rangeEnd: 20, 75 blocks: []*block.Meta{withShardIDLabel(block1, "1_of_2"), withShardIDLabel(block2, "1_of_2")}, 76 }, 77 }, 78 second: &job{ 79 stage: stageMerge, 80 shardID: "1_of_2", 81 blocksGroup: blocksGroup{ 82 rangeStart: 21, 83 rangeEnd: 30, 84 blocks: []*block.Meta{withShardIDLabel(block3, "1_of_2"), withShardIDLabel(block4, "1_of_2")}, 85 }, 86 }, 87 expected: false, 88 }, 89 "should NOT conflict if jobs compact same blocks with overlapping time ranges but different shard": { 90 first: &job{ 91 stage: stageMerge, 92 shardID: "1_of_2", 93 blocksGroup: blocksGroup{ 94 rangeStart: 10, 95 rangeEnd: 20, 96 blocks: []*block.Meta{withShardIDLabel(block1, "1_of_2"), withShardIDLabel(block2, "1_of_2")}, 97 }, 98 }, 99 second: &job{ 100 stage: stageMerge, 101 shardID: "2_of_2", 102 blocksGroup: blocksGroup{ 103 rangeStart: 10, 104 rangeEnd: 20, 105 blocks: []*block.Meta{withShardIDLabel(block1, "2_of_2"), withShardIDLabel(block2, "2_of_2")}, 106 }, 107 }, 108 expected: false, 109 }, 110 "should conflict if jobs compact same blocks with overlapping time ranges and different shard but at a different stage": { 111 first: &job{ 112 stage: stageSplit, 113 shardID: "1_of_2", 114 blocksGroup: blocksGroup{ 115 rangeStart: 10, 116 rangeEnd: 20, 117 blocks: []*block.Meta{withShardIDLabel(block1, "1_of_2"), withShardIDLabel(block2, "1_of_2")}, 118 }, 119 }, 120 second: &job{ 121 stage: stageMerge, 122 shardID: "2_of_2", 123 blocksGroup: blocksGroup{ 124 rangeStart: 10, 125 rangeEnd: 20, 126 blocks: []*block.Meta{withShardIDLabel(block1, "2_of_2"), withShardIDLabel(block2, "2_of_2")}, 127 }, 128 }, 129 expected: true, 130 }, 131 "should conflict between split and merge jobs with overlapping time ranges": { 132 first: &job{ 133 stage: stageSplit, 134 shardID: "", 135 blocksGroup: blocksGroup{ 136 rangeStart: 10, 137 rangeEnd: 20, 138 blocks: []*block.Meta{block1, block2}, 139 }, 140 }, 141 second: &job{ 142 stage: stageMerge, 143 shardID: "1_of_2", 144 blocksGroup: blocksGroup{ 145 rangeStart: 0, 146 rangeEnd: 40, 147 blocks: []*block.Meta{withShardIDLabel(block3, "1_of_2"), withShardIDLabel(block4, "1_of_2")}, 148 }, 149 }, 150 expected: true, 151 }, 152 "should NOT conflict between split and merge jobs with non-overlapping time ranges": { 153 first: &job{ 154 stage: stageSplit, 155 shardID: "", 156 blocksGroup: blocksGroup{ 157 rangeStart: 10, 158 rangeEnd: 20, 159 blocks: []*block.Meta{block1, block2}, 160 }, 161 }, 162 second: &job{ 163 stage: stageMerge, 164 shardID: "1_of_2", 165 blocksGroup: blocksGroup{ 166 rangeStart: 21, 167 rangeEnd: 40, 168 blocks: []*block.Meta{withShardIDLabel(block3, "1_of_2"), withShardIDLabel(block4, "1_of_2")}, 169 }, 170 }, 171 expected: false, 172 }, 173 } 174 175 for testName, testCase := range tests { 176 t.Run(testName, func(t *testing.T) { 177 assert.Equal(t, testCase.expected, testCase.first.conflicts(testCase.second)) 178 assert.Equal(t, testCase.expected, testCase.second.conflicts(testCase.first)) 179 }) 180 } 181 } 182 183 func TestBlocksGroup_overlaps(t *testing.T) { 184 tests := []struct { 185 first blocksGroup 186 second blocksGroup 187 expected bool 188 }{ 189 { 190 first: blocksGroup{rangeStart: 10, rangeEnd: 20}, 191 second: blocksGroup{rangeStart: 20, rangeEnd: 30}, 192 expected: false, 193 }, { 194 first: blocksGroup{rangeStart: 10, rangeEnd: 20}, 195 second: blocksGroup{rangeStart: 21, rangeEnd: 30}, 196 expected: false, 197 }, { 198 first: blocksGroup{rangeStart: 10, rangeEnd: 20}, 199 second: blocksGroup{rangeStart: 19, rangeEnd: 30}, 200 expected: true, 201 }, { 202 first: blocksGroup{rangeStart: 10, rangeEnd: 21}, 203 second: blocksGroup{rangeStart: 20, rangeEnd: 30}, 204 expected: true, 205 }, { 206 first: blocksGroup{rangeStart: 10, rangeEnd: 20}, 207 second: blocksGroup{rangeStart: 12, rangeEnd: 18}, 208 expected: true, 209 }, 210 } 211 212 for _, tc := range tests { 213 assert.Equal(t, tc.expected, tc.first.overlaps(tc.second)) 214 assert.Equal(t, tc.expected, tc.second.overlaps(tc.first)) 215 } 216 } 217 218 func TestBlocksGroup_getNonShardedBlocks(t *testing.T) { 219 block1 := ulid.MustNew(1, nil) 220 block2 := ulid.MustNew(2, nil) 221 block3 := ulid.MustNew(3, nil) 222 223 tests := map[string]struct { 224 input blocksGroup 225 expected []*block.Meta 226 }{ 227 "should return nil if the group is empty": { 228 input: blocksGroup{}, 229 expected: nil, 230 }, 231 "should return nil if the group contains only sharded blocks": { 232 input: blocksGroup{blocks: []*block.Meta{ 233 {ULID: block1, Labels: map[string]string{sharding.CompactorShardIDLabel: "1"}}, 234 {ULID: block2, Labels: map[string]string{sharding.CompactorShardIDLabel: "1"}}, 235 }}, 236 expected: nil, 237 }, 238 "should return the list of non-sharded blocks if exist in the group": { 239 input: blocksGroup{blocks: []*block.Meta{ 240 {ULID: block1}, 241 {ULID: block2, Labels: map[string]string{sharding.CompactorShardIDLabel: "1"}}, 242 {ULID: block3, Labels: map[string]string{"key": "value"}}, 243 }}, 244 expected: []*block.Meta{ 245 {ULID: block1}, 246 {ULID: block3, Labels: map[string]string{"key": "value"}}, 247 }, 248 }, 249 "should consider non-sharded a block with the shard ID label but empty value": { 250 input: blocksGroup{blocks: []*block.Meta{ 251 {ULID: block1, Labels: map[string]string{sharding.CompactorShardIDLabel: ""}}, 252 {ULID: block2, Labels: map[string]string{sharding.CompactorShardIDLabel: "1"}}, 253 {ULID: block3, Labels: map[string]string{"key": "value"}}, 254 }}, 255 expected: []*block.Meta{ 256 {ULID: block1, Labels: map[string]string{sharding.CompactorShardIDLabel: ""}}, 257 {ULID: block3, Labels: map[string]string{"key": "value"}}, 258 }, 259 }, 260 } 261 262 for _, tc := range tests { 263 assert.Equal(t, tc.expected, tc.input.getNonShardedBlocks()) 264 } 265 }