Files
Joe Elliottandzeekay e94374166e [Tempo 3.0] Remove compactor and v2 block encoding code (#6273)
* o7 compactor

Signed-off-by: Joe Elliott <[email protected]>

* wip: o7 v2

Signed-off-by: Joe Elliott <[email protected]>

* test cleanup

Signed-off-by: Joe Elliott <[email protected]>

* remove compactor from jsonnet

Signed-off-by: Joe Elliott <[email protected]>

* changelog

Signed-off-by: Joe Elliott <[email protected]>

* gen manifest

Signed-off-by: Joe Elliott <[email protected]>

* config cleanup

Signed-off-by: Joe Elliott <[email protected]>

* docs

Signed-off-by: Joe Elliott <[email protected]>

* jsonnet

Signed-off-by: Joe Elliott <[email protected]>

* remove encoding

Signed-off-by: Joe Elliott <[email protected]>

* remove data encoding

Signed-off-by: Joe Elliott <[email protected]>

* lint and cleanup

Signed-off-by: Joe Elliott <[email protected]>

* unflake TestWorker\?

Signed-off-by: Joe Elliott <[email protected]>

---------

Signed-off-by: Joe Elliott <[email protected]>
2026-02-22 00:55:18 -08:00

464 lines
14 KiB
Go

package provider
import (
"context"
"flag"
"strconv"
"testing"
"time"
"github.com/go-kit/log"
"github.com/grafana/tempo/modules/backendscheduler/work"
"github.com/grafana/tempo/modules/overrides"
"github.com/grafana/tempo/modules/storage"
"github.com/grafana/tempo/pkg/tempopb"
"github.com/grafana/tempo/pkg/util/test"
"github.com/grafana/tempo/tempodb"
"github.com/grafana/tempo/tempodb/backend"
"github.com/grafana/tempo/tempodb/backend/local"
"github.com/grafana/tempo/tempodb/blockselector"
"github.com/grafana/tempo/tempodb/encoding"
"github.com/grafana/tempo/tempodb/encoding/common"
"github.com/grafana/tempo/tempodb/wal"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
)
var tenant = "test-tenant"
func TestCompactionProvider(t *testing.T) {
cfg := CompactionConfig{}
cfg.RegisterFlagsAndApplyDefaults("", &flag.FlagSet{})
cfg.MaxJobsPerTenant = 2
cfg.MeasureInterval = 100 * time.Millisecond
cfg.MinCycleInterval = 100 * time.Millisecond
tmpDir := t.TempDir()
var (
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
store, _, ww = newStore(ctx, t, tmpDir)
)
defer func() {
cancel()
store.Shutdown()
}()
// Push some data to a few tenants
tenantCount := 3
for i := range tenantCount {
testTenant := tenant + strconv.Itoa(i)
writeTenantBlocks(ctx, t, backend.NewWriter(ww), testTenant, 5)
}
time.Sleep(100 * time.Millisecond)
limits, err := overrides.NewOverrides(overrides.Config{Defaults: overrides.Overrides{}}, nil, prometheus.DefaultRegisterer)
require.NoError(t, err)
w := work.New(work.Config{})
p := NewCompactionProvider(
cfg,
test.NewTestingLogger(t),
store,
limits,
w,
)
jobChan := p.Start(ctx)
var receivedJobs []*work.Job
for job := range jobChan {
receivedJobs = append(receivedJobs, job)
err = w.AddJob(job)
require.NoError(t, err)
}
for _, job := range receivedJobs {
require.NotNil(t, job)
require.Equal(t, tempopb.JobType_JOB_TYPE_COMPACTION, job.Type)
require.Greater(t, len(job.GetCompactionInput()), 0)
require.NotEqual(t, "", job.Tenant())
require.NotEqual(t, "", job.ID)
// Check that the newBlockSelector does not include the input blocks
for i := range tenantCount {
testTenant := tenant + strconv.Itoa(i)
twbs, _ := p.newBlockSelector(testTenant)
require.NotNil(t, twbs)
metas := collectAllMetas(twbs)
// All blocks which have been received were addded to the work queue.
// New instances of the block selector should yield zero blocks.
require.Len(t, metas, 0)
}
}
}
func TestCompactionProvider_EmptyStart(t *testing.T) {
cfg := CompactionConfig{}
cfg.RegisterFlagsAndApplyDefaults("", &flag.FlagSet{})
cfg.MaxJobsPerTenant = 1
cfg.MeasureInterval = 100 * time.Millisecond
cfg.MinCycleInterval = 100 * time.Millisecond
tmpDir := t.TempDir()
var (
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
store, _, ww = newStore(ctx, t, tmpDir)
)
defer func() {
cancel()
store.Shutdown()
}()
limits, err := overrides.NewOverrides(overrides.Config{Defaults: overrides.Overrides{}}, nil, prometheus.DefaultRegisterer)
require.NoError(t, err)
w := work.New(work.Config{})
p := NewCompactionProvider(
cfg,
test.NewTestingLogger(t),
store,
limits,
w,
)
b := p.prepareNextTenant(ctx, false)
require.False(t, b, "no tenant should be found")
require.Nil(t, p.curTenant, "a tenant should not be set")
require.Nil(t, p.curSelector, "a block selector should not be set")
writeTenantBlocks(ctx, t, backend.NewWriter(ww), tenant, 1)
time.Sleep(150 * time.Millisecond)
b = p.prepareNextTenant(ctx, false)
require.False(t, b, "no tenant with a single block should be found")
require.Nil(t, p.curTenant, "a tenant should not be set")
require.Nil(t, p.curSelector, "a block selector should not be set")
writeTenantBlocks(ctx, t, backend.NewWriter(ww), tenant, 1)
time.Sleep(150 * time.Millisecond)
b = p.prepareNextTenant(ctx, false)
require.True(t, b, "tenant with two blocks should be found")
require.NotNil(t, p.curTenant, "a tenant should be set")
require.NotNil(t, p.curSelector, "a block selector should be set")
jobChan := p.Start(ctx)
require.NotNil(t, jobChan, "job channel should not be nil")
blocksSeen := make(map[string]struct{})
var metas []*backend.BlockMeta
for job := range jobChan {
require.NotNil(t, job, "job should not be nil")
require.Equal(t, tempopb.JobType_JOB_TYPE_COMPACTION, job.Type, "job type should be compaction")
require.Equal(t, tenant, job.Tenant(), "job tenant should match the current tenant")
require.Greater(t, len(job.GetCompactionInput()), 0, "compaction input should not be empty")
metas = store.BlockMetas(tenant)
// Add each job to the work queue, so we don't process the blocks within.
err = w.AddJob(job)
require.NoError(t, err, "should be able to add job to work queue")
for _, blockID := range job.GetCompactionInput() {
_, seen := blocksSeen[blockID]
require.False(t, seen, "block ID %s should not have been seen before", blockID)
blocksSeen[blockID] = struct{}{}
u := backend.MustParse(blockID)
if _, ok := foundMetaInMetas(metas, u); ok {
// metas = append(metas, meta)
err = store.MarkBlockCompacted(tenant, u)
require.NoError(t, err, "should be able to mark block %s compacted for tenant %s", blockID, tenant)
}
}
require.Greater(t, len(metas), 0, "there should be some block metas to compact")
}
}
func TestCompactionProvider_RecentJobsCache(t *testing.T) {
// Create a test provider
provider := &CompactionProvider{
logger: log.NewNopLogger(),
sched: &mockScheduler{},
outstandingJobs: make(map[string][]backend.UUID),
}
var (
block1 = backend.NewUUID()
block2 = backend.NewUUID()
block3 = backend.NewUUID()
block4 = backend.NewUUID()
)
// Cache starts empty
require.Len(t, provider.outstandingJobs, 0, "Cache should start empty")
job1 := &work.Job{
ID: "job1",
Type: tempopb.JobType_JOB_TYPE_COMPACTION,
JobDetail: tempopb.JobDetail{
Compaction: &tempopb.CompactionDetail{
Input: []string{block1.String(), block2.String()},
},
},
}
ctx := context.Background()
provider.addToRecentJobs(ctx, job1)
require.Len(t, provider.outstandingJobs, 1, "Cache should contain one job")
require.Contains(t, provider.outstandingJobs, "job1", "Cache should contain job1")
require.Equal(t, []backend.UUID{block1, block2}, provider.outstandingJobs["job1"], "Cache should contain correct blocks")
job2 := &work.Job{
ID: "job2",
Type: tempopb.JobType_JOB_TYPE_COMPACTION,
JobDetail: tempopb.JobDetail{
Compaction: &tempopb.CompactionDetail{
Input: []string{block3.String(), block4.String()},
},
},
}
provider.addToRecentJobs(ctx, job2)
require.Len(t, provider.outstandingJobs, 2, "Cache should contain two jobs")
require.Equal(t, []backend.UUID{block3, block4}, provider.outstandingJobs["job2"], "Cache should contain correct blocks for job2")
// Non-compaction job should not be added to cache
retentionJob := &work.Job{
ID: "retention1",
Type: tempopb.JobType_JOB_TYPE_RETENTION,
}
provider.addToRecentJobs(ctx, retentionJob)
require.Len(t, provider.outstandingJobs, 2, "Non-compaction jobs should not be added to cache")
// Empty compaction input should not be added
emptyJob := &work.Job{
ID: "empty-job",
Type: tempopb.JobType_JOB_TYPE_COMPACTION,
JobDetail: tempopb.JobDetail{
Compaction: &tempopb.CompactionDetail{
Input: []string{}, // Empty input
},
},
}
provider.addToRecentJobs(ctx, emptyJob)
require.Len(t, provider.outstandingJobs, 2, "Jobs with empty input should not be added to cache")
}
func TestCompactionProvider_RecentJobsCachePreventseDuplicatesAndCleansUp(t *testing.T) {
const tenant = "test-tenant"
cfg := CompactionConfig{}
cfg.RegisterFlagsAndApplyDefaults("", &flag.FlagSet{})
cfg.MaxJobsPerTenant = 1000 // Set high enough so we don't hit the limit
cfg.MeasureInterval = 100 * time.Millisecond
cfg.MinCycleInterval = 100 * time.Millisecond
tmpDir := t.TempDir()
var (
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
store, _, ww = newStore(ctx, t, tmpDir)
)
defer func() {
cancel()
store.Shutdown()
}()
// Push some data to one tenant - enough blocks to create multiple compaction jobs
writeTenantBlocks(ctx, t, backend.NewWriter(ww), tenant, 10)
time.Sleep(100 * time.Millisecond)
limits, err := overrides.NewOverrides(overrides.Config{Defaults: overrides.Overrides{}}, nil, prometheus.DefaultRegisterer)
require.NoError(t, err)
w := work.New(work.Config{})
p := NewCompactionProvider(
cfg,
test.NewTestingLogger(t),
store,
limits,
w,
)
jobChan := p.Start(ctx)
var receivedJobs []*work.Job
blockIDs := make(map[string]int)
for job := range jobChan {
receivedJobs = append(receivedJobs, job)
// Collect the jobs but do not add them to the work queue yet
for _, blockID := range job.GetCompactionInput() {
// Count how many times each block ID appears across all jobs
blockIDs[blockID]++
require.Equal(t, 1, blockIDs[blockID], "Block ID %s should only appear once across all jobs", blockID)
}
}
for _, job := range receivedJobs {
require.NotNil(t, job)
require.Equal(t, tempopb.JobType_JOB_TYPE_COMPACTION, job.Type)
require.Greater(t, len(job.GetCompactionInput()), 0)
require.Equal(t, tenant, job.Tenant(), "Job tenant should match the test tenant")
require.NotEqual(t, "", job.ID)
// // Check that the newBlockSelector does not include the input blocks
// twbs, _ := p.newBlockSelector(tenant)
// require.NotNil(t, twbs)
//
// metas := collectAllMetas(twbs)
//
// // All blocks which have been received were addded to the work queue.
// // New instances of the block selector should yield zero blocks.
// require.Len(t, metas, 0)
}
// Verify that all received jobs are present in the recent jobs cache
require.Len(t, p.outstandingJobs, len(receivedJobs), "Recent jobs cache should contain all received jobs")
for _, job := range receivedJobs {
require.Contains(t, p.outstandingJobs, job.ID, "Recent jobs cache should contain job %s", job.ID)
require.Equal(t, len(job.GetCompactionInput()), len(p.outstandingJobs[job.ID]), "Recent jobs cache should contain correct number of blocks for job %s", job.ID)
for _, blockID := range job.GetCompactionInput() {
u := backend.MustParse(blockID)
require.Equal(t, u.String(), blockID, "Block ID %s should match the UUID format", blockID)
}
}
// Now add a job to the work queue and verify it is removed from the recent jobs cache
firstJob := receivedJobs[0]
err = w.AddJob(firstJob)
require.NoError(t, err, "should be able to add first job to work queue")
// Get a new block selector for the tenant
twbs, _ := p.newBlockSelector(tenant)
require.NotNil(t, twbs)
// Verify the recent jobs cache was cleaned up
require.NotContains(t, p.outstandingJobs, firstJob.ID, "Job should be removed from recent jobs cache after being added to work queue")
metas := collectAllMetas(twbs)
// Verify that the blocks in the first job are not present in the metas
for _, blockID := range firstJob.GetCompactionInput() {
u := backend.MustParse(blockID)
meta, found := foundMetaInMetas(metas, u)
require.False(t, found, "Block %s from job %s should not be present in the block selector after being added to work queue", blockID, firstJob.ID)
require.Nil(t, meta, "Block %s from job %s should not be present in the block selector after being added to work queue", blockID, firstJob.ID)
}
// Verify that the remaining jobs are still in the cache
for _, job := range receivedJobs[1:] {
require.Contains(t, p.outstandingJobs, job.ID, "Recent jobs cache should still contain job %s", job.ID)
require.Equal(t, len(job.GetCompactionInput()), len(p.outstandingJobs[job.ID]), "Recent jobs cache should contain correct number of blocks for job %s", job.ID)
for _, blockID := range job.GetCompactionInput() {
u := backend.MustParse(blockID)
require.Equal(t, u.String(), blockID, "Block ID %s should match the UUID format", blockID)
}
}
}
func newStore(ctx context.Context, t testing.TB, tmpDir string) (storage.Store, backend.RawReader, backend.RawWriter) {
rr, ww, _, err := local.New(&local.Config{
Path: tmpDir + "/traces",
})
require.NoError(t, err)
return newStoreWithLogger(ctx, t, test.NewTestingLogger(t), tmpDir), rr, ww
}
func newStoreWithLogger(ctx context.Context, t testing.TB, log log.Logger, tmpDir string) storage.Store {
s, err := storage.NewStore(storage.Config{
Trace: tempodb.Config{
Backend: backend.Local,
Local: &local.Config{
Path: tmpDir + "/traces",
},
Block: &common.BlockConfig{
BloomFP: 0.01,
BloomShardSizeBytes: 100_000,
Version: encoding.LatestEncoding().Version(),
},
WAL: &wal.Config{
Filepath: tmpDir + "/wal",
},
BlocklistPoll: 100 * time.Millisecond,
},
}, nil, log)
require.NoError(t, err)
s.EnablePolling(ctx, &ownsEverythingSharder{}, false)
return s
}
func writeTenantBlocks(ctx context.Context, t *testing.T, w backend.Writer, tenant string, count int) {
var err error
for range count {
meta := &backend.BlockMeta{
BlockID: backend.NewUUID(),
TenantID: tenant,
Version: encoding.LatestEncoding().Version(),
}
err = w.WriteBlockMeta(ctx, meta)
require.NoError(t, err)
}
}
func foundMetaInMetas(metas []*backend.BlockMeta, u backend.UUID) (*backend.BlockMeta, bool) {
for _, m := range metas {
if m.BlockID == u {
return m, true
}
}
return nil, false
}
type ownsEverythingSharder struct{}
func (ownsEverythingSharder) Owns(_ string) bool {
return true
}
func collectAllMetas(bs blockselector.CompactionBlockSelector) []*backend.BlockMeta {
metas, _ := bs.BlocksToCompact()
for newMetas, _ := bs.BlocksToCompact(); len(newMetas) > 0; {
metas = append(metas, newMetas...)
}
return metas
}
// mockScheduler implements the Scheduler interface for testing
type mockScheduler struct {
jobs []*work.Job
}
func (m *mockScheduler) ListJobs() []*work.Job {
return m.jobs
}
// func (m *mockScheduler) addJob(job *work.Job) {
// m.jobs = append(m.jobs, job)
// }