apache/druid · error · ISE

segment[%s] and segment[%s] have the same rootPartitionRange

Error message

segment[%s] and segment[%s] have the same rootPartitionRange, but different minorVersion or atomicUpdateGroupSize

What it means

Thrown by verifyRootPartitionIsAdjacentAndAtomicUpdateGroupIsFull when two consecutive sorted segments share the exact same rootPartition range (same startRootPartitionId and endRootPartitionId) but differ in minorVersion or atomicUpdateGroupSize. Segments occupying the same root-partition slot must belong to one complete atomic-update group (same minor version and group size) so the overwrite can safely replace them together; disagreement indicates a corrupt or mixed shadowing state.

Source

Thrown at indexing-service/src/main/java/org/apache/druid/indexing/common/task/TaskLockHelper.java:280

    if (sortedSegments.isEmpty()) {
      return;
    }

    Preconditions.checkArgument(
        sortedSegments.stream().allMatch(segment -> segment.getInterval().equals(sortedSegments.get(0).getInterval()))
    );

    short atomicUpdateGroupSize = 1;
    // sanity check
    for (int i = 0; i < sortedSegments.size() - 1; i++) {
      final DataSegment curSegment = sortedSegments.get(i);
      final DataSegment nextSegment = sortedSegments.get(i + 1);
      if (curSegment.getStartRootPartitionId() == nextSegment.getStartRootPartitionId()
          && curSegment.getEndRootPartitionId() == nextSegment.getEndRootPartitionId()) {
        // Input segments should have the same or consecutive rootPartition range
        if (curSegment.getMinorVersion() != nextSegment.getMinorVersion()
            || curSegment.getAtomicUpdateGroupSize() != nextSegment.getAtomicUpdateGroupSize()) {
          throw new ISE(
              "segment[%s] and segment[%s] have the same rootPartitionRange, but different minorVersion or atomicUpdateGroupSize",
              curSegment,
              nextSegment
          );
        }
        atomicUpdateGroupSize++;
      } else {
        if (curSegment.getEndRootPartitionId() != nextSegment.getStartRootPartitionId()) {
          throw new ISE(
              "Can't compact segments of non-consecutive rootPartition range. Missing partitionIds between [%s] and [%s]",
              curSegment.getEndRootPartitionId(),
              nextSegment.getStartRootPartitionId()
          );
        }
        if (atomicUpdateGroupSize != curSegment.getAtomicUpdateGroupSize()) {
          throw new ISE(
              "All atomicUpdateGroup must be compacted together. Expected size[%s] but current size[%s]",
              curSegment.getAtomicUpdateGroupSize(),

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Include ALL segments of the affected rootPartitionRange (the full atomicUpdateGroup) in the task's input segment list.
  2. Inspect the segments (segment metadata query or segments table) sharing the rootPartitionRange and remove/overwrite stale ones with mismatched minorVersion via a full replace.
  3. Re-run compaction over the whole interval so all segments are rewritten into a single consistent generation.
  4. Check for concurrently running tasks over the same interval and cancel or wait for them before retrying.

Example fix

// before: partial input, one segment of an atomicUpdateGroup missing
List<DataSegment> input = segments.stream().filter(s -> s.getVersion().equals(targetVersion)).collect(toList());
verifyRootPartitionIsAdjacentAndAtomicUpdateGroupIsFull(input); // ISE

// after: take all segments in the interval so each group is complete
List<DataSegment> input = segmentCache.getUsedSegmentsForInterval(ds, interval);
verifyRootPartitionIsAdjacentAndAtomicUpdateGroupIsFull(input);
Defensive patterns

Strategy: validation

Validate before calling

// Java: group segments by rootPartitionRange and check consistent minorVersion/atomicUpdateGroupSize
Map<List<Integer>, List<DataSegment>> byRange = segments.stream()
    .collect(Collectors.groupingBy(s -> Arrays.asList(s.getStartRootPartitionId(), s.getEndRootPartitionId())));
for (List<DataSegment> group : byRange.values()) {
  long minorVers = group.stream().map(DataSegment::getMinorVersion).distinct().count();
  long sizes = group.stream().map(DataSegment::getAtomicUpdateGroupSize).distinct().count();
  if (minorVers != 1 || sizes != 1) throw new IllegalStateException("Mixed generation within a rootPartitionRange");
}

Try / catch

try {
  TaskLockHelper.verifyRootPartitionIsAdjacentAndAtomicUpdateGroupIsFull(sorted);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("same rootPartitionRange")) {
    // re-run full replace of the interval to rebuild a consistent generation
  } else { throw e; }
}

Prevention

When it happens

Trigger: verifyAndLockExistingSegments -> tryLockSegments -> verifyAndFindRootPartitionRangeAndMinorVersion with segments in one interval where two segments have identical rootPartitionRange but were written by different replace/append generations (different minorVersion) or with different maxRowsInMemory/partitioning, yielding different atomicUpdateGroupSize.

Common situations: Overlapping partial compactions that rewrote only part of an atomicUpdateGroup; concurrent replace tasks that published segments at different minorVersions for the same partition slot; corrupted/metadata-rebuilt segment lists; changing tuning partitioning (rows-per-partition) between partial compaction runs.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/824e5bbfa1f6bf71. Report an issue: GitHub.