{"record":{"id":"b601d18681877e7b","repo":"apache/druid","slug":"locks-s-for-task-s-can-t-cover-segments-s","errorCode":null,"errorMessage":"Locks[%s] for task[%s] can't cover segments[%s]","messagePattern":"Locks\\[(.+?)\\] for task\\[(.+?)\\] can't cover segments\\[(.+?)\\]","errorType":"exception","errorClass":"ISE","httpStatus":null,"severity":"error","filePath":"indexing-service/src/main/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTask.java","lineNumber":274,"sourceCode":"        // No more segments eligible for kill, do not proceed further\n        break;\n      }\n\n      // Fetch locks each time as a revokal could have occurred in between batches\n      final NavigableMap<DateTime, List<TaskLock>> taskLockMap\n              = getNonRevokedTaskLockMap(toolbox.getTaskActionClient());\n\n      final Set<DataSegment> unusedSegments = unusedSegmentsPlus.stream()\n                                                                .map(DataSegmentPlus::getDataSegment)\n                                                                .collect(Collectors.toSet());\n      final Map<String, DataSegmentPlus> unusedIdToSegmentPlus = CollectionUtils.toMap(\n          unusedSegmentsPlus,\n          segment -> segment.getDataSegment().getId().toString(),\n          Function.identity()\n      );\n\n      if (!TaskLocks.isLockCoversSegments(taskLockMap, unusedSegments)) {\n        throw new ISE(\n                \"Locks[%s] for task[%s] can't cover segments[%s]\",\n                taskLockMap.values().stream().flatMap(List::stream).collect(Collectors.toList()),\n                getId(),\n                unusedSegments\n        );\n      }\n\n      // Kill segments - order of steps 1, 2, 3, 4 must remain the same\n\n      // 1. Determine parent segment ids of killable unused segments\n      final Map<String, String> upgradedFromSegmentIds\n          = fetchParentIdsForSegments(toolbox, unusedIdToSegmentPlus);\n\n      // 2. Identify killable segments whose load specs are not shared with any other segment\n      final List<DataSegment> segmentsToKillFromDeepStore = getKillableSegments(\n          unusedIdToSegmentPlus,\n          upgradedFromSegmentIds,\n          usedSegmentLoadSpecs,","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/indexing-service/src/main/java/org/apache/druid/indexing/common/task/KillUnusedSegmentsTask.java#L256-L292","documentation":"KillUnusedSegmentsTask verifies, before deleting unused segments, that the task's locks actually cover every segment interval it intends to kill. If some unused segments fall outside the locked intervals, the run aborts to prevent deleting data the task has no write-authority over.","triggerScenarios":"Calling kill with an interval/partitions filter whose matching unused segments extend beyond the intervals acquired by the task's locks (e.g. segment interval wider than the kill interval, or lock revoked/shrunk during the run).","commonSituations":"Kill tasks submitted with intervals that don't exactly match segment intervals (misaligned segmentGranularity); concurrent compaction/replacement changing segment intervals; automatic lock downgrades while the kill task runs.","solutions":["Re-submit the kill task with an interval that fully covers (and is aligned to) the segments to be killed, or enable 'markAsUsed' null + limit filters so locks cover candidates","Check the Overlord lock table for the task's acquired locks and compare with the segments in the error message","Retry after any concurrent compaction/kill finishes so locks are granted over the full interval"],"exampleFix":"// before\nKillUnusedSegmentsTask kill = new KillUnusedSegmentsTask(null, null, Ids,\n    Intervals.of(\"2024-01-05/2024-01-07\"), null, null, null);\n// after (interval widened to cover full segment intervals)\nKillUnusedSegmentsTask kill = new KillUnusedSegmentsTask(null, null, Ids,\n    Intervals.of(\"2024-01-01/2024-01-15\"), null, null, null);","handlingStrategy":"validation","validationCode":"// before submitting kill: ensure interval covers all candidate segments\nInterval aligned = Intervals.utc(granularity.bucketStart(raw.getStart()).getMillis(), granularity.bucketEnd(raw.getEnd().minus(1)).getMillis());","typeGuard":null,"tryCatchPattern":"try { killTask.runTask(actionClient); } catch (ISE e) { if (e.getMessage().contains(\"can't cover segments\")) { resubmitWithWiderInterval(); } else { throw e; } }","preventionTips":["Align kill intervals to segment granularity buckets","Avoid running kill concurrently with compaction over the same interval","Check the Overlord lock table before submitting kill tasks"],"tags":["druid","locking","segments","kill-task"],"backgroundTag":"insufficient-permissions","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}