github.com/grafana/pyroscope@v1.18.0/pkg/compactor/compactor_test.go (about) 1 // SPDX-License-Identifier: AGPL-3.0-only 2 // Provenance-includes-location: https://github.com/grafana/mimir/blob/main/pkg/compactor/compactor_test.go 3 // Provenance-includes-license: Apache-2.0 4 // Provenance-includes-copyright: The Cortex Authors. 5 6 package compactor 7 8 import ( 9 "bytes" 10 "context" 11 "crypto/rand" 12 "encoding/json" 13 "flag" 14 "fmt" 15 "os" 16 "path" 17 "path/filepath" 18 "strconv" 19 "strings" 20 "testing" 21 "time" 22 23 "github.com/go-kit/log" 24 "github.com/grafana/dskit/concurrency" 25 "github.com/grafana/dskit/flagext" 26 "github.com/grafana/dskit/kv/consul" 27 "github.com/grafana/dskit/ring" 28 "github.com/grafana/dskit/services" 29 "github.com/grafana/dskit/test" 30 "github.com/oklog/ulid/v2" 31 "github.com/pkg/errors" 32 "github.com/prometheus/client_golang/prometheus" 33 prom_testutil "github.com/prometheus/client_golang/prometheus/testutil" 34 "github.com/prometheus/common/model" 35 "github.com/stretchr/testify/assert" 36 "github.com/stretchr/testify/mock" 37 "github.com/stretchr/testify/require" 38 "github.com/thanos-io/objstore" 39 "gopkg.in/yaml.v3" 40 41 pyroscope_objstore "github.com/grafana/pyroscope/pkg/objstore" 42 "github.com/grafana/pyroscope/pkg/objstore/providers/filesystem" 43 "github.com/grafana/pyroscope/pkg/phlaredb/block" 44 "github.com/grafana/pyroscope/pkg/phlaredb/block/testutil" 45 "github.com/grafana/pyroscope/pkg/phlaredb/bucket" 46 "github.com/grafana/pyroscope/pkg/pprof/testhelper" 47 "github.com/grafana/pyroscope/pkg/test/mocks/mockobjstore" 48 "github.com/grafana/pyroscope/pkg/validation" 49 ) 50 51 const ( 52 instanceID = "compactor-1" 53 addr = "1.2.3.4" 54 ) 55 56 func TestConfig_ShouldSupportYamlConfig(t *testing.T) { 57 yamlCfg := ` 58 block_ranges: [2h, 48h] 59 block_sync_concurrency: 123 60 data_dir: /tmp 61 compaction_interval: 15m 62 compaction_retries: 123 63 ` 64 65 cfg := Config{} 66 flagext.DefaultValues(&cfg) 67 assert.NoError(t, yaml.Unmarshal([]byte(yamlCfg), &cfg)) 68 assert.Equal(t, DurationList{2 * time.Hour, 48 * time.Hour}, cfg.BlockRanges) 69 assert.Equal(t, 123, cfg.BlockSyncConcurrency) 70 assert.Equal(t, "/tmp", cfg.DataDir) 71 assert.Equal(t, 15*time.Minute, cfg.CompactionInterval) 72 assert.Equal(t, 123, cfg.CompactionRetries) 73 } 74 75 func TestConfig_ShouldSupportCliFlags(t *testing.T) { 76 fs := flag.NewFlagSet("", flag.PanicOnError) 77 cfg := Config{} 78 cfg.RegisterFlags(fs, log.NewNopLogger()) 79 require.NoError(t, fs.Parse([]string{ 80 "-compactor.block-ranges=2h,48h", 81 "-compactor.block-sync-concurrency=123", 82 "-compactor.data-dir=/tmp", 83 "-compactor.compaction-interval=15m", 84 "-compactor.compaction-retries=123", 85 })) 86 87 assert.Equal(t, DurationList{2 * time.Hour, 48 * time.Hour}, cfg.BlockRanges) 88 assert.Equal(t, 123, cfg.BlockSyncConcurrency) 89 assert.Equal(t, "/tmp", cfg.DataDir) 90 assert.Equal(t, 15*time.Minute, cfg.CompactionInterval) 91 assert.Equal(t, 123, cfg.CompactionRetries) 92 } 93 94 func TestConfig_Validate(t *testing.T) { 95 tests := map[string]struct { 96 setup func(cfg *Config) 97 expected string 98 maxBlock time.Duration 99 }{ 100 "should pass with the default config": { 101 setup: func(cfg *Config) {}, 102 expected: "", 103 maxBlock: 1 * time.Hour, 104 }, 105 "should pass with only 1 block range period": { 106 setup: func(cfg *Config) { 107 cfg.BlockRanges = DurationList{time.Hour} 108 }, 109 expected: "", 110 maxBlock: 1 * time.Hour, 111 }, 112 "should fail with non divisible block range periods": { 113 setup: func(cfg *Config) { 114 cfg.BlockRanges = DurationList{2 * time.Hour, 12 * time.Hour, 24 * time.Hour, 30 * time.Hour} 115 }, 116 expected: errors.Errorf(errInvalidBlockRanges, 30*time.Hour, 24*time.Hour).Error(), 117 maxBlock: 2 * time.Hour, 118 }, 119 "should fail on unknown compaction jobs order": { 120 setup: func(cfg *Config) { 121 cfg.CompactionJobsOrder = "everything-is-important" 122 }, 123 expected: errInvalidCompactionOrder.Error(), 124 maxBlock: 1 * time.Hour, 125 }, 126 "should fail on invalid value of max-opening-blocks-concurrency": { 127 setup: func(cfg *Config) { cfg.MaxOpeningBlocksConcurrency = 0 }, 128 expected: errInvalidMaxOpeningBlocksConcurrency.Error(), 129 maxBlock: 1 * time.Hour, 130 }, 131 "should fail on first range not divisible by max block duration": { 132 setup: func(cfg *Config) { 133 cfg.BlockRanges = DurationList{2 * time.Hour, 12 * time.Hour, 24 * time.Hour} 134 }, 135 expected: errors.Errorf(errInvalidBlockDuration, (2 * time.Hour).String(), (15 * time.Hour).String()).Error(), 136 maxBlock: 15 * time.Hour, 137 }, 138 } 139 140 for testName, testData := range tests { 141 t.Run(testName, func(t *testing.T) { 142 cfg := &Config{} 143 flagext.DefaultValues(cfg) 144 testData.setup(cfg) 145 146 if actualErr := cfg.Validate(testData.maxBlock); testData.expected != "" { 147 assert.EqualError(t, actualErr, testData.expected) 148 } else { 149 assert.NoError(t, actualErr) 150 } 151 }) 152 } 153 } 154 155 func TestMultitenantCompactor_ShouldDoNothingOnNoUserBlocks(t *testing.T) { 156 t.Parallel() 157 158 // No user blocks stored in the bucket. 159 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 160 bucketClient.MockIter("", []string{}, nil) 161 cfg := prepareConfig(t) 162 c, _, _, logs, registry := prepare(t, cfg, bucketClient) 163 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 164 165 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 166 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 167 168 // Wait until a run has completed. 169 test.Poll(t, time.Second, 1.0, func() interface{} { 170 return prom_testutil.ToFloat64(c.compactionRunsCompleted) 171 }) 172 173 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 174 175 assert.Equal(t, prom_testutil.ToFloat64(c.compactionRunInterval), cfg.CompactionInterval.Seconds()) 176 177 assert.Equal(t, []string{ 178 `level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`, 179 `level=info component=compactor msg="compactor is ACTIVE in the ring"`, 180 `level=info component=compactor msg="discovering users from bucket"`, 181 `level=info component=compactor msg="discovered users from bucket" users=0`, 182 }, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n"))) 183 184 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 185 # TYPE pyroscope_compactor_runs_started_total counter 186 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 187 pyroscope_compactor_runs_started_total 1 188 189 # TYPE pyroscope_compactor_runs_completed_total counter 190 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 191 pyroscope_compactor_runs_completed_total 1 192 193 # TYPE pyroscope_compactor_runs_failed_total counter 194 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 195 pyroscope_compactor_runs_failed_total{reason="error"} 0 196 pyroscope_compactor_runs_failed_total{reason="shutdown"} 0 197 198 # HELP pyroscope_compactor_garbage_collection_duration_seconds Time it took to perform garbage collection iteration. 199 # TYPE pyroscope_compactor_garbage_collection_duration_seconds histogram 200 pyroscope_compactor_garbage_collection_duration_seconds_bucket{le="+Inf"} 0 201 pyroscope_compactor_garbage_collection_duration_seconds_sum 0 202 pyroscope_compactor_garbage_collection_duration_seconds_count 0 203 204 # HELP pyroscope_compactor_garbage_collection_failures_total Total number of failed garbage collection operations. 205 # TYPE pyroscope_compactor_garbage_collection_failures_total counter 206 pyroscope_compactor_garbage_collection_failures_total 0 207 208 # HELP pyroscope_compactor_garbage_collection_total Total number of garbage collection operations. 209 # TYPE pyroscope_compactor_garbage_collection_total counter 210 pyroscope_compactor_garbage_collection_total 0 211 212 # HELP pyroscope_compactor_meta_sync_duration_seconds Duration of the blocks metadata synchronization in seconds. 213 # TYPE pyroscope_compactor_meta_sync_duration_seconds histogram 214 pyroscope_compactor_meta_sync_duration_seconds_bucket{le="+Inf"} 0 215 pyroscope_compactor_meta_sync_duration_seconds_sum 0 216 pyroscope_compactor_meta_sync_duration_seconds_count 0 217 218 # HELP pyroscope_compactor_meta_sync_failures_total Total blocks metadata synchronization failures. 219 # TYPE pyroscope_compactor_meta_sync_failures_total counter 220 pyroscope_compactor_meta_sync_failures_total 0 221 222 # HELP pyroscope_compactor_meta_syncs_total Total blocks metadata synchronization attempts. 223 # TYPE pyroscope_compactor_meta_syncs_total counter 224 pyroscope_compactor_meta_syncs_total 0 225 226 # HELP pyroscope_compactor_group_compaction_runs_completed_total Total number of group completed compaction runs. This also includes compactor group runs that resulted with no compaction. 227 # TYPE pyroscope_compactor_group_compaction_runs_completed_total counter 228 pyroscope_compactor_group_compaction_runs_completed_total 0 229 230 # HELP pyroscope_compactor_group_compaction_runs_started_total Total number of group compaction attempts. 231 # TYPE pyroscope_compactor_group_compaction_runs_started_total counter 232 pyroscope_compactor_group_compaction_runs_started_total 0 233 234 # HELP pyroscope_compactor_group_compactions_failures_total Total number of failed group compactions. 235 # TYPE pyroscope_compactor_group_compactions_failures_total counter 236 pyroscope_compactor_group_compactions_failures_total 0 237 238 # HELP pyroscope_compactor_group_compactions_total Total number of group compaction attempts that resulted in new block(s). 239 # TYPE pyroscope_compactor_group_compactions_total counter 240 pyroscope_compactor_group_compactions_total 0 241 242 # TYPE pyroscope_compactor_block_cleanup_failures_total counter 243 # HELP pyroscope_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted. 244 pyroscope_compactor_block_cleanup_failures_total 0 245 246 # HELP pyroscope_compactor_blocks_cleaned_total Total number of blocks deleted. 247 # TYPE pyroscope_compactor_blocks_cleaned_total counter 248 pyroscope_compactor_blocks_cleaned_total 0 249 250 # HELP pyroscope_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor. 251 # TYPE pyroscope_compactor_blocks_marked_for_deletion_total counter 252 pyroscope_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0 253 pyroscope_compactor_blocks_marked_for_deletion_total{reason="partial"} 0 254 pyroscope_compactor_blocks_marked_for_deletion_total{reason="retention"} 0 255 256 # TYPE pyroscope_compactor_block_cleanup_started_total counter 257 # HELP pyroscope_compactor_block_cleanup_started_total Total number of blocks cleanup runs started. 258 pyroscope_compactor_block_cleanup_started_total 1 259 260 # TYPE pyroscope_compactor_block_cleanup_completed_total counter 261 # HELP pyroscope_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed. 262 pyroscope_compactor_block_cleanup_completed_total 1 263 264 # TYPE pyroscope_compactor_block_cleanup_failed_total counter 265 # HELP pyroscope_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed. 266 pyroscope_compactor_block_cleanup_failed_total 0 267 `), 268 "pyroscope_compactor_runs_started_total", 269 "pyroscope_compactor_runs_completed_total", 270 "pyroscope_compactor_runs_failed_total", 271 "pyroscope_compactor_garbage_collection_duration_seconds", 272 "pyroscope_compactor_garbage_collection_failures_total", 273 "pyroscope_compactor_garbage_collection_total", 274 "pyroscope_compactor_meta_sync_duration_seconds", 275 "pyroscope_compactor_meta_sync_failures_total", 276 "pyroscope_compactor_meta_syncs_total", 277 "pyroscope_compactor_group_compaction_runs_completed_total", 278 "pyroscope_compactor_group_compaction_runs_started_total", 279 "pyroscope_compactor_group_compactions_failures_total", 280 "pyroscope_compactor_group_compactions_total", 281 "pyroscope_compactor_block_cleanup_failures_total", 282 "pyroscope_compactor_blocks_cleaned_total", 283 "pyroscope_compactor_blocks_marked_for_deletion_total", 284 "pyroscope_compactor_block_cleanup_started_total", 285 "pyroscope_compactor_block_cleanup_completed_total", 286 "pyroscope_compactor_block_cleanup_failed_total", 287 )) 288 } 289 290 func TestMultitenantCompactor_ShouldRetryCompactionOnFailureWhileDiscoveringUsersFromBucket(t *testing.T) { 291 t.Parallel() 292 293 // Fail to iterate over the bucket while discovering users. 294 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 295 bucketClient.MockIter("", nil, errors.New("failed to iterate the bucket")) 296 297 c, _, _, logs, registry := prepare(t, prepareConfig(t), bucketClient) 298 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 299 300 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 301 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 302 t.Cleanup(func() { 303 t.Log(logs.String()) 304 }) 305 306 // Wait until all retry attempts have completed. 307 test.Poll(t, time.Second, 1.0, func() interface{} { 308 return prom_testutil.ToFloat64(c.compactionRunsErred) 309 }) 310 311 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 312 313 // Ensure the bucket iteration has been retried the configured number of times. 314 bucketClient.AssertNumberOfCalls(t, "Iter", 1+3) 315 316 assert.Equal(t, []string{ 317 `level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`, 318 `level=info component=compactor msg="compactor is ACTIVE in the ring"`, 319 `level=info component=compactor msg="discovering users from bucket"`, 320 `level=error component=compactor msg="failed to discover users from bucket" err="failed to iterate the bucket"`, 321 }, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n"))) 322 323 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 324 # TYPE pyroscope_compactor_runs_started_total counter 325 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 326 pyroscope_compactor_runs_started_total 1 327 328 # TYPE pyroscope_compactor_runs_completed_total counter 329 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 330 pyroscope_compactor_runs_completed_total 0 331 332 # TYPE pyroscope_compactor_runs_failed_total counter 333 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 334 pyroscope_compactor_runs_failed_total{reason="error"} 1 335 pyroscope_compactor_runs_failed_total{reason="shutdown"} 0 336 337 # HELP pyroscope_compactor_garbage_collection_duration_seconds Time it took to perform garbage collection iteration. 338 # TYPE pyroscope_compactor_garbage_collection_duration_seconds histogram 339 pyroscope_compactor_garbage_collection_duration_seconds_bucket{le="+Inf"} 0 340 pyroscope_compactor_garbage_collection_duration_seconds_sum 0 341 pyroscope_compactor_garbage_collection_duration_seconds_count 0 342 343 # HELP pyroscope_compactor_garbage_collection_failures_total Total number of failed garbage collection operations. 344 # TYPE pyroscope_compactor_garbage_collection_failures_total counter 345 pyroscope_compactor_garbage_collection_failures_total 0 346 347 # HELP pyroscope_compactor_garbage_collection_total Total number of garbage collection operations. 348 # TYPE pyroscope_compactor_garbage_collection_total counter 349 pyroscope_compactor_garbage_collection_total 0 350 351 # HELP pyroscope_compactor_meta_sync_duration_seconds Duration of the blocks metadata synchronization in seconds. 352 # TYPE pyroscope_compactor_meta_sync_duration_seconds histogram 353 pyroscope_compactor_meta_sync_duration_seconds_bucket{le="+Inf"} 0 354 pyroscope_compactor_meta_sync_duration_seconds_sum 0 355 pyroscope_compactor_meta_sync_duration_seconds_count 0 356 357 # HELP pyroscope_compactor_meta_sync_failures_total Total blocks metadata synchronization failures. 358 # TYPE pyroscope_compactor_meta_sync_failures_total counter 359 pyroscope_compactor_meta_sync_failures_total 0 360 361 # HELP pyroscope_compactor_meta_syncs_total Total blocks metadata synchronization attempts. 362 # TYPE pyroscope_compactor_meta_syncs_total counter 363 pyroscope_compactor_meta_syncs_total 0 364 365 # HELP pyroscope_compactor_group_compaction_runs_completed_total Total number of group completed compaction runs. This also includes compactor group runs that resulted with no compaction. 366 # TYPE pyroscope_compactor_group_compaction_runs_completed_total counter 367 pyroscope_compactor_group_compaction_runs_completed_total 0 368 369 # HELP pyroscope_compactor_group_compaction_runs_started_total Total number of group compaction attempts. 370 # TYPE pyroscope_compactor_group_compaction_runs_started_total counter 371 pyroscope_compactor_group_compaction_runs_started_total 0 372 373 # HELP pyroscope_compactor_group_compactions_failures_total Total number of failed group compactions. 374 # TYPE pyroscope_compactor_group_compactions_failures_total counter 375 pyroscope_compactor_group_compactions_failures_total 0 376 377 # HELP pyroscope_compactor_group_compactions_total Total number of group compaction attempts that resulted in new block(s). 378 # TYPE pyroscope_compactor_group_compactions_total counter 379 pyroscope_compactor_group_compactions_total 0 380 381 # TYPE pyroscope_compactor_block_cleanup_failures_total counter 382 # HELP pyroscope_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted. 383 pyroscope_compactor_block_cleanup_failures_total 0 384 385 # HELP pyroscope_compactor_blocks_cleaned_total Total number of blocks deleted. 386 # TYPE pyroscope_compactor_blocks_cleaned_total counter 387 pyroscope_compactor_blocks_cleaned_total 0 388 389 # HELP pyroscope_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor. 390 # TYPE pyroscope_compactor_blocks_marked_for_deletion_total counter 391 pyroscope_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0 392 pyroscope_compactor_blocks_marked_for_deletion_total{reason="partial"} 0 393 pyroscope_compactor_blocks_marked_for_deletion_total{reason="retention"} 0 394 395 # TYPE pyroscope_compactor_block_cleanup_started_total counter 396 # HELP pyroscope_compactor_block_cleanup_started_total Total number of blocks cleanup runs started. 397 pyroscope_compactor_block_cleanup_started_total 1 398 399 # TYPE pyroscope_compactor_block_cleanup_completed_total counter 400 # HELP pyroscope_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed. 401 pyroscope_compactor_block_cleanup_completed_total 0 402 403 # TYPE pyroscope_compactor_block_cleanup_failed_total counter 404 # HELP pyroscope_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed. 405 pyroscope_compactor_block_cleanup_failed_total 1 406 `), 407 "pyroscope_compactor_runs_started_total", 408 "pyroscope_compactor_runs_completed_total", 409 "pyroscope_compactor_runs_failed_total", 410 "pyroscope_compactor_garbage_collection_duration_seconds", 411 "pyroscope_compactor_garbage_collection_failures_total", 412 "pyroscope_compactor_garbage_collection_total", 413 "pyroscope_compactor_meta_sync_duration_seconds", 414 "pyroscope_compactor_meta_sync_failures_total", 415 "pyroscope_compactor_meta_syncs_total", 416 "pyroscope_compactor_group_compaction_runs_completed_total", 417 "pyroscope_compactor_group_compaction_runs_started_total", 418 "pyroscope_compactor_group_compactions_failures_total", 419 "pyroscope_compactor_group_compactions_total", 420 "pyroscope_compactor_block_cleanup_failures_total", 421 "pyroscope_compactor_blocks_cleaned_total", 422 "pyroscope_compactor_blocks_marked_for_deletion_total", 423 "pyroscope_compactor_block_cleanup_started_total", 424 "pyroscope_compactor_block_cleanup_completed_total", 425 "pyroscope_compactor_block_cleanup_failed_total", 426 )) 427 } 428 429 func TestMultitenantCompactor_ShouldIncrementCompactionErrorIfFailedToCompactASingleTenant(t *testing.T) { 430 t.Parallel() 431 432 userID := "test-user" 433 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 434 bucketClient.MockIter("", []string{userID}, nil) 435 bucketClient.MockIter(userID+"/phlaredb/", []string{userID + "/phlaredb/01DTVP434PA9VFXSW2JKB3392D", userID + "/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ"}, nil) 436 bucketClient.MockIter(userID+"/phlaredb/markers/", nil, nil) 437 bucketClient.MockExists(path.Join(userID, "phlaredb", bucket.TenantDeletionMarkPath), false, nil) 438 bucketClient.MockGet(userID+"/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil) 439 bucketClient.MockGet(userID+"/phlaredb/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil) 440 bucketClient.MockGet(userID+"/phlaredb/01DTVP434PA9VFXSW2JKB3392D/no-compact-mark.json", "", nil) 441 bucketClient.MockGet(userID+"/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", mockBlockMetaJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ"), nil) 442 bucketClient.MockGet(userID+"/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", "", nil) 443 bucketClient.MockGet(userID+"/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/no-compact-mark.json", "", nil) 444 bucketClient.MockGet(userID+"/phlaredb/bucket-index.json.gz", "", nil) 445 bucketClient.MockUpload(userID+"/phlaredb/bucket-index.json.gz", nil) 446 447 c, _, tsdbPlannerMock, _, registry := prepare(t, prepareConfig(t), bucketClient) 448 tsdbPlannerMock.On("Plan", mock.Anything, mock.Anything).Return([]*block.Meta{}, errors.New("Failed to plan")) 449 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 450 451 // Wait until all retry attempts have completed. 452 test.Poll(t, time.Minute, 1.0, func() interface{} { 453 return prom_testutil.ToFloat64(c.compactionRunsErred) 454 }) 455 456 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 457 458 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 459 # TYPE pyroscope_compactor_runs_started_total counter 460 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 461 pyroscope_compactor_runs_started_total 1 462 463 # TYPE pyroscope_compactor_runs_completed_total counter 464 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 465 pyroscope_compactor_runs_completed_total 0 466 467 # TYPE pyroscope_compactor_runs_failed_total counter 468 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 469 pyroscope_compactor_runs_failed_total{reason="error"} 1 470 pyroscope_compactor_runs_failed_total{reason="shutdown"} 0 471 `), 472 "pyroscope_compactor_runs_started_total", 473 "pyroscope_compactor_runs_completed_total", 474 "pyroscope_compactor_runs_failed_total", 475 )) 476 } 477 478 func TestMultitenantCompactor_ShouldIncrementCompactionShutdownIfTheContextIsCancelled(t *testing.T) { 479 t.Parallel() 480 481 userID := "test-user" 482 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 483 bucketClient.MockIter("", []string{userID}, nil) 484 bucketClient.MockIter(userID+"/phlaredb/markers/", nil, nil) 485 bucketClient.MockIter(userID+"/phlaredb/", []string{userID + "/phlaredb/01DTVP434PA9VFXSW2JKB3392D", userID + "/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ"}, nil) 486 bucketClient.MockExists(path.Join(userID, "phlaredb/", bucket.TenantDeletionMarkPath), false, nil) 487 bucketClient.MockGet(userID+"/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil) 488 bucketClient.MockGet(userID+"/phlaredb/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil) 489 bucketClient.MockGet(userID+"/phlaredb/01DTVP434PA9VFXSW2JKB3392D/no-compact-mark.json", "", nil) 490 bucketClient.MockGet(userID+"/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", mockBlockMetaJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ"), nil) 491 bucketClient.MockGet(userID+"/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", "", nil) 492 bucketClient.MockGet(userID+"/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/no-compact-mark.json", "", nil) 493 bucketClient.MockGet(userID+"/phlaredb/bucket-index.json.gz", "", nil) 494 bucketClient.MockUpload(userID+"/phlaredb/bucket-index.json.gz", nil) 495 496 c, _, tsdbPlannerMock, logs, registry := prepare(t, prepareConfig(t), bucketClient) 497 t.Cleanup(func() { 498 t.Log(logs.String()) 499 }) 500 // Mock the planner as if a shutdown was triggered and the service was terminated. 501 tsdbPlannerMock.On("Plan", mock.Anything, mock.Anything).Return([]*block.Meta{}, context.Canceled) 502 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 503 504 // Wait until the error is recorded. 505 test.Poll(t, time.Second, 1.0, func() interface{} { 506 return prom_testutil.ToFloat64(c.compactionRunsShutdown) 507 }) 508 509 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 510 511 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 512 # TYPE pyroscope_compactor_runs_started_total counter 513 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 514 pyroscope_compactor_runs_started_total 1 515 516 # TYPE pyroscope_compactor_runs_completed_total counter 517 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 518 pyroscope_compactor_runs_completed_total 0 519 520 # TYPE pyroscope_compactor_runs_failed_total counter 521 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 522 pyroscope_compactor_runs_failed_total{reason="error"} 0 523 pyroscope_compactor_runs_failed_total{reason="shutdown"} 1 524 `), 525 "pyroscope_compactor_runs_started_total", 526 "pyroscope_compactor_runs_completed_total", 527 "pyroscope_compactor_runs_failed_total", 528 )) 529 } 530 531 func TestMultitenantCompactor_ShouldIterateOverUsersAndRunCompaction(t *testing.T) { 532 t.Parallel() 533 534 // Mock the bucket to contain two users, each one with two blocks (to make sure that grouper doesn't skip them). 535 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 536 bucketClient.MockIter("", []string{"user-1", "user-2"}, nil) 537 bucketClient.MockExists(path.Join("user-1", "phlaredb/", bucket.TenantDeletionMarkPath), false, nil) 538 bucketClient.MockExists(path.Join("user-2", "phlaredb/", bucket.TenantDeletionMarkPath), false, nil) 539 bucketClient.MockIter("user-1/phlaredb/", []string{"user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D", "user-1/phlaredb/01FS51A7GQ1RQWV35DBVYQM4KF"}, nil) 540 bucketClient.MockIter("user-2/phlaredb/", []string{"user-2/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ", "user-2/phlaredb/01FRSF035J26D6CGX7STCSD1KG"}, nil) 541 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil) 542 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil) 543 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/no-compact-mark.json", "", nil) 544 bucketClient.MockGet("user-1/phlaredb/01FS51A7GQ1RQWV35DBVYQM4KF/meta.json", mockBlockMetaJSON("01FS51A7GQ1RQWV35DBVYQM4KF"), nil) 545 bucketClient.MockGet("user-1/phlaredb/01FS51A7GQ1RQWV35DBVYQM4KF/deletion-mark.json", "", nil) 546 bucketClient.MockGet("user-1/phlaredb/01FS51A7GQ1RQWV35DBVYQM4KF/no-compact-mark.json", "", nil) 547 548 bucketClient.MockGet("user-2/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", mockBlockMetaJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ"), nil) 549 bucketClient.MockGet("user-2/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", "", nil) 550 bucketClient.MockGet("user-2/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/no-compact-mark.json", "", nil) 551 bucketClient.MockGet("user-2/phlaredb/01FRSF035J26D6CGX7STCSD1KG/meta.json", mockBlockMetaJSON("01FRSF035J26D6CGX7STCSD1KG"), nil) 552 bucketClient.MockGet("user-2/phlaredb/01FRSF035J26D6CGX7STCSD1KG/deletion-mark.json", "", nil) 553 bucketClient.MockGet("user-2/phlaredb/01FRSF035J26D6CGX7STCSD1KG/no-compact-mark.json", "", nil) 554 bucketClient.MockGet("user-1/phlaredb/bucket-index.json.gz", "", nil) 555 bucketClient.MockGet("user-2/phlaredb/bucket-index.json.gz", "", nil) 556 bucketClient.MockIter("user-1/phlaredb/markers/", nil, nil) 557 bucketClient.MockIter("user-2/phlaredb/markers/", nil, nil) 558 bucketClient.MockUpload("user-1/phlaredb/bucket-index.json.gz", nil) 559 bucketClient.MockUpload("user-2/phlaredb/bucket-index.json.gz", nil) 560 561 c, _, tsdbPlanner, logs, registry := prepare(t, prepareConfig(t), bucketClient) 562 563 // Mock the planner as if there's no compaction to do, 564 // in order to simplify tests (all in all, we just want to 565 // test our logic and not TSDB compactor which we expect to 566 // be already tested). 567 tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*block.Meta{}, nil) 568 569 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 570 571 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 572 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 573 574 // Wait until a run has completed. 575 test.Poll(t, time.Second, 1.0, func() interface{} { 576 return prom_testutil.ToFloat64(c.compactionRunsCompleted) 577 }) 578 579 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 580 581 // Ensure a plan has been executed for the blocks of each user. 582 tsdbPlanner.AssertNumberOfCalls(t, "Plan", 2) 583 584 assert.ElementsMatch(t, []string{ 585 `level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`, 586 `level=info component=compactor msg="compactor is ACTIVE in the ring"`, 587 `level=info component=compactor msg="discovering users from bucket"`, 588 `level=info component=compactor msg="discovered users from bucket" users=2`, 589 `level=info component=compactor msg="starting compaction of user blocks" tenant=user-1`, 590 `level=info component=compactor tenant=user-1 msg="start sync of metas"`, 591 `level=info component=compactor tenant=user-1 msg="start of GC"`, 592 `level=debug component=compactor tenant=user-1 msg="grouper found a compactable blocks group" groupKey=0@17241709254077376921-merge--1574776800000-1574784000000 job="stage: merge, range start: 1574776800000, range end: 1574784000000, shard: , blocks: 01DTVP434PA9VFXSW2JKB3392D (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z),01FS51A7GQ1RQWV35DBVYQM4KF (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z)"`, 593 `level=info component=compactor tenant=user-1 msg="start of compactions"`, 594 `level=info component=compactor tenant=user-1 groupKey=0@17241709254077376921-merge--1574776800000-1574784000000 msg="compaction job succeeded"`, 595 `level=info component=compactor tenant=user-1 msg="compaction iterations done"`, 596 `level=info component=compactor msg="successfully compacted user blocks" tenant=user-1`, 597 `level=info component=compactor msg="starting compaction of user blocks" tenant=user-2`, 598 `level=info component=compactor tenant=user-2 msg="start sync of metas"`, 599 `level=info component=compactor tenant=user-2 msg="start of GC"`, 600 `level=debug component=compactor tenant=user-2 msg="grouper found a compactable blocks group" groupKey=0@17241709254077376921-merge--1574776800000-1574784000000 job="stage: merge, range start: 1574776800000, range end: 1574784000000, shard: , blocks: 01DTW0ZCPDDNV4BV83Q2SV4QAZ (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z),01FRSF035J26D6CGX7STCSD1KG (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z)"`, 601 `level=info component=compactor tenant=user-2 msg="start of compactions"`, 602 `level=info component=compactor tenant=user-2 groupKey=0@17241709254077376921-merge--1574776800000-1574784000000 msg="compaction job succeeded"`, 603 `level=info component=compactor tenant=user-2 msg="compaction iterations done"`, 604 `level=info component=compactor msg="successfully compacted user blocks" tenant=user-2`, 605 }, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n"))) 606 607 // Instead of testing for shipper metrics, we only check our metrics here. 608 // Real shipper metrics are too variable to embed into a test. 609 testedMetrics := []string{ 610 "pyroscope_compactor_runs_started_total", "pyroscope_compactor_runs_completed_total", "pyroscope_compactor_runs_failed_total", 611 "pyroscope_compactor_blocks_cleaned_total", "pyroscope_compactor_block_cleanup_failures_total", "pyroscope_compactor_blocks_marked_for_deletion_total", 612 "pyroscope_compactor_block_cleanup_started_total", "pyroscope_compactor_block_cleanup_completed_total", "pyroscope_compactor_block_cleanup_failed_total", 613 "pyroscope_compactor_group_compaction_runs_completed_total", "pyroscope_compactor_group_compaction_runs_started_total", 614 "pyroscope_compactor_group_compactions_failures_total", "pyroscope_compactor_group_compactions_total", 615 } 616 617 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 618 # TYPE pyroscope_compactor_runs_started_total counter 619 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 620 pyroscope_compactor_runs_started_total 1 621 622 # TYPE pyroscope_compactor_runs_completed_total counter 623 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 624 pyroscope_compactor_runs_completed_total 1 625 626 # TYPE pyroscope_compactor_runs_failed_total counter 627 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 628 pyroscope_compactor_runs_failed_total{reason="error"} 0 629 pyroscope_compactor_runs_failed_total{reason="shutdown"} 0 630 631 # HELP pyroscope_compactor_group_compaction_runs_completed_total Total number of group completed compaction runs. This also includes compactor group runs that resulted with no compaction. 632 # TYPE pyroscope_compactor_group_compaction_runs_completed_total counter 633 pyroscope_compactor_group_compaction_runs_completed_total 2 634 635 # HELP pyroscope_compactor_group_compaction_runs_started_total Total number of group compaction attempts. 636 # TYPE pyroscope_compactor_group_compaction_runs_started_total counter 637 pyroscope_compactor_group_compaction_runs_started_total 2 638 639 # HELP pyroscope_compactor_group_compactions_failures_total Total number of failed group compactions. 640 # TYPE pyroscope_compactor_group_compactions_failures_total counter 641 pyroscope_compactor_group_compactions_failures_total 0 642 643 # HELP pyroscope_compactor_group_compactions_total Total number of group compaction attempts that resulted in new block(s). 644 # TYPE pyroscope_compactor_group_compactions_total counter 645 pyroscope_compactor_group_compactions_total 0 646 647 # TYPE pyroscope_compactor_block_cleanup_failures_total counter 648 # HELP pyroscope_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted. 649 pyroscope_compactor_block_cleanup_failures_total 0 650 651 # HELP pyroscope_compactor_blocks_cleaned_total Total number of blocks deleted. 652 # TYPE pyroscope_compactor_blocks_cleaned_total counter 653 pyroscope_compactor_blocks_cleaned_total 0 654 655 # HELP pyroscope_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor. 656 # TYPE pyroscope_compactor_blocks_marked_for_deletion_total counter 657 pyroscope_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0 658 pyroscope_compactor_blocks_marked_for_deletion_total{reason="partial"} 0 659 pyroscope_compactor_blocks_marked_for_deletion_total{reason="retention"} 0 660 661 # TYPE pyroscope_compactor_block_cleanup_started_total counter 662 # HELP pyroscope_compactor_block_cleanup_started_total Total number of blocks cleanup runs started. 663 pyroscope_compactor_block_cleanup_started_total 1 664 665 # TYPE pyroscope_compactor_block_cleanup_completed_total counter 666 # HELP pyroscope_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed. 667 pyroscope_compactor_block_cleanup_completed_total 1 668 669 # TYPE pyroscope_compactor_block_cleanup_failed_total counter 670 # HELP pyroscope_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed. 671 pyroscope_compactor_block_cleanup_failed_total 0 672 `), testedMetrics...)) 673 } 674 675 func TestMultitenantCompactor_ShouldStopCompactingTenantOnReachingMaxCompactionTime(t *testing.T) { 676 t.Parallel() 677 678 // By using blocks with different labels, we get two compaction jobs. Only one of these jobs will be started, 679 // and since its planning will take longer than maxCompactionTime, we stop compactions early. 680 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 681 bucketClient.MockIter("", []string{"user-1"}, nil) 682 bucketClient.MockExists(path.Join("user-1", "phlaredb/", bucket.TenantDeletionMarkPath), false, nil) 683 bucketClient.MockIter("user-1/phlaredb/", []string{"user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D", "user-1/phlaredb/01FN3VCQV5X342W2ZKMQQXAZRX", "user-1/phlaredb/01FS51A7GQ1RQWV35DBVYQM4KF", "user-1/phlaredb/01FRQGQB7RWQ2TS0VWA82QTPXE"}, nil) 684 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSONWithTimeRangeAndLabels("01DTVP434PA9VFXSW2JKB3392D", 1574776800000, 1574784000000, map[string]string{"A": "B"}), nil) 685 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil) 686 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/no-compact-mark.json", "", nil) 687 bucketClient.MockGet("user-1/phlaredb/01FS51A7GQ1RQWV35DBVYQM4KF/meta.json", mockBlockMetaJSONWithTimeRangeAndLabels("01FS51A7GQ1RQWV35DBVYQM4KF", 1574776800000, 1574784000000, map[string]string{"A": "B"}), nil) 688 bucketClient.MockGet("user-1/phlaredb/01FS51A7GQ1RQWV35DBVYQM4KF/deletion-mark.json", "", nil) 689 bucketClient.MockGet("user-1/phlaredb/01FS51A7GQ1RQWV35DBVYQM4KF/no-compact-mark.json", "", nil) 690 bucketClient.MockGet("user-1/phlaredb/01FN3VCQV5X342W2ZKMQQXAZRX/meta.json", mockBlockMetaJSONWithTimeRangeAndLabels("01FN3VCQV5X342W2ZKMQQXAZRX", 1574776800000, 1574784000000, map[string]string{"C": "D"}), nil) 691 bucketClient.MockGet("user-1/phlaredb/01FN3VCQV5X342W2ZKMQQXAZRX/deletion-mark.json", "", nil) 692 bucketClient.MockGet("user-1/phlaredb/01FN3VCQV5X342W2ZKMQQXAZRX/no-compact-mark.json", "", nil) 693 bucketClient.MockGet("user-1/phlaredb/01FRQGQB7RWQ2TS0VWA82QTPXE/meta.json", mockBlockMetaJSONWithTimeRangeAndLabels("01FRQGQB7RWQ2TS0VWA82QTPXE", 1574776800000, 1574784000000, map[string]string{"C": "D"}), nil) 694 bucketClient.MockGet("user-1/phlaredb/01FRQGQB7RWQ2TS0VWA82QTPXE/deletion-mark.json", "", nil) 695 bucketClient.MockGet("user-1/phlaredb/01FRQGQB7RWQ2TS0VWA82QTPXE/no-compact-mark.json", "", nil) 696 bucketClient.MockGet("user-1/phlaredb/bucket-index.json.gz", "", nil) 697 bucketClient.MockIter("user-1/phlaredb/markers/", nil, nil) 698 bucketClient.MockUpload("user-1/phlaredb/bucket-index.json.gz", nil) 699 700 cfg := prepareConfig(t) 701 cfg.MaxCompactionTime = 500 * time.Millisecond // Enough time to start one compaction. We will make it last longer than this. 702 cfg.CompactionConcurrency = 1 703 704 c, _, tsdbPlanner, logs, _ := prepare(t, cfg, bucketClient) 705 706 // Planner is called at the beginning of each job. We make it return no work, but only after delay. 707 plannerDelay := 2 * cfg.MaxCompactionTime 708 tsdbPlanner.On("Plan", mock.Anything, mock.Anything).After(plannerDelay).Return([]*block.Meta{}, nil) 709 710 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 711 712 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 713 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 714 715 // Wait until a run has completed. Since planner takes "2*cfg.MaxCompactionTime", we wait for twice as long. 716 test.Poll(t, 2*plannerDelay, 1.0, func() interface{} { 717 return prom_testutil.ToFloat64(c.compactionRunsCompleted) 718 }) 719 720 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 721 722 // Ensure a plan has been called only once. 723 tsdbPlanner.AssertNumberOfCalls(t, "Plan", 1) 724 725 assert.Equal(t, []string{ 726 `level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`, 727 `level=info component=compactor msg="compactor is ACTIVE in the ring"`, 728 `level=info component=compactor msg="discovering users from bucket"`, 729 `level=info component=compactor msg="discovered users from bucket" users=1`, 730 `level=info component=compactor msg="starting compaction of user blocks" tenant=user-1`, 731 `level=info component=compactor tenant=user-1 msg="start sync of metas"`, 732 `level=info component=compactor tenant=user-1 msg="start of GC"`, 733 `level=debug component=compactor tenant=user-1 msg="grouper found a compactable blocks group" groupKey=0@12695595599644216241-merge--1574776800000-1574784000000 job="stage: merge, range start: 1574776800000, range end: 1574784000000, shard: , blocks: 01FN3VCQV5X342W2ZKMQQXAZRX (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z),01FRQGQB7RWQ2TS0VWA82QTPXE (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z)"`, 734 `level=debug component=compactor tenant=user-1 msg="grouper found a compactable blocks group" groupKey=0@414047632870839233-merge--1574776800000-1574784000000 job="stage: merge, range start: 1574776800000, range end: 1574784000000, shard: , blocks: 01DTVP434PA9VFXSW2JKB3392D (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z),01FS51A7GQ1RQWV35DBVYQM4KF (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z)"`, 735 `level=info component=compactor tenant=user-1 msg="start of compactions"`, 736 `level=info component=compactor tenant=user-1 msg="max compaction time reached, no more compactions will be started"`, 737 `level=info component=compactor tenant=user-1 groupKey=0@12695595599644216241-merge--1574776800000-1574784000000 msg="compaction job succeeded"`, 738 `level=info component=compactor tenant=user-1 msg="compaction iterations done"`, 739 `level=info component=compactor msg="successfully compacted user blocks" tenant=user-1`, 740 }, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n"))) 741 } 742 743 func TestMultitenantCompactor_ShouldNotCompactBlocksMarkedForDeletion(t *testing.T) { 744 t.Parallel() 745 746 cfg := prepareConfig(t) 747 cfg.DeletionDelay = 10 * time.Minute // Delete block after 10 minutes 748 749 // Mock the bucket to contain two users, each one with one block. 750 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 751 bucketClient.MockIter("", []string{"user-1"}, nil) 752 bucketClient.MockIter("user-1/phlaredb/", []string{"user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D", "user-1/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ"}, nil) 753 bucketClient.MockExists(path.Join("user-1", "phlaredb/", bucket.TenantDeletionMarkPath), false, nil) 754 755 // Block that has just been marked for deletion. It will not be deleted just yet, and it also will not be compacted. 756 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil) 757 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", mockDeletionMarkJSON("01DTVP434PA9VFXSW2JKB3392D", time.Now()), nil) 758 bucketClient.MockGet("user-1/phlaredb/markers/01DTVP434PA9VFXSW2JKB3392D-deletion-mark.json", mockDeletionMarkJSON("01DTVP434PA9VFXSW2JKB3392D", time.Now()), nil) 759 760 // This block will be deleted by cleaner. 761 bucketClient.MockGet("user-1/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", mockBlockMetaJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ"), nil) 762 bucketClient.MockGet("user-1/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", mockDeletionMarkJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ", time.Now().Add(-cfg.DeletionDelay)), nil) 763 bucketClient.MockGet("user-1/phlaredb/markers/01DTW0ZCPDDNV4BV83Q2SV4QAZ-deletion-mark.json", mockDeletionMarkJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ", time.Now().Add(-cfg.DeletionDelay)), nil) 764 765 bucketClient.MockIter("user-1/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ", []string{ 766 "user-1/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", 767 "user-1/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", 768 }, nil) 769 770 bucketClient.MockIter("user-1/phlaredb/markers/", []string{ 771 "user-1/phlaredb/markers/01DTVP434PA9VFXSW2JKB3392D-deletion-mark.json", 772 "user-1/phlaredb/markers/01DTW0ZCPDDNV4BV83Q2SV4QAZ-deletion-mark.json", 773 }, nil) 774 775 bucketClient.MockDelete("user-1/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", nil) 776 bucketClient.MockDelete("user-1/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", nil) 777 bucketClient.MockDelete("user-1/phlaredb/markers/01DTW0ZCPDDNV4BV83Q2SV4QAZ-deletion-mark.json", nil) 778 bucketClient.MockDelete("user-1/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ", nil) 779 bucketClient.MockGet("user-1/phlaredb/bucket-index.json.gz", "", nil) 780 bucketClient.MockUpload("user-1/phlaredb/bucket-index.json.gz", nil) 781 782 c, _, tsdbPlanner, logs, registry := prepare(t, cfg, bucketClient) 783 784 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 785 786 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 787 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 788 789 // Wait until a run has completed. 790 test.Poll(t, time.Second, 1.0, func() interface{} { 791 return prom_testutil.ToFloat64(c.compactionRunsCompleted) 792 }) 793 794 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 795 796 // Since both blocks are marked for deletion, none of them are going to be compacted. 797 tsdbPlanner.AssertNumberOfCalls(t, "Plan", 0) 798 799 assert.ElementsMatch(t, []string{ 800 `level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`, 801 `level=info component=compactor msg="compactor is ACTIVE in the ring"`, 802 `level=info component=compactor msg="discovering users from bucket"`, 803 `level=info component=compactor msg="discovered users from bucket" users=1`, 804 `level=info component=compactor msg="starting compaction of user blocks" tenant=user-1`, 805 `level=info component=compactor tenant=user-1 msg="start sync of metas"`, 806 `level=info component=compactor tenant=user-1 msg="start of GC"`, 807 `level=info component=compactor tenant=user-1 msg="start of compactions"`, 808 `level=info component=compactor tenant=user-1 msg="compaction iterations done"`, 809 `level=info component=compactor msg="successfully compacted user blocks" tenant=user-1`, 810 }, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n"))) 811 812 // Instead of testing for shipper metrics, we only check our metrics here. 813 // Real shipper metrics are too variable to embed into a test. 814 testedMetrics := []string{ 815 "pyroscope_compactor_runs_started_total", "pyroscope_compactor_runs_completed_total", "pyroscope_compactor_runs_failed_total", 816 "pyroscope_compactor_blocks_cleaned_total", "pyroscope_compactor_block_cleanup_failures_total", "pyroscope_compactor_blocks_marked_for_deletion_total", 817 "pyroscope_compactor_block_cleanup_started_total", "pyroscope_compactor_block_cleanup_completed_total", "pyroscope_compactor_block_cleanup_failed_total", 818 } 819 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 820 # TYPE pyroscope_compactor_runs_started_total counter 821 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 822 pyroscope_compactor_runs_started_total 1 823 824 # TYPE pyroscope_compactor_runs_completed_total counter 825 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 826 pyroscope_compactor_runs_completed_total 1 827 828 # TYPE pyroscope_compactor_runs_failed_total counter 829 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 830 pyroscope_compactor_runs_failed_total{reason="error"} 0 831 pyroscope_compactor_runs_failed_total{reason="shutdown"} 0 832 833 # TYPE pyroscope_compactor_block_cleanup_failures_total counter 834 # HELP pyroscope_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted. 835 pyroscope_compactor_block_cleanup_failures_total 0 836 837 # HELP pyroscope_compactor_blocks_cleaned_total Total number of blocks deleted. 838 # TYPE pyroscope_compactor_blocks_cleaned_total counter 839 pyroscope_compactor_blocks_cleaned_total 1 840 841 # HELP pyroscope_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor. 842 # TYPE pyroscope_compactor_blocks_marked_for_deletion_total counter 843 pyroscope_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0 844 pyroscope_compactor_blocks_marked_for_deletion_total{reason="partial"} 0 845 pyroscope_compactor_blocks_marked_for_deletion_total{reason="retention"} 0 846 847 # TYPE pyroscope_compactor_block_cleanup_started_total counter 848 # HELP pyroscope_compactor_block_cleanup_started_total Total number of blocks cleanup runs started. 849 pyroscope_compactor_block_cleanup_started_total 1 850 851 # TYPE pyroscope_compactor_block_cleanup_completed_total counter 852 # HELP pyroscope_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed. 853 pyroscope_compactor_block_cleanup_completed_total 1 854 855 # TYPE pyroscope_compactor_block_cleanup_failed_total counter 856 # HELP pyroscope_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed. 857 pyroscope_compactor_block_cleanup_failed_total 0 858 `), testedMetrics...)) 859 } 860 861 func TestMultitenantCompactor_ShouldNotCompactBlocksMarkedForNoCompaction(t *testing.T) { 862 t.Parallel() 863 864 cfg := prepareConfig(t) 865 cfg.DeletionDelay = 10 * time.Minute // Delete block after 10 minutes 866 867 // Mock the bucket to contain one user with a block marked for no-compaction. 868 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 869 bucketClient.MockIter("", []string{"user-1"}, nil) 870 bucketClient.MockIter("user-1/phlaredb/", []string{"user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D"}, nil) 871 bucketClient.MockExists(path.Join("user-1", "phlaredb/", bucket.TenantDeletionMarkPath), false, nil) 872 873 // Block that is marked for no compaction. It will be ignored. 874 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil) 875 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil) 876 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/no-compact-mark.json", `{"id":"01DTVP434PA9VFXSW2JKB3392D","version":1,"details":"details","no_compact_time":1637757932,"reason":"reason"}`, nil) 877 878 bucketClient.MockIter("user-1/phlaredb/markers/", []string{"user-1/markers/01DTVP434PA9VFXSW2JKB3392D-no-compact-mark.json"}, nil) 879 880 bucketClient.MockGet("user-1/phlaredb/bucket-index.json.gz", "", nil) 881 bucketClient.MockUpload("user-1/phlaredb/bucket-index.json.gz", nil) 882 883 c, _, tsdbPlanner, logs, _ := prepare(t, cfg, bucketClient) 884 885 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 886 887 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 888 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 889 890 // Wait until a run has completed. 891 test.Poll(t, time.Second, 1.0, func() interface{} { 892 return prom_testutil.ToFloat64(c.compactionRunsCompleted) 893 }) 894 895 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 896 897 // Since block is not compacted, there will be no planning done. 898 tsdbPlanner.AssertNumberOfCalls(t, "Plan", 0) 899 900 assert.ElementsMatch(t, []string{ 901 `level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`, 902 `level=info component=compactor msg="compactor is ACTIVE in the ring"`, 903 `level=info component=compactor msg="discovering users from bucket"`, 904 `level=info component=compactor msg="discovered users from bucket" users=1`, 905 `level=info component=compactor msg="starting compaction of user blocks" tenant=user-1`, 906 `level=info component=compactor tenant=user-1 msg="start sync of metas"`, 907 `level=info component=compactor tenant=user-1 msg="start of GC"`, 908 `level=info component=compactor tenant=user-1 msg="start of compactions"`, 909 `level=info component=compactor tenant=user-1 msg="compaction iterations done"`, 910 `level=info component=compactor msg="successfully compacted user blocks" tenant=user-1`, 911 }, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n"))) 912 } 913 914 func TestMultitenantCompactor_ShouldNotCompactBlocksForUsersMarkedForDeletion(t *testing.T) { 915 t.Parallel() 916 917 cfg := prepareConfig(t) 918 cfg.DeletionDelay = 10 * time.Minute // Delete block after 10 minutes 919 cfg.TenantCleanupDelay = 10 * time.Minute // To make sure it's not 0. 920 921 // Mock the bucket to contain two users, each one with one block. 922 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 923 bucketClient.MockIter("", []string{"user-1"}, nil) 924 bucketClient.MockIter("user-1/phlaredb/", []string{"user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D"}, nil) 925 bucketClient.MockGet(path.Join("user-1", "phlaredb/", bucket.TenantDeletionMarkPath), `{"deletion_time": 1}`, nil) 926 bucketClient.MockUpload(path.Join("user-1", "phlaredb/", bucket.TenantDeletionMarkPath), nil) 927 928 bucketClient.MockIter("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D", []string{"user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", "user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/index"}, nil) 929 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil) 930 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/index", "some index content", nil) 931 bucketClient.MockExists("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", false, nil) 932 bucketClient.MockExists("user-1/phlaredb/markers/01DTVP434PA9VFXSW2JKB3392D-deletion-mark.json", false, nil) 933 934 bucketClient.MockDelete("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", nil) 935 bucketClient.MockDelete("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/index", nil) 936 bucketClient.MockDelete("user-1/phlaredb/bucket-index.json.gz", nil) 937 938 c, _, tsdbPlanner, logs, registry := prepare(t, cfg, bucketClient) 939 940 // Mock the planner as if there's no compaction to do, 941 // in order to simplify tests (all in all, we just want to 942 // test our logic and not TSDB compactor which we expect to 943 // be already tested). 944 tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*block.Meta{}, nil) 945 946 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 947 948 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 949 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 950 951 // Wait until a run has completed. 952 test.Poll(t, time.Second, 1.0, func() interface{} { 953 return prom_testutil.ToFloat64(c.compactionRunsCompleted) 954 }) 955 956 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 957 958 // No user is compacted, single user we have is marked for deletion. 959 tsdbPlanner.AssertNumberOfCalls(t, "Plan", 0) 960 961 assert.ElementsMatch(t, []string{ 962 `level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`, 963 `level=info component=compactor msg="compactor is ACTIVE in the ring"`, 964 `level=info component=compactor msg="discovering users from bucket"`, 965 `level=info component=compactor msg="discovered users from bucket" users=1`, 966 `level=debug component=compactor msg="skipping user because it is marked for deletion" tenant=user-1`, 967 }, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n"))) 968 969 // Instead of testing for shipper metrics, we only check our metrics here. 970 // Real shipper metrics are too variable to embed into a test. 971 testedMetrics := []string{ 972 "pyroscope_compactor_runs_started_total", "pyroscope_compactor_runs_completed_total", "pyroscope_compactor_runs_failed_total", 973 "pyroscope_compactor_blocks_cleaned_total", "pyroscope_compactor_block_cleanup_failures_total", "pyroscope_compactor_blocks_marked_for_deletion_total", 974 "pyroscope_compactor_block_cleanup_started_total", "pyroscope_compactor_block_cleanup_completed_total", "pyroscope_compactor_block_cleanup_failed_total", 975 "pyroscope_bucket_blocks_count", "pyroscope_bucket_blocks_marked_for_deletion_count", "pyroscope_bucket_index_last_successful_update_timestamp_seconds", 976 } 977 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 978 # TYPE pyroscope_compactor_runs_started_total counter 979 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 980 pyroscope_compactor_runs_started_total 1 981 982 # TYPE pyroscope_compactor_runs_completed_total counter 983 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 984 pyroscope_compactor_runs_completed_total 1 985 986 # TYPE pyroscope_compactor_runs_failed_total counter 987 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 988 pyroscope_compactor_runs_failed_total{reason="error"} 0 989 pyroscope_compactor_runs_failed_total{reason="shutdown"} 0 990 991 # TYPE pyroscope_compactor_block_cleanup_failures_total counter 992 # HELP pyroscope_compactor_block_cleanup_failures_total Total number of blocks failed to be deleted. 993 pyroscope_compactor_block_cleanup_failures_total 0 994 995 # HELP pyroscope_compactor_blocks_cleaned_total Total number of blocks deleted. 996 # TYPE pyroscope_compactor_blocks_cleaned_total counter 997 pyroscope_compactor_blocks_cleaned_total 1 998 999 # HELP pyroscope_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor. 1000 # TYPE pyroscope_compactor_blocks_marked_for_deletion_total counter 1001 pyroscope_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0 1002 pyroscope_compactor_blocks_marked_for_deletion_total{reason="partial"} 0 1003 pyroscope_compactor_blocks_marked_for_deletion_total{reason="retention"} 0 1004 1005 # TYPE pyroscope_compactor_block_cleanup_started_total counter 1006 # HELP pyroscope_compactor_block_cleanup_started_total Total number of blocks cleanup runs started. 1007 pyroscope_compactor_block_cleanup_started_total 1 1008 1009 # TYPE pyroscope_compactor_block_cleanup_completed_total counter 1010 # HELP pyroscope_compactor_block_cleanup_completed_total Total number of blocks cleanup runs successfully completed. 1011 pyroscope_compactor_block_cleanup_completed_total 1 1012 1013 # TYPE pyroscope_compactor_block_cleanup_failed_total counter 1014 # HELP pyroscope_compactor_block_cleanup_failed_total Total number of blocks cleanup runs failed. 1015 pyroscope_compactor_block_cleanup_failed_total 0 1016 `), testedMetrics...)) 1017 } 1018 1019 func TestMultitenantCompactor_ShouldCompactAllUsersOnShardingEnabledButOnlyOneInstanceRunning(t *testing.T) { 1020 t.Parallel() 1021 1022 // Mock the bucket to contain two users, each one with one block. 1023 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 1024 bucketClient.MockIter("", []string{"user-1", "user-2"}, nil) 1025 bucketClient.MockExists(path.Join("user-1", "phlaredb", bucket.TenantDeletionMarkPath), false, nil) 1026 bucketClient.MockExists(path.Join("user-2", "phlaredb", bucket.TenantDeletionMarkPath), false, nil) 1027 bucketClient.MockIter("user-1/phlaredb/", []string{"user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D", "user-1/phlaredb/01FSTQ95C8FS0ZAGTQS2EF1NEG"}, nil) 1028 bucketClient.MockIter("user-2/phlaredb/", []string{"user-2/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ", "user-2/phlaredb/01FSV54G6QFQH1G9QE93G3B9TB"}, nil) 1029 bucketClient.MockIter("user-1/phlaredb/markers/", nil, nil) 1030 bucketClient.MockIter("user-2/phlaredb/markers/", nil, nil) 1031 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil) 1032 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil) 1033 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JKB3392D/no-compact-mark.json", "", nil) 1034 bucketClient.MockGet("user-1/phlaredb/01FSTQ95C8FS0ZAGTQS2EF1NEG/meta.json", mockBlockMetaJSON("01FSTQ95C8FS0ZAGTQS2EF1NEG"), nil) 1035 bucketClient.MockGet("user-1/phlaredb/01FSTQ95C8FS0ZAGTQS2EF1NEG/deletion-mark.json", "", nil) 1036 bucketClient.MockGet("user-1/phlaredb/01FSTQ95C8FS0ZAGTQS2EF1NEG/no-compact-mark.json", "", nil) 1037 bucketClient.MockGet("user-2/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/meta.json", mockBlockMetaJSON("01DTW0ZCPDDNV4BV83Q2SV4QAZ"), nil) 1038 bucketClient.MockGet("user-2/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/deletion-mark.json", "", nil) 1039 bucketClient.MockGet("user-2/phlaredb/01DTW0ZCPDDNV4BV83Q2SV4QAZ/no-compact-mark.json", "", nil) 1040 bucketClient.MockGet("user-2/phlaredb/01FSV54G6QFQH1G9QE93G3B9TB/meta.json", mockBlockMetaJSON("01FSV54G6QFQH1G9QE93G3B9TB"), nil) 1041 bucketClient.MockGet("user-2/phlaredb/01FSV54G6QFQH1G9QE93G3B9TB/deletion-mark.json", "", nil) 1042 bucketClient.MockGet("user-2/phlaredb/01FSV54G6QFQH1G9QE93G3B9TB/no-compact-mark.json", "", nil) 1043 bucketClient.MockGet("user-1/phlaredb/bucket-index.json.gz", "", nil) 1044 bucketClient.MockGet("user-2/phlaredb/bucket-index.json.gz", "", nil) 1045 bucketClient.MockUpload("user-1/phlaredb/bucket-index.json.gz", nil) 1046 bucketClient.MockUpload("user-2/phlaredb/bucket-index.json.gz", nil) 1047 1048 ringStore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil) 1049 t.Cleanup(func() { assert.NoError(t, closer.Close()) }) 1050 1051 cfg := prepareConfig(t) 1052 cfg.ShardingRing.Common.InstanceID = instanceID 1053 cfg.ShardingRing.Common.InstanceAddr = addr 1054 cfg.ShardingRing.Common.KVStore.Mock = ringStore 1055 c, _, tsdbPlanner, logs, registry := prepare(t, cfg, bucketClient) 1056 1057 // Mock the planner as if there's no compaction to do, 1058 // in order to simplify tests (all in all, we just want to 1059 // test our logic and not TSDB compactor which we expect to 1060 // be already tested). 1061 tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*block.Meta{}, nil) 1062 1063 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 1064 1065 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 1066 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 1067 1068 // Wait until a run has completed. 1069 test.Poll(t, 5*time.Second, 1.0, func() interface{} { 1070 return prom_testutil.ToFloat64(c.compactionRunsCompleted) 1071 }) 1072 1073 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 1074 1075 // Ensure a plan has been executed for the blocks of each user. 1076 tsdbPlanner.AssertNumberOfCalls(t, "Plan", 2) 1077 1078 assert.ElementsMatch(t, []string{ 1079 `level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`, 1080 `level=info component=compactor msg="compactor is ACTIVE in the ring"`, 1081 `level=info component=compactor msg="discovering users from bucket"`, 1082 `level=info component=compactor msg="discovered users from bucket" users=2`, 1083 `level=info component=compactor msg="starting compaction of user blocks" tenant=user-1`, 1084 `level=info component=compactor tenant=user-1 msg="start sync of metas"`, 1085 `level=info component=compactor tenant=user-1 msg="start of GC"`, 1086 `level=debug component=compactor tenant=user-1 msg="grouper found a compactable blocks group" groupKey=0@17241709254077376921-merge--1574776800000-1574784000000 job="stage: merge, range start: 1574776800000, range end: 1574784000000, shard: , blocks: 01DTVP434PA9VFXSW2JKB3392D (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z),01FSTQ95C8FS0ZAGTQS2EF1NEG (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z)"`, 1087 `level=info component=compactor tenant=user-1 msg="start of compactions"`, 1088 `level=info component=compactor tenant=user-1 groupKey=0@17241709254077376921-merge--1574776800000-1574784000000 msg="compaction job succeeded"`, 1089 `level=info component=compactor tenant=user-1 msg="compaction iterations done"`, 1090 `level=info component=compactor msg="successfully compacted user blocks" tenant=user-1`, 1091 `level=info component=compactor msg="starting compaction of user blocks" tenant=user-2`, 1092 `level=info component=compactor tenant=user-2 msg="start sync of metas"`, 1093 `level=info component=compactor tenant=user-2 msg="start of GC"`, 1094 `level=debug component=compactor tenant=user-2 msg="grouper found a compactable blocks group" groupKey=0@17241709254077376921-merge--1574776800000-1574784000000 job="stage: merge, range start: 1574776800000, range end: 1574784000000, shard: , blocks: 01DTW0ZCPDDNV4BV83Q2SV4QAZ (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z),01FSV54G6QFQH1G9QE93G3B9TB (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z)"`, 1095 `level=info component=compactor tenant=user-2 msg="start of compactions"`, 1096 `level=info component=compactor tenant=user-2 groupKey=0@17241709254077376921-merge--1574776800000-1574784000000 msg="compaction job succeeded"`, 1097 `level=info component=compactor tenant=user-2 msg="compaction iterations done"`, 1098 `level=info component=compactor msg="successfully compacted user blocks" tenant=user-2`, 1099 }, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n"))) 1100 1101 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 1102 # TYPE pyroscope_compactor_runs_started_total counter 1103 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 1104 pyroscope_compactor_runs_started_total 1 1105 1106 # TYPE pyroscope_compactor_runs_completed_total counter 1107 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 1108 pyroscope_compactor_runs_completed_total 1 1109 1110 # TYPE pyroscope_compactor_runs_failed_total counter 1111 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 1112 pyroscope_compactor_runs_failed_total{reason="error"} 0 1113 pyroscope_compactor_runs_failed_total{reason="shutdown"} 0 1114 1115 # HELP pyroscope_compactor_group_compaction_runs_completed_total Total number of group completed compaction runs. This also includes compactor group runs that resulted with no compaction. 1116 # TYPE pyroscope_compactor_group_compaction_runs_completed_total counter 1117 pyroscope_compactor_group_compaction_runs_completed_total 2 1118 1119 # HELP pyroscope_compactor_group_compaction_runs_started_total Total number of group compaction attempts. 1120 # TYPE pyroscope_compactor_group_compaction_runs_started_total counter 1121 pyroscope_compactor_group_compaction_runs_started_total 2 1122 1123 # HELP pyroscope_compactor_group_compactions_failures_total Total number of failed group compactions. 1124 # TYPE pyroscope_compactor_group_compactions_failures_total counter 1125 pyroscope_compactor_group_compactions_failures_total 0 1126 1127 # HELP pyroscope_compactor_group_compactions_total Total number of group compaction attempts that resulted in new block(s). 1128 # TYPE pyroscope_compactor_group_compactions_total counter 1129 pyroscope_compactor_group_compactions_total 0 1130 1131 # HELP pyroscope_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor. 1132 # TYPE pyroscope_compactor_blocks_marked_for_deletion_total counter 1133 pyroscope_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0 1134 pyroscope_compactor_blocks_marked_for_deletion_total{reason="partial"} 0 1135 pyroscope_compactor_blocks_marked_for_deletion_total{reason="retention"} 0 1136 `), 1137 "pyroscope_compactor_runs_started_total", 1138 "pyroscope_compactor_runs_completed_total", 1139 "pyroscope_compactor_runs_failed_total", 1140 "pyroscope_compactor_group_compaction_runs_completed_total", 1141 "pyroscope_compactor_group_compaction_runs_started_total", 1142 "pyroscope_compactor_group_compactions_failures_total", 1143 "pyroscope_compactor_group_compactions_total", 1144 "pyroscope_compactor_blocks_marked_for_deletion_total", 1145 )) 1146 } 1147 1148 func TestMultitenantCompactor_ShouldCompactOnlyUsersOwnedByTheInstanceOnShardingEnabledAndMultipleInstancesRunning(t *testing.T) { 1149 t.Parallel() 1150 1151 numUsers := 100 1152 1153 // Setup user IDs 1154 userIDs := make([]string, 0, numUsers) 1155 for i := 1; i <= numUsers; i++ { 1156 userIDs = append(userIDs, fmt.Sprintf("user-%d", i)) 1157 } 1158 1159 // Mock the bucket to contain all users, each one with one block. 1160 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 1161 bucketClient.MockIter("", userIDs, nil) 1162 for _, userID := range userIDs { 1163 bucketClient.MockIter(userID+"/phlaredb/", []string{userID + "/phlaredb/01DTVP434PA9VFXSW2JKB3392D"}, nil) 1164 bucketClient.MockIter(userID+"/phlaredb/markers/", nil, nil) 1165 bucketClient.MockExists(path.Join(userID, "phlaredb/", bucket.TenantDeletionMarkPath), false, nil) 1166 bucketClient.MockGet(userID+"/phlaredb/01DTVP434PA9VFXSW2JKB3392D/meta.json", mockBlockMetaJSON("01DTVP434PA9VFXSW2JKB3392D"), nil) 1167 bucketClient.MockGet(userID+"/phlaredb/01DTVP434PA9VFXSW2JKB3392D/deletion-mark.json", "", nil) 1168 bucketClient.MockGet(userID+"/phlaredb/01DTVP434PA9VFXSW2JKB3392D/no-compact-mark.json", "", nil) 1169 bucketClient.MockGet(userID+"/phlaredb/bucket-index.json.gz", "", nil) 1170 bucketClient.MockUpload(userID+"/phlaredb/bucket-index.json.gz", nil) 1171 } 1172 1173 // Create a shared KV Store 1174 kvstore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil) 1175 t.Cleanup(func() { assert.NoError(t, closer.Close()) }) 1176 1177 // Create two compactors 1178 var compactors []*MultitenantCompactor 1179 var logs []*concurrency.SyncBuffer 1180 1181 for i := 1; i <= 2; i++ { 1182 cfg := prepareConfig(t) 1183 cfg.ShardingRing.Common.InstanceID = fmt.Sprintf("compactor-%d", i) 1184 cfg.ShardingRing.Common.InstanceAddr = fmt.Sprintf("127.0.0.%d", i) 1185 cfg.ShardingRing.WaitStabilityMinDuration = 3 * time.Second 1186 cfg.ShardingRing.WaitStabilityMaxDuration = 10 * time.Second 1187 cfg.ShardingRing.Common.KVStore.Mock = kvstore 1188 1189 var limits validation.Limits 1190 flagext.DefaultValues(&limits) 1191 limits.CompactorTenantShardSize = 1 1192 overrides, err := validation.NewOverrides(limits, nil) 1193 require.NoError(t, err) 1194 1195 c, _, tsdbPlanner, l, _ := prepareWithConfigProvider(t, cfg, bucketClient, overrides) 1196 defer services.StopAndAwaitTerminated(context.Background(), c) //nolint:errcheck 1197 1198 compactors = append(compactors, c) 1199 logs = append(logs, l) 1200 1201 // Mock the planner as if there's no compaction to do, 1202 // in order to simplify tests (all in all, we just want to 1203 // test our logic and not TSDB compactor which we expect to 1204 // be already tested). 1205 tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*block.Meta{}, nil) 1206 } 1207 1208 // Start all compactors 1209 for _, c := range compactors { 1210 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 1211 } 1212 1213 // Wait until a run has been completed on each compactor 1214 test.Poll(t, 30*time.Second, true, func() interface{} { 1215 for _, c := range compactors { 1216 if prom_testutil.ToFloat64(c.compactionRunsCompleted) < 1.0 { 1217 return false 1218 } 1219 } 1220 return true 1221 }) 1222 1223 // Ensure that each user has been compacted by the correct instance 1224 for _, userID := range userIDs { 1225 _, l, err := findCompactorByUserID(compactors, logs, userID) 1226 require.NoError(t, err) 1227 assert.Contains(t, l.String(), fmt.Sprintf(`level=info component=compactor msg="successfully compacted user blocks" tenant=%s`, userID)) 1228 } 1229 } 1230 1231 func TestMultitenantCompactor_ShouldSkipCompactionForJobsNoMoreOwnedAfterPlanning(t *testing.T) { 1232 t.Parallel() 1233 1234 // Mock the bucket to contain one user with two non-overlapping blocks (we expect two compaction jobs to be scheduled 1235 // for the splitting stage). 1236 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 1237 bucketClient.MockIter("", []string{"user-1"}, nil) 1238 bucketClient.MockExists(path.Join("user-1", "phlaredb", bucket.TenantDeletionMarkPath), false, nil) 1239 bucketClient.MockIter("user-1/phlaredb/", []string{"user-1/phlaredb/01DTVP434PA9VFXSW2JK000001", "user-1/phlaredb/01DTVP434PA9VFXSW2JK000002"}, nil) 1240 bucketClient.MockIter("user-1/phlaredb/markers/", nil, nil) 1241 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JK000001/meta.json", mockBlockMetaJSONWithTimeRange("01DTVP434PA9VFXSW2JK000001", 1574776800000, 1574784000000), nil) 1242 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JK000001/deletion-mark.json", "", nil) 1243 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JK000001/no-compact-mark.json", "", nil) 1244 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JK000002/meta.json", mockBlockMetaJSONWithTimeRange("01DTVP434PA9VFXSW2JK000002", 1574863200000, 1574870400000), nil) 1245 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JK000002/deletion-mark.json", "", nil) 1246 bucketClient.MockGet("user-1/phlaredb/01DTVP434PA9VFXSW2JK000002/no-compact-mark.json", "", nil) 1247 bucketClient.MockGet("user-1/phlaredb/bucket-index.json.gz", "", nil) 1248 bucketClient.MockUpload("user-1/phlaredb/bucket-index.json.gz", nil) 1249 1250 ringStore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil) 1251 t.Cleanup(func() { assert.NoError(t, closer.Close()) }) 1252 1253 cfg := prepareConfig(t) 1254 cfg.CompactionConcurrency = 1 1255 cfg.ShardingRing.Common.InstanceID = instanceID 1256 cfg.ShardingRing.Common.InstanceAddr = addr 1257 cfg.ShardingRing.Common.KVStore.Mock = ringStore 1258 1259 limits := newMockConfigProvider() 1260 limits.splitAndMergeShards = map[string]int{"user-1": 4} 1261 limits.splitGroups = map[string]int{"user-1": 4} 1262 1263 c, _, tsdbPlanner, logs, registry := prepareWithConfigProvider(t, cfg, bucketClient, limits) 1264 1265 // Mock the planner as if there's no compaction to do, in order to simplify tests. 1266 tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*block.Meta{}, nil).Run(func(args mock.Arguments) { 1267 // As soon as the first Plan() is called by the compactor, we do switch 1268 // the instance to LEAVING state. This way, after this call, we expect the compactor 1269 // to skip next compaction job because not owned anymore by this instance. 1270 require.NoError(t, c.ringLifecycler.ChangeState(context.Background(), ring.LEAVING)) 1271 1272 // Wait until the compactor ring client has updated. 1273 test.Poll(t, time.Second, 0, func() interface{} { 1274 set, _ := c.ring.GetAllHealthy(RingOp) 1275 return len(set.Instances) 1276 }) 1277 }) 1278 1279 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 1280 1281 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 1282 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 1283 1284 // Wait until a run has completed. 1285 test.Poll(t, 5*time.Second, 1.0, func() interface{} { 1286 return prom_testutil.ToFloat64(c.compactionRunsCompleted) 1287 }) 1288 1289 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 1290 1291 // We expect only 1 compaction job has been expected, while the 2nd has been skipped. 1292 tsdbPlanner.AssertNumberOfCalls(t, "Plan", 1) 1293 1294 assert.ElementsMatch(t, []string{ 1295 `level=info component=compactor msg="waiting until compactor is ACTIVE in the ring"`, 1296 `level=info component=compactor msg="compactor is ACTIVE in the ring"`, 1297 `level=info component=compactor msg="discovering users from bucket"`, 1298 `level=info component=compactor msg="discovered users from bucket" users=1`, 1299 `level=info component=compactor msg="starting compaction of user blocks" tenant=user-1`, 1300 `level=info component=compactor tenant=user-1 msg="start sync of metas"`, 1301 `level=info component=compactor tenant=user-1 msg="start of GC"`, 1302 `level=info component=compactor tenant=user-1 msg="start of compactions"`, 1303 `level=debug component=compactor tenant=user-1 msg="grouper found a compactable blocks group" groupKey=0@17241709254077376921-split-4_of_4-1574776800000-1574784000000 job="stage: split, range start: 1574776800000, range end: 1574784000000, shard: 4_of_4, blocks: 01DTVP434PA9VFXSW2JK000001 (min time: 2019-11-26T14:00:00Z, max time: 2019-11-26T16:00:00Z)"`, 1304 `level=debug component=compactor tenant=user-1 msg="grouper found a compactable blocks group" groupKey=0@17241709254077376921-split-1_of_4-1574863200000-1574870400000 job="stage: split, range start: 1574863200000, range end: 1574870400000, shard: 1_of_4, blocks: 01DTVP434PA9VFXSW2JK000002 (min time: 2019-11-27T14:00:00Z, max time: 2019-11-27T16:00:00Z)"`, 1305 // The ownership check is failing because, to keep this test simple, we've just switched 1306 // the instance state to LEAVING and there are no other instances in the ring. 1307 `level=info component=compactor tenant=user-1 groupKey=0@17241709254077376921-split-4_of_4-1574776800000-1574784000000 msg="compaction job succeeded"`, 1308 `level=info component=compactor tenant=user-1 msg="skipped compaction because unable to check whether the job is owned by the compactor instance" groupKey=0@17241709254077376921-split-1_of_4-1574863200000-1574870400000 err="at least 1 live replicas required, could only find 0 - unhealthy instances: 1.2.3.4:0"`, 1309 `level=info component=compactor tenant=user-1 msg="compaction iterations done"`, 1310 `level=info component=compactor msg="successfully compacted user blocks" tenant=user-1`, 1311 }, removeIgnoredLogs(strings.Split(strings.TrimSpace(logs.String()), "\n"))) 1312 1313 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 1314 # TYPE pyroscope_compactor_runs_started_total counter 1315 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 1316 pyroscope_compactor_runs_started_total 1 1317 1318 # TYPE pyroscope_compactor_runs_completed_total counter 1319 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 1320 pyroscope_compactor_runs_completed_total 1 1321 1322 # TYPE pyroscope_compactor_runs_failed_total counter 1323 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 1324 pyroscope_compactor_runs_failed_total{reason="error"} 0 1325 pyroscope_compactor_runs_failed_total{reason="shutdown"} 0 1326 1327 # HELP pyroscope_compactor_group_compaction_runs_completed_total Total number of group completed compaction runs. This also includes compactor group runs that resulted with no compaction. 1328 # TYPE pyroscope_compactor_group_compaction_runs_completed_total counter 1329 pyroscope_compactor_group_compaction_runs_completed_total 1 1330 1331 # HELP pyroscope_compactor_group_compaction_runs_started_total Total number of group compaction attempts. 1332 # TYPE pyroscope_compactor_group_compaction_runs_started_total counter 1333 pyroscope_compactor_group_compaction_runs_started_total 1 1334 1335 # HELP pyroscope_compactor_group_compactions_failures_total Total number of failed group compactions. 1336 # TYPE pyroscope_compactor_group_compactions_failures_total counter 1337 pyroscope_compactor_group_compactions_failures_total 0 1338 1339 # HELP pyroscope_compactor_group_compactions_total Total number of group compaction attempts that resulted in new block(s). 1340 # TYPE pyroscope_compactor_group_compactions_total counter 1341 pyroscope_compactor_group_compactions_total 0 1342 1343 # HELP pyroscope_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor. 1344 # TYPE pyroscope_compactor_blocks_marked_for_deletion_total counter 1345 pyroscope_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0 1346 pyroscope_compactor_blocks_marked_for_deletion_total{reason="partial"} 0 1347 pyroscope_compactor_blocks_marked_for_deletion_total{reason="retention"} 0 1348 `), 1349 "pyroscope_compactor_runs_started_total", 1350 "pyroscope_compactor_runs_completed_total", 1351 "pyroscope_compactor_runs_failed_total", 1352 "pyroscope_compactor_group_compaction_runs_completed_total", 1353 "pyroscope_compactor_group_compaction_runs_started_total", 1354 "pyroscope_compactor_group_compactions_failures_total", 1355 "pyroscope_compactor_group_compactions_total", 1356 "pyroscope_compactor_blocks_marked_for_deletion_total", 1357 )) 1358 } 1359 1360 func TestMultitenantCompactor_ShouldSkipCompactionForJobsWithFirstLevelCompactionBlocksAndWaitPeriodNotElapsed(t *testing.T) { 1361 t.Parallel() 1362 1363 storageDir := t.TempDir() 1364 bucketClient, err := filesystem.NewBucket(storageDir) 1365 require.NoError(t, err) 1366 user1Meta1 := createDBBlock(t, bucketClient, "user-1", 0, (2 * time.Hour).Milliseconds(), 10, nil) 1367 user1Meta2 := createDBBlock(t, bucketClient, "user-1", 0, (2 * time.Hour).Milliseconds(), 10, nil) 1368 user2Meta1 := createDBBlock(t, bucketClient, "user-2", 0, (2 * time.Hour).Milliseconds(), 10, nil) 1369 user2Meta2 := createDBBlock(t, bucketClient, "user-2", 0, (2 * time.Hour).Milliseconds(), 10, nil) 1370 1371 // Mock the last modified timestamp returned for each of the block's meta.json. 1372 const waitPeriod = 10 * time.Minute 1373 mockClient := &bucketWithMockedAttributes{ 1374 Bucket: bucketClient, 1375 customAttributes: map[string]objstore.ObjectAttributes{ 1376 path.Join("user-1", "phlaredb", user1Meta1.String(), block.MetaFilename): {LastModified: time.Now().Add(-20 * time.Minute)}, 1377 path.Join("user-1", "phlaredb", user1Meta2.String(), block.MetaFilename): {LastModified: time.Now().Add(-20 * time.Minute)}, 1378 path.Join("user-2", "phlaredb", user2Meta1.String(), block.MetaFilename): {LastModified: time.Now().Add(-20 * time.Minute)}, 1379 path.Join("user-2", "phlaredb", user2Meta2.String(), block.MetaFilename): {LastModified: time.Now().Add(-5 * time.Minute)}, 1380 }, 1381 } 1382 1383 cfg := prepareConfig(t) 1384 cfg.CompactionWaitPeriod = waitPeriod 1385 c, _, tsdbPlanner, logs, registry := prepare(t, cfg, mockClient) 1386 1387 // Mock the planner as if there's no compaction to do, in order to simplify tests. 1388 tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*block.Meta{}, nil) 1389 1390 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 1391 1392 // Compactor doesn't wait for blocks cleaner to finish, but our test checks for cleaner metrics. 1393 require.NoError(t, c.blocksCleaner.AwaitRunning(context.Background())) 1394 1395 // Wait until a run has completed. 1396 test.Poll(t, 5*time.Second, 1.0, func() interface{} { 1397 return prom_testutil.ToFloat64(c.compactionRunsCompleted) 1398 }) 1399 1400 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 1401 1402 // We expect only 1 compaction job has been expected, while the 2nd has been skipped. 1403 tsdbPlanner.AssertNumberOfCalls(t, "Plan", 1) 1404 1405 // Ensure the skipped compaction job is the expected one. 1406 assert.Contains(t, strings.Split(strings.TrimSpace(logs.String()), "\n"), 1407 fmt.Sprintf(`level=info component=compactor tenant=user-2 msg="skipping compaction job because blocks in this job were uploaded too recently (within wait period)" groupKey=0@17241709254077376921-merge--0-7200000 waitPeriodNotElapsedFor="%s (min time: 1970-01-01T00:00:00Z, max time: 1970-01-01T02:00:00Z)"`, user2Meta2.String())) 1408 1409 assert.NoError(t, prom_testutil.GatherAndCompare(registry, strings.NewReader(` 1410 # TYPE pyroscope_compactor_runs_started_total counter 1411 # HELP pyroscope_compactor_runs_started_total Total number of compaction runs started. 1412 pyroscope_compactor_runs_started_total 1 1413 1414 # TYPE pyroscope_compactor_runs_completed_total counter 1415 # HELP pyroscope_compactor_runs_completed_total Total number of compaction runs successfully completed. 1416 pyroscope_compactor_runs_completed_total 1 1417 1418 # TYPE pyroscope_compactor_runs_failed_total counter 1419 # HELP pyroscope_compactor_runs_failed_total Total number of compaction runs failed. 1420 pyroscope_compactor_runs_failed_total{reason="error"} 0 1421 pyroscope_compactor_runs_failed_total{reason="shutdown"} 0 1422 1423 # HELP pyroscope_compactor_group_compaction_runs_completed_total Total number of group completed compaction runs. This also includes compactor group runs that resulted with no compaction. 1424 # TYPE pyroscope_compactor_group_compaction_runs_completed_total counter 1425 pyroscope_compactor_group_compaction_runs_completed_total 1 1426 1427 # HELP pyroscope_compactor_group_compaction_runs_started_total Total number of group compaction attempts. 1428 # TYPE pyroscope_compactor_group_compaction_runs_started_total counter 1429 pyroscope_compactor_group_compaction_runs_started_total 1 1430 1431 # HELP pyroscope_compactor_group_compactions_failures_total Total number of failed group compactions. 1432 # TYPE pyroscope_compactor_group_compactions_failures_total counter 1433 pyroscope_compactor_group_compactions_failures_total 0 1434 1435 # HELP pyroscope_compactor_group_compactions_total Total number of group compaction attempts that resulted in new block(s). 1436 # TYPE pyroscope_compactor_group_compactions_total counter 1437 pyroscope_compactor_group_compactions_total 0 1438 1439 # HELP pyroscope_compactor_blocks_marked_for_deletion_total Total number of blocks marked for deletion in compactor. 1440 # TYPE pyroscope_compactor_blocks_marked_for_deletion_total counter 1441 pyroscope_compactor_blocks_marked_for_deletion_total{reason="compaction"} 0 1442 pyroscope_compactor_blocks_marked_for_deletion_total{reason="partial"} 0 1443 pyroscope_compactor_blocks_marked_for_deletion_total{reason="retention"} 0 1444 `), 1445 "pyroscope_compactor_runs_started_total", 1446 "pyroscope_compactor_runs_completed_total", 1447 "pyroscope_compactor_runs_failed_total", 1448 "pyroscope_compactor_group_compaction_runs_completed_total", 1449 "pyroscope_compactor_group_compaction_runs_started_total", 1450 "pyroscope_compactor_group_compactions_failures_total", 1451 "pyroscope_compactor_group_compactions_total", 1452 "pyroscope_compactor_blocks_marked_for_deletion_total", 1453 )) 1454 } 1455 1456 func createCustomBlock(t *testing.T, bkt objstore.Bucket, userID string, externalLabels map[string]string, generator func() []*testhelper.ProfileBuilder) ulid.ULID { 1457 meta, dir := testutil.CreateBlock(t, generator) 1458 blockLocalPath := filepath.Join(dir, meta.ULID.String()) 1459 1460 meta.Source = "test" 1461 for k, v := range externalLabels { 1462 meta.Labels[k] = v 1463 } 1464 _, err := meta.WriteToFile(log.NewNopLogger(), blockLocalPath) 1465 require.NoError(t, err) 1466 1467 // Copy the block files to the bucket. 1468 require.NoError(t, filepath.Walk(blockLocalPath, func(file string, info os.FileInfo, err error) error { 1469 if err != nil { 1470 return err 1471 } 1472 if info.IsDir() { 1473 return nil 1474 } 1475 1476 // Read the file content in memory. 1477 content, err := os.ReadFile(file) 1478 if err != nil { 1479 return err 1480 } 1481 1482 // Upload it to the bucket. 1483 relPath, err := filepath.Rel(blockLocalPath, file) 1484 if err != nil { 1485 return err 1486 } 1487 1488 return bkt.Upload(context.Background(), path.Join(userID, "phlaredb/", meta.ULID.String(), relPath), bytes.NewReader(content)) 1489 })) 1490 1491 return meta.ULID 1492 } 1493 1494 func createDBBlock(t *testing.T, bkt objstore.Bucket, userID string, minT, maxT int64, numSeries int, externalLabels map[string]string) ulid.ULID { 1495 return createCustomBlock(t, bkt, userID, externalLabels, func() []*testhelper.ProfileBuilder { 1496 result := []*testhelper.ProfileBuilder{} 1497 appendSample := func(seriesID int, ts int64) { 1498 profile := testhelper.NewProfileBuilder(ts*int64(time.Millisecond)). 1499 CPUProfile(). 1500 WithLabels( 1501 "series_id", strconv.Itoa(seriesID), 1502 ).ForStacktraceString("foo", "bar", "baz").AddSamples(1) 1503 result = append(result, profile) 1504 } 1505 1506 seriesID := 0 1507 1508 // Append a sample for each series, spreading it between minT and maxT-1 (both included). 1509 // Since we append one more series below, here we create N-1 series. 1510 if numSeries > 1 { 1511 for ts := minT; ts <= maxT; ts += (maxT - minT) / int64(numSeries-1) { 1512 appendSample(seriesID, ts) 1513 seriesID++ 1514 } 1515 } else { 1516 appendSample(seriesID, minT) 1517 } 1518 // Guarantee a series with a sample at time maxT 1519 appendSample(seriesID, maxT) 1520 return result 1521 }) 1522 } 1523 1524 func createDeletionMark(t *testing.T, bkt objstore.Bucket, userID string, blockID ulid.ULID, deletionTime time.Time) { 1525 content := mockDeletionMarkJSON(blockID.String(), deletionTime) 1526 blockPath := path.Join(userID, "phlaredb/", blockID.String()) 1527 markPath := path.Join(blockPath, block.DeletionMarkFilename) 1528 1529 require.NoError(t, bkt.Upload(context.Background(), markPath, strings.NewReader(content))) 1530 } 1531 1532 func findCompactorByUserID(compactors []*MultitenantCompactor, logs []*concurrency.SyncBuffer, userID string) (*MultitenantCompactor, *concurrency.SyncBuffer, error) { 1533 var compactor *MultitenantCompactor 1534 var log *concurrency.SyncBuffer 1535 1536 for i, c := range compactors { 1537 owned, err := c.shardingStrategy.compactorOwnUser(userID) 1538 if err != nil { 1539 return nil, nil, err 1540 } 1541 1542 // Ensure the user is not owned by multiple compactors 1543 if owned && compactor != nil { 1544 return nil, nil, fmt.Errorf("user %s owned by multiple compactors", userID) 1545 } 1546 if owned { 1547 compactor = c 1548 log = logs[i] 1549 } 1550 } 1551 1552 // Return an error if we've not been able to find it 1553 if compactor == nil { 1554 return nil, nil, fmt.Errorf("user %s not owned by any compactor", userID) 1555 } 1556 1557 return compactor, log, nil 1558 } 1559 1560 func removeIgnoredLogs(input []string) []string { 1561 ignoredLogStringsMap := map[string]struct{}{ 1562 // Since we moved to the component logger from the global logger for the ring in dskit these lines are now expected but are just ring setup information. 1563 `level=info component=compactor msg="ring doesn't exist in KV store yet"`: {}, 1564 `level=info component=compactor msg="not loading tokens from file, tokens file path is empty"`: {}, 1565 `level=info component=compactor msg="tokens verification succeeded" ring=compactor`: {}, 1566 `level=info component=compactor msg="waiting stable tokens" ring=compactor`: {}, 1567 `level=info component=compactor msg="instance not found in ring, adding with no tokens" ring=compactor`: {}, 1568 `level=debug component=compactor msg="JoinAfter expired" ring=compactor`: {}, 1569 `level=info component=compactor msg="auto-joining cluster after timeout" ring=compactor`: {}, 1570 `level=info component=compactor msg="lifecycler loop() exited gracefully" ring=compactor`: {}, 1571 `level=info component=compactor msg="changing instance state from" old_state=ACTIVE new_state=LEAVING ring=compactor`: {}, 1572 `level=error component=compactor msg="failed to set state to LEAVING" ring=compactor err="Changing instance state from LEAVING -> LEAVING is disallowed"`: {}, 1573 `level=error component=compactor msg="failed to set state to LEAVING" ring=compactor err="Changing instance state from JOINING -> LEAVING is disallowed"`: {}, 1574 `level=info component=compactor msg="unregistering instance from ring" ring=compactor`: {}, 1575 `level=info component=compactor msg="instance removed from the ring" ring=compactor`: {}, 1576 `level=info component=compactor msg="observing tokens before going ACTIVE" ring=compactor`: {}, 1577 `level=info component=compactor msg="lifecycler entering final sleep before shutdown" final_sleep=0s`: {}, 1578 `level=info component=compactor msg="ring lifecycler is shutting down" ring=compactor`: {}, 1579 } 1580 1581 out := make([]string, 0, len(input)) 1582 1583 for i := 0; i < len(input); i++ { 1584 log := input[i] 1585 if strings.Contains(log, "block.MetaFetcher") || strings.Contains(log, "instance not found in the ring") { 1586 continue 1587 } 1588 1589 if _, exists := ignoredLogStringsMap[log]; exists { 1590 continue 1591 } 1592 1593 out = append(out, log) 1594 } 1595 1596 return out 1597 } 1598 1599 func prepareConfig(t *testing.T) Config { 1600 compactorCfg := Config{} 1601 flagext.DefaultValues(&compactorCfg) 1602 // Use multiple range for testing. 1603 compactorCfg.BlockRanges = DurationList{2 * time.Hour, 12 * time.Hour, 24 * time.Hour} 1604 1605 compactorCfg.retryMinBackoff = 0 1606 compactorCfg.retryMaxBackoff = 0 1607 1608 // Use settings that ensure things will be done concurrently, verifying ordering assumptions. 1609 compactorCfg.MaxOpeningBlocksConcurrency = 3 1610 1611 // Do not wait for ring stability by default, in order to speed up tests. 1612 compactorCfg.ShardingRing.WaitStabilityMinDuration = 0 1613 compactorCfg.ShardingRing.WaitStabilityMaxDuration = 0 1614 1615 // Set lower timeout for waiting on compactor to become ACTIVE in the ring for unit tests 1616 compactorCfg.ShardingRing.WaitActiveInstanceTimeout = 5 * time.Second 1617 1618 // Inject default KV store. Must be overridden if "real" sharding is required. 1619 inmem, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil) 1620 t.Cleanup(func() { _ = closer.Close() }) 1621 compactorCfg.ShardingRing.Common.KVStore.Mock = inmem 1622 compactorCfg.ShardingRing.Common.InstanceAddr = "localhost" 1623 1624 // The new default is 25m, but tests rely on the previous value of 0s 1625 compactorCfg.CompactionWaitPeriod = 0 1626 1627 return compactorCfg 1628 } 1629 1630 func prepare(t *testing.T, compactorCfg Config, bucketClient objstore.Bucket) (*MultitenantCompactor, *blockCompactorMock, *tsdbPlannerMock, *concurrency.SyncBuffer, prometheus.Gatherer) { 1631 var limits validation.Limits 1632 flagext.DefaultValues(&limits) 1633 overrides, err := validation.NewOverrides(limits, nil) 1634 require.NoError(t, err) 1635 1636 return prepareWithConfigProvider(t, compactorCfg, bucketClient, overrides) 1637 } 1638 1639 func prepareWithConfigProvider(t *testing.T, compactorCfg Config, bucketClient objstore.Bucket, limits ConfigProvider) (*MultitenantCompactor, *blockCompactorMock, *tsdbPlannerMock, *concurrency.SyncBuffer, prometheus.Gatherer) { 1640 // Create a temporary directory for compactor data. 1641 dataDir := t.TempDir() 1642 1643 compactorCfg.DataDir = dataDir 1644 1645 blockCompactor := &blockCompactorMock{} 1646 tsdbPlanner := &tsdbPlannerMock{} 1647 logs := &concurrency.SyncBuffer{} 1648 logger := &componentLogger{component: "compactor", log: log.NewLogfmtLogger(logs)} 1649 registry := prometheus.NewRegistry() 1650 1651 blocksCompactorFactory := func(ctx context.Context, cfg Config, cfgProvider ConfigProvider, userID string, logger log.Logger, metrics *CompactorMetrics) (Compactor, error) { 1652 return blockCompactor, nil 1653 } 1654 1655 blocksPlannerFactory := func(cfg Config) Planner { 1656 return tsdbPlanner 1657 } 1658 1659 c, err := newMultitenantCompactor(compactorCfg, pyroscope_objstore.NewBucket(bucketClient), limits, logger, registry, splitAndMergeGrouperFactory, blocksCompactorFactory, blocksPlannerFactory) 1660 require.NoError(t, err) 1661 1662 return c, blockCompactor, tsdbPlanner, logs, registry 1663 } 1664 1665 type componentLogger struct { 1666 component string 1667 log log.Logger 1668 } 1669 1670 func (c *componentLogger) Log(keyvals ...interface{}) error { 1671 // Remove duration fields. 1672 for ix := 0; ix+1 < len(keyvals); { 1673 k := keyvals[ix] 1674 1675 ks, ok := k.(string) 1676 if !ok { 1677 ix += 2 1678 continue 1679 } 1680 1681 if ks == "duration" || ks == "duration_ms" { 1682 keyvals = append(keyvals[:ix], keyvals[ix+2:]...) 1683 } else { 1684 ix += 2 1685 } 1686 } 1687 1688 for ix := 0; ix+1 < len(keyvals); ix += 2 { 1689 k := keyvals[ix] 1690 v := keyvals[ix+1] 1691 1692 ks, ok := k.(string) 1693 if !ok { 1694 continue 1695 } 1696 vs, ok := v.(string) 1697 if !ok { 1698 continue 1699 } 1700 if ks == "component" && vs == c.component { 1701 return c.log.Log(keyvals...) 1702 } 1703 } 1704 return nil 1705 } 1706 1707 type blockCompactorMock struct { 1708 mock.Mock 1709 } 1710 1711 func (m *blockCompactorMock) CompactWithSplitting(ctx context.Context, dest string, dirs []string, shardCount, stageSize uint64) (result []ulid.ULID, _ error) { 1712 args := m.Called(ctx, dest, dirs, shardCount, stageSize) 1713 return args.Get(0).([]ulid.ULID), args.Error(1) 1714 } 1715 1716 type tsdbPlannerMock struct { 1717 mock.Mock 1718 } 1719 1720 func (m *tsdbPlannerMock) Plan(ctx context.Context, metasByMinTime []*block.Meta) ([]*block.Meta, error) { 1721 args := m.Called(ctx, metasByMinTime) 1722 return args.Get(0).([]*block.Meta), args.Error(1) 1723 } 1724 1725 func mockBlockMetaJSON(id string) string { 1726 return mockBlockMetaJSONWithTimeRange(id, 1574776800000, 1574784000000) 1727 } 1728 1729 func mockBlockMetaJSONWithTimeRange(id string, mint, maxt int64) string { 1730 return mockBlockMetaJSONWithTimeRangeAndLabels(id, mint, maxt, nil) 1731 } 1732 1733 func mockBlockMetaJSONWithTimeRangeAndLabels(id string, mint, maxt int64, lbls map[string]string) string { 1734 content, err := json.Marshal(blockMeta(id, mint, maxt, lbls)) 1735 if err != nil { 1736 panic("failed to marshal mocked block meta") 1737 } 1738 return string(content) 1739 } 1740 1741 func blockMeta(id string, mint, maxt int64, lbls map[string]string) *block.Meta { 1742 return &block.Meta{ 1743 Version: 1, 1744 ULID: ulid.MustParse(id), 1745 MinTime: model.Time(mint), 1746 MaxTime: model.Time(maxt), 1747 Compaction: block.BlockMetaCompaction{ 1748 Level: 1, 1749 Sources: []ulid.ULID{ulid.MustParse(id)}, 1750 }, 1751 1752 Labels: lbls, 1753 } 1754 } 1755 1756 func mockDeletionMarkJSON(id string, deletionTime time.Time) string { 1757 meta := block.DeletionMark{ 1758 Version: block.DeletionMarkVersion1, 1759 ID: ulid.MustParse(id), 1760 DeletionTime: deletionTime.Unix(), 1761 } 1762 1763 content, err := json.Marshal(meta) 1764 if err != nil { 1765 panic("failed to marshal mocked block meta") 1766 } 1767 1768 return string(content) 1769 } 1770 1771 func TestMultitenantCompactor_DeleteLocalSyncFiles(t *testing.T) { 1772 numUsers := 10 1773 1774 // Setup user IDs 1775 userIDs := make([]string, 0, numUsers) 1776 for i := 1; i <= numUsers; i++ { 1777 userIDs = append(userIDs, fmt.Sprintf("user-%d", i)) 1778 } 1779 1780 inmem := objstore.NewInMemBucket() 1781 for _, userID := range userIDs { 1782 id, err := ulid.New(ulid.Now(), rand.Reader) 1783 require.NoError(t, err) 1784 require.NoError(t, inmem.Upload(context.Background(), userID+"/"+id.String()+"/meta.json", strings.NewReader(mockBlockMetaJSON(id.String())))) 1785 } 1786 1787 // Create a shared KV Store 1788 kvstore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil) 1789 t.Cleanup(func() { assert.NoError(t, closer.Close()) }) 1790 1791 // Create two compactors 1792 var compactors []*MultitenantCompactor 1793 1794 for i := 1; i <= 2; i++ { 1795 cfg := prepareConfig(t) 1796 cfg.CompactionInterval = 10 * time.Minute // We will only call compaction manually. 1797 1798 cfg.ShardingRing.Common.InstanceID = fmt.Sprintf("compactor-%d", i) 1799 cfg.ShardingRing.Common.InstanceAddr = fmt.Sprintf("127.0.0.%d", i) 1800 cfg.ShardingRing.WaitStabilityMinDuration = 3 * time.Second 1801 cfg.ShardingRing.WaitStabilityMaxDuration = 10 * time.Second 1802 cfg.ShardingRing.Common.KVStore.Mock = kvstore 1803 1804 // Each compactor will get its own temp dir for storing local files. 1805 var limits validation.Limits 1806 flagext.DefaultValues(&limits) 1807 limits.CompactorTenantShardSize = 1 // Each tenant will belong to single compactor only. 1808 overrides, err := validation.NewOverrides(limits, nil) 1809 require.NoError(t, err) 1810 1811 c, _, tsdbPlanner, _, _ := prepareWithConfigProvider(t, cfg, inmem, overrides) 1812 t.Cleanup(func() { 1813 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), c)) 1814 }) 1815 1816 compactors = append(compactors, c) 1817 1818 // Mock the planner as if there's no compaction to do, 1819 // in order to simplify tests (all in all, we just want to 1820 // test our logic and not TSDB compactor which we expect to 1821 // be already tested). 1822 tsdbPlanner.On("Plan", mock.Anything, mock.Anything).Return([]*block.Meta{}, nil) 1823 } 1824 1825 require.Equal(t, 2, len(compactors)) 1826 c1 := compactors[0] 1827 c2 := compactors[1] 1828 1829 // Start first compactor 1830 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c1)) 1831 1832 // Wait until a run has been completed on first compactor. This happens as soon as compactor starts. 1833 test.Poll(t, 10*time.Second, 1.0, func() interface{} { 1834 return prom_testutil.ToFloat64(c1.compactionRunsCompleted) 1835 }) 1836 1837 require.NoError(t, os.Mkdir(c1.metaSyncDirForUser("new-user"), 0o600)) 1838 1839 // Verify that first compactor has synced all the users, plus there is one extra we have just created. 1840 require.Equal(t, numUsers+1, len(c1.listTenantsWithMetaSyncDirectories())) 1841 1842 // Now start second compactor, and wait until it runs compaction. 1843 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c2)) 1844 test.Poll(t, 10*time.Second, 1.0, func() interface{} { 1845 return prom_testutil.ToFloat64(c2.compactionRunsCompleted) 1846 }) 1847 1848 // Let's check how many users second compactor has. 1849 c2Users := len(c2.listTenantsWithMetaSyncDirectories()) 1850 require.NotZero(t, c2Users) 1851 1852 // Force new compaction cycle on first compactor. It will run the cleanup of un-owned users at the end of compaction cycle. 1853 c1.compactUsers(context.Background()) 1854 c1Users := len(c1.listTenantsWithMetaSyncDirectories()) 1855 1856 // Now compactor 1 should have cleaned old sync files. 1857 require.NotEqual(t, numUsers, c1Users) 1858 require.Equal(t, numUsers, c1Users+c2Users) 1859 } 1860 1861 func TestMultitenantCompactor_ShouldFailCompactionOnTimeout(t *testing.T) { 1862 t.Parallel() 1863 1864 // Mock the bucket 1865 bucketClient := mockobjstore.NewMockBucketWithHelper(t) 1866 bucketClient.MockIter("", []string{}, nil) 1867 1868 ringStore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil) 1869 t.Cleanup(func() { assert.NoError(t, closer.Close()) }) 1870 1871 cfg := prepareConfig(t) 1872 cfg.ShardingRing.Common.InstanceID = instanceID 1873 cfg.ShardingRing.Common.InstanceAddr = addr 1874 cfg.ShardingRing.Common.KVStore.Mock = ringStore 1875 1876 // Set ObservePeriod to longer than the timeout period to mock a timeout while waiting on ring to become ACTIVE 1877 cfg.ShardingRing.ObservePeriod = time.Second * 10 1878 1879 c, _, _, _, _ := prepare(t, cfg, bucketClient) 1880 1881 // Try to start the compactor with a bad consul kv-store. The 1882 err := services.StartAndAwaitRunning(context.Background(), c) 1883 1884 // Assert that the compactor timed out 1885 require.ErrorIs(t, err, context.DeadlineExceeded) 1886 } 1887 1888 type ownUserReason int 1889 1890 const ( 1891 ownUserReasonBlocksCleaner ownUserReason = iota 1892 ownUserReasonCompactor 1893 ) 1894 1895 func TestOwnUser(t *testing.T) { 1896 type testCase struct { 1897 compactors int 1898 enabledUsers []string 1899 disabledUsers []string 1900 compactorShards map[string]int 1901 1902 check func(t *testing.T, comps []*MultitenantCompactor) 1903 } 1904 1905 const user1 = "user1" 1906 const user2 = "another-user" 1907 1908 testCases := map[string]testCase{ 1909 "5 compactors, sharding enabled, no compactor shard size": { 1910 compactors: 5, 1911 compactorShards: nil, // no limits 1912 1913 check: func(t *testing.T, comps []*MultitenantCompactor) { 1914 require.Len(t, owningCompactors(t, comps, user1, ownUserReasonCompactor), 5) 1915 require.Len(t, owningCompactors(t, comps, user1, ownUserReasonBlocksCleaner), 1) 1916 1917 require.Len(t, owningCompactors(t, comps, user2, ownUserReasonCompactor), 5) 1918 require.Len(t, owningCompactors(t, comps, user2, ownUserReasonBlocksCleaner), 1) 1919 }, 1920 }, 1921 1922 "10 compactors, sharding enabled, with non-zero shard sizes": { 1923 compactors: 10, 1924 compactorShards: map[string]int{user1: 2, user2: 3}, 1925 1926 check: func(t *testing.T, comps []*MultitenantCompactor) { 1927 require.Len(t, owningCompactors(t, comps, user1, ownUserReasonCompactor), 2) 1928 require.Len(t, owningCompactors(t, comps, user1, ownUserReasonBlocksCleaner), 1) 1929 // Blocks cleanup is done by one of the compactors that "own" the user. 1930 require.Subset(t, owningCompactors(t, comps, user1, ownUserReasonCompactor), owningCompactors(t, comps, user1, ownUserReasonBlocksCleaner)) 1931 1932 require.Len(t, owningCompactors(t, comps, user2, ownUserReasonCompactor), 3) 1933 require.Len(t, owningCompactors(t, comps, user2, ownUserReasonBlocksCleaner), 1) 1934 // Blocks cleanup is done by one of the compactors that "own" the user. 1935 require.Subset(t, owningCompactors(t, comps, user2, ownUserReasonCompactor), owningCompactors(t, comps, user2, ownUserReasonBlocksCleaner)) 1936 }, 1937 }, 1938 1939 "10 compactors, sharding enabled, with zero shard size": { 1940 compactors: 10, 1941 compactorShards: map[string]int{user2: 0}, 1942 1943 check: func(t *testing.T, comps []*MultitenantCompactor) { 1944 require.Len(t, owningCompactors(t, comps, user2, ownUserReasonCompactor), 10) 1945 require.Len(t, owningCompactors(t, comps, user2, ownUserReasonBlocksCleaner), 1) 1946 }, 1947 }, 1948 } 1949 1950 for name, tc := range testCases { 1951 tc := tc 1952 t.Run(name, func(t *testing.T) { 1953 t.Parallel() 1954 1955 kvStore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil) 1956 t.Cleanup(func() { assert.NoError(t, closer.Close()) }) 1957 1958 inmem := objstore.NewInMemBucket() 1959 1960 compactors := []*MultitenantCompactor(nil) 1961 1962 for i := 0; i < tc.compactors; i++ { 1963 cfg := prepareConfig(t) 1964 cfg.CompactionInterval = 10 * time.Minute // We will only call compaction manually. 1965 1966 cfg.EnabledTenants = tc.enabledUsers 1967 cfg.DisabledTenants = tc.disabledUsers 1968 1969 cfg.ShardingRing.Common.InstanceID = fmt.Sprintf("compactor-%d", i) 1970 cfg.ShardingRing.Common.InstanceAddr = fmt.Sprintf("127.0.0.%d", i) 1971 // No need to wait. All compactors are started before we do any tests, and we wait for all of them 1972 // to appear in all rings. 1973 cfg.ShardingRing.WaitStabilityMinDuration = 0 1974 cfg.ShardingRing.WaitStabilityMaxDuration = 0 1975 cfg.ShardingRing.Common.KVStore.Mock = kvStore 1976 1977 limits := newMockConfigProvider() 1978 limits.instancesShardSize = tc.compactorShards 1979 1980 c, _, _, _, _ := prepareWithConfigProvider(t, cfg, inmem, limits) 1981 require.NoError(t, services.StartAndAwaitRunning(context.Background(), c)) 1982 t.Cleanup(stopServiceFn(t, c)) 1983 1984 compactors = append(compactors, c) 1985 } 1986 1987 // Make sure all compactors see all other compactors in the ring before running tests. 1988 test.Poll(t, 2*time.Second, true, func() interface{} { 1989 for _, c := range compactors { 1990 rs, err := c.ring.GetAllHealthy(RingOp) 1991 if err != nil { 1992 return false 1993 } 1994 if len(rs.Instances) != len(compactors) { 1995 return false 1996 } 1997 } 1998 return true 1999 }) 2000 2001 tc.check(t, compactors) 2002 }) 2003 } 2004 } 2005 2006 func owningCompactors(t *testing.T, comps []*MultitenantCompactor, user string, reason ownUserReason) []string { 2007 result := []string(nil) 2008 for _, c := range comps { 2009 var f func(string) (bool, error) 2010 if reason == ownUserReasonCompactor { 2011 f = c.shardingStrategy.compactorOwnUser 2012 } else { 2013 f = c.shardingStrategy.blocksCleanerOwnUser 2014 } 2015 ok, err := f(user) 2016 require.NoError(t, err) 2017 if ok { 2018 // We set instance ID even when not using sharding. It makes output nicer, since 2019 // calling method only wants to see some identifier. 2020 result = append(result, c.compactorCfg.ShardingRing.Common.InstanceID) 2021 } 2022 } 2023 return result 2024 } 2025 2026 func stopServiceFn(t *testing.T, serv services.Service) func() { 2027 return func() { 2028 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), serv)) 2029 } 2030 } 2031 2032 type bucketWithMockedAttributes struct { 2033 objstore.Bucket 2034 2035 customAttributes map[string]objstore.ObjectAttributes 2036 } 2037 2038 func (b *bucketWithMockedAttributes) Attributes(ctx context.Context, name string) (objstore.ObjectAttributes, error) { 2039 if attrs, ok := b.customAttributes[name]; ok { 2040 return attrs, nil 2041 } 2042 2043 return b.Bucket.Attributes(ctx, name) 2044 }