{"record":{"id":"904098a7eacfeaa4","repo":"apache/druid","slug":"unused-segment-s-has-version-s-task-version-904098","errorCode":null,"errorMessage":"Unused segment[%s] has version[%s] > task version[%s]","messagePattern":"Unused segment\\[(.+?)\\] has version\\[(.+?)\\] > task version\\[(.+?)\\]","errorType":"exception","errorClass":"ISE","httpStatus":null,"severity":"error","filePath":"indexing-service/src/main/java/org/apache/druid/indexing/common/task/RestoreTask.java","lineNumber":89,"sourceCode":"  public Set<ResourceAction> getInputSourceResources()\n  {\n    return ImmutableSet.of();\n  }\n\n  @Override\n  public TaskStatus runTask(TaskToolbox toolbox) throws Exception\n  {\n    final TaskLock myLock = getAndCheckLock(toolbox);\n\n    // List unused segments\n    final List<DataSegment> unusedSegments = toolbox\n        .getTaskActionClient()\n        .submit(new RetrieveUnusedSegmentsAction(myLock.getDataSource(), myLock.getInterval(), null, null, null));\n\n    // Verify none of these segments have versions > lock version\n    for (final DataSegment unusedSegment : unusedSegments) {\n      if (unusedSegment.getVersion().compareTo(myLock.getVersion()) > 0) {\n        throw new ISE(\n            \"Unused segment[%s] has version[%s] > task version[%s]\",\n            unusedSegment.getId(),\n            unusedSegment.getVersion(),\n            myLock.getVersion()\n        );\n      }\n\n      log.info(\"OK to restore segment: %s\", unusedSegment.getId());\n    }\n\n    final List<DataSegment> restoredSegments = new ArrayList<>();\n\n    // Move segments\n    for (DataSegment segment : unusedSegments) {\n      final DataSegment restored = toolbox.getDataSegmentArchiver().restore(segment);\n      if (restored != null) {\n        restoredSegments.add(restored);\n      } else {","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/indexing-service/src/main/java/org/apache/druid/indexing/common/task/RestoreTask.java#L71-L107","documentation":"RestoreTask verifies that every unused segment it is about to restore within its lock interval has a segment version less than or equal to the version of the task's time-partition lock. A segment with a newer version would be overwritten by restoring older data, which would violate Druid's versioning-based concurrency model (newer data must never be shadowed by older restores). The task throws ISE to abort the restore rather than silently clobber newer segments.","triggerScenarios":"Running RestoreTask on a datasource/interval where, after the segments were marked unused, another task (e.g. a compaction or replacement task) has written segments with a higher version into the same interval that were themselves later marked unused, so RetrieveUnusedSegmentsAction returns segments whose version exceeds the lock version held by RestoreTask.","commonSituations":"Compaction or re-indexing ran between the original mark-unused and the restore attempt; manual segment version bumps; replaying old restore tasks after other ingestion tasks upgraded the segment version in the same interval.","solutions":["Inspect the unused segments (sys.segments table or coordinator API) and identify which have versions newer than the RestoreTask lock version; decide whether the restore is still intended.","If the newer unused segments are not needed, verify their creation history and re-run RestoreTask only for intervals where no newer-version unused segments exist.","If the restore is stale, drop/skip it — restoring would overwrite newer data; re-ingest the data instead to produce segments with a version newer than the conflicting ones.","Upgrade the lock version of the restore task (e.g. via a new task with a higher version / useLineageBasedSegmentAllocation or explicit version) so it dominates the unused segments' versions."],"exampleFix":"// before\nTaskLock myLock = ...; // old lock version\nsubmitRestoreTask(lock); // ISE if unused segments have newer versions\n// after\n// first check versions via coordinator API, then restore only safe intervals\nif (unusedSegments.stream().allMatch(s -> s.getVersion().compareTo(myLock.getVersion()) <= 0)) {\n  submitRestoreTask(myLock);\n}","handlingStrategy":"validation","validationCode":"List<DataSegment> unused = taskActionClient.submit(new RetrieveUnusedSegmentsAction(dataSource, interval, null, null, null));\nboolean safe = unused.stream().allMatch(s -> s.getVersion().compareTo(lock.getVersion()) <= 0);\nif (!safe) { throw new IllegalStateException(\"Restore aborted: newer-version unused segments present\"); }","typeGuard":"boolean isRestorable(DataSegment s, TaskLock lock) { return s.getVersion().compareTo(lock.getVersion()) <= 0; }","tryCatchPattern":"try {\n  runRestoreTask();\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"has version\")) {\n    log.error(\"Conflicting newer unused segments; skipping restore\", e);\n  } else { throw e; }\n}","preventionTips":["Check sys.segments for unused segments with versions newer than your lock before restoring","Avoid interleaving compaction tasks with restore tasks over the same interval","Restore promptly after marking segments unused, before other ingestion touches the interval"],"tags":["druid","segment-versioning","restore-task","locking"],"backgroundTag":"invalid-state-transition","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"}