package plugin import ( "sync" "time" ) // SchedulerLane identifies an independent scheduling track. Each lane runs // its own goroutine and maintains its own detection timing so that // workloads in different lanes never block each other. type SchedulerLane string const ( // LaneDefault handles volume management operations (vacuum, balance, // erasure coding) and admin scripts. It is the fallback lane for any // job type that is not explicitly mapped elsewhere. LaneDefault SchedulerLane = "default" // LaneIceberg handles table-bucket Iceberg compaction and maintenance. LaneIceberg SchedulerLane = "iceberg" // LaneLifecycle handles S3 object store lifecycle management // (expiration, transition, abort incomplete multipart uploads). LaneLifecycle SchedulerLane = "lifecycle" // LaneLance handles table-bucket Lance maintenance: fragment compaction, // index optimization and version cleanup. LaneLance SchedulerLane = "lance" ) // AllLanes returns every defined scheduler lane in a stable order. func AllLanes() []SchedulerLane { return []SchedulerLane{LaneDefault, LaneIceberg, LaneLifecycle, LaneLance} } // laneIdleSleep maps each lane to its default idle sleep duration. // Each lane can sleep for a different amount when no work is detected, // independent of the per-job-type DetectionInterval. var laneIdleSleep = map[SchedulerLane]time.Duration{ LaneDefault: 61 * time.Second, LaneIceberg: 61 * time.Second, LaneLifecycle: 5 * time.Minute, LaneLance: 61 * time.Second, } // laneRequiresLock maps each lane to whether its job types must be // serialised and run under the shared cluster admin lock. The default // lane needs this because volume-management operations share global // state; the lock is taken around each detection and each job so manual // shell operations can interleave. Other lanes run each job type // independently without the lock. var laneRequiresLock = map[SchedulerLane]bool{ LaneDefault: true, LaneIceberg: false, LaneLifecycle: false, // Lance maintenance rewrites files inside one table and shares no global // state, so it has no more need of the cluster admin lock than Iceberg does. LaneLance: false, } // LaneRequiresLock returns true if the given lane serialises its job types // and runs them under the cluster admin lock. Unknown lanes default to true. func LaneRequiresLock(lane SchedulerLane) bool { if v, ok := laneRequiresLock[lane]; ok { return v } return true } // LaneIdleSleep returns the idle sleep duration for the given lane, // falling back to defaultSchedulerIdleSleep if the lane is unknown. func LaneIdleSleep(lane SchedulerLane) time.Duration { if d, ok := laneIdleSleep[lane]; ok { return d } return defaultSchedulerIdleSleep } // jobTypeLaneMap is the hardcoded mapping from job type to scheduler lane. // Job types not present here are assigned to LaneDefault. var jobTypeLaneMap = map[string]SchedulerLane{ // Volume management (default lane) "vacuum": LaneDefault, "volume_balance": LaneDefault, "ec_balance": LaneDefault, "erasure_coding": LaneDefault, "admin_script": LaneDefault, // Iceberg table maintenance "iceberg_maintenance": LaneIceberg, // S3 lifecycle management "s3_lifecycle": LaneLifecycle, // Lance table maintenance. Without these the job types fall back to the // default lane, which serialises everything under the cluster admin lock // and would queue a compaction behind volume balancing. "lance_compact": LaneLance, "lance_optimize_indices": LaneLance, "lance_cleanup_versions": LaneLance, } // JobTypeLane returns the scheduler lane for the given job type. // Unknown job types are assigned to LaneDefault. func JobTypeLane(jobType string) SchedulerLane { if lane, ok := jobTypeLaneMap[jobType]; ok { return lane } return LaneDefault } // LaneJobTypes returns the set of known job types assigned to the given lane. func LaneJobTypes(lane SchedulerLane) []string { var result []string for jobType, l := range jobTypeLaneMap { if l == lane { result = append(result, jobType) } } return result } // schedulerLaneState holds the per-lane runtime state used by the scheduler. type schedulerLaneState struct { lane SchedulerLane wakeCh chan struct{} loopMu sync.Mutex loop schedulerLoopState // Per-lane execution reservation pool. Each lane tracks how many // execution slots it has reserved on each worker independently, // so lanes cannot starve each other. execMu sync.Mutex execRes map[string]int } // newLaneState creates a schedulerLaneState for the given lane. func newLaneState(lane SchedulerLane) *schedulerLaneState { return &schedulerLaneState{ lane: lane, wakeCh: make(chan struct{}, 1), execRes: make(map[string]int), } }