Files
Zach LeslieandGitHub c5fe2e7e6b [backend-scheduler] initial work file sharding for more targeted marshaling (#5412)
* [backend-scheduler] initial work file sharding for more targeted marshaling

* Add integration coverage for both work implementations

* Update config manifest

* Add integration coverage for sharded work configuration

* Fix integration test config path

* Add test coverage for sharded work migrations and marshaling

* Use new sharded work file in the backend and handle migration

* Clean up backend after migration

* Use shared method for shard file name

* Add method to write files atomically and test

* Update work interface for simpler flush/load interactions

* Update callers for local flush/load interface methods

* Fix lint

* Fix lint

* Fix lint

* Clean up

* Clean up after refactor new interface functions

* Promote the sharded implementation to be the only implementation

* Clean up comments

* Generate config manifest

* Clean up interface and improve test coverage

* Drop transitional comments

* Protect work marshal/unmarshal with a mutex

* Add test coverage for concurrent marshal/unmarshal

* Lint

* Fix race condition in Marshal/Unmarshal when jobs are operating

* Relocate methods

* Update changelog

* Drop interface method only used in tests

* Rename method for clarity, add doc

* Update doc for clarity

* Lock for safety

* Clarify and improve locking flush behavior

* Encapsulate the lock/unlock in a func

* Address some PR feedback

* Further unifi shard file name generation

* Modify work to use int rather than uint8

* Fix lint
2025-07-25 18:04:22 +00:00

164 lines
4.7 KiB
Go

package backendscheduler
import (
"context"
"flag"
"os"
"testing"
"github.com/google/uuid"
"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/tempodb/backend"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
)
// TestShardedIntegration verifies that sharded work can be used as a drop-in replacement
func TestShardedIntegration(t *testing.T) {
tests := []struct {
name string
}{
{"sharded_work"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
// Create config with or without sharding
cfg := Config{}
cfg.RegisterFlagsAndApplyDefaults("", &flag.FlagSet{})
cfg.LocalWorkPath = tmpDir + "/work"
var (
ctx, cancel = context.WithCancel(context.Background())
store, rr, 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)
scheduler, err := New(cfg, store, limits, rr, ww)
require.NoError(t, err)
err = scheduler.starting(ctx)
require.NoError(t, err)
testJobOperations(ctx, t, scheduler)
testPersistenceAndRecovery(ctx, t, scheduler, cfg, store, limits, rr, ww)
})
}
}
func testJobOperations(ctx context.Context, t *testing.T, scheduler *BackendScheduler) {
// Add some jobs
jobCount := 5
jobIDs := make([]string, jobCount)
for i := range jobCount {
// The scheduler will create jobs through providers, but for testing we'll add them directly
job := &work.Job{
ID: uuid.New().String(),
Type: tempopb.JobType_JOB_TYPE_COMPACTION,
JobDetail: tempopb.JobDetail{
Tenant: "test-tenant",
Compaction: &tempopb.CompactionDetail{
Input: []string{uuid.New().String(), uuid.New().String()},
},
},
}
err := scheduler.work.AddJob(job)
require.NoError(t, err)
jobIDs[i] = job.ID
}
// Test job retrieval
allJobs := scheduler.ListJobs()
require.Len(t, allJobs, jobCount, "Should have added jobs to the scheduler")
// Mark all jobs completed
for _, jobID := range jobIDs {
// Start the job
scheduler.work.StartJob(jobID)
// Complete the job
_, err := scheduler.UpdateJob(ctx, &tempopb.UpdateJobStatusRequest{
JobId: jobID,
Status: tempopb.JobStatus_JOB_STATUS_SUCCEEDED,
})
require.NoError(t, err)
}
// Verify all jobs are completed
allJobs = scheduler.ListJobs()
for _, job := range allJobs {
require.Equal(t, tempopb.JobStatus_JOB_STATUS_SUCCEEDED, job.GetStatus())
}
require.Len(t, allJobs, jobCount, "Should have same number of jobs after completion")
// If sharding is enabled, verify sharding-specific functionality
stats := scheduler.work.(*work.Work).GetShardStats()
require.NotNil(t, stats)
totalJobs, exists := stats["total_jobs"]
require.True(t, exists)
require.Equal(t, jobCount, totalJobs)
t.Logf("Shard distribution stats: %+v", stats)
}
func testPersistenceAndRecovery(ctx context.Context, t *testing.T, originalScheduler *BackendScheduler, cfg Config, store storage.Store, limits overrides.Interface, rr backend.RawReader, ww backend.RawWriter) {
// Get initial jobs
initialJobs := originalScheduler.ListJobs()
require.NotEmpty(t, initialJobs, "Should have jobs to test persistence")
// Force a flush to disk
err := originalScheduler.work.FlushToLocal(ctx, originalScheduler.cfg.LocalWorkPath, nil) // Flush all
require.NoError(t, err)
// Create a new scheduler instance (simulating restart)
newScheduler, err := New(cfg, store, limits, rr, ww)
require.NoError(t, err)
err = newScheduler.starting(ctx)
require.NoError(t, err)
// Verify jobs were recovered
recoveredJobs := newScheduler.ListJobs()
require.Len(t, recoveredJobs, len(initialJobs))
// Verify job details match
initialJobMap := make(map[string]*work.Job)
for _, job := range initialJobs {
initialJobMap[job.ID] = job
}
for _, recoveredJob := range recoveredJobs {
originalJob, exists := initialJobMap[recoveredJob.ID]
require.True(t, exists, "Recovered job should exist in original jobs")
require.Equal(t, originalJob.GetStatus(), recoveredJob.GetStatus())
require.Equal(t, originalJob.Tenant(), recoveredJob.Tenant())
}
// Should have shard files
foundShardFiles := 0
for i := range work.ShardCount {
shardPath := newScheduler.filepathForShard(uint8(i))
if _, err := os.Stat(shardPath); err == nil {
foundShardFiles++
}
}
require.Greater(t, foundShardFiles, 0, "Should have at least some shard files")
t.Logf("Found %d shard files", foundShardFiles)
}