apache/druid · error · IllegalStateException

Root partition range

Error message

Root partition range[%d, %d] of new segments doesn't match to root partition range[%d, %d] of old segments

What it means

SegmentTransactionalInsertAction.checkWithSegmentLock() validates that when new segments replace old segments for an interval, the new segments' root partition range exactly matches the old ones'. Root partition ids define top-level chunk boundaries in the segment partitioning lineage; a mismatch means the publish would change the core partition layout of existing data, which core partition sets forbid. Druid throws this ISE to protect lineage consistency.

Solutions

  1. Align the new taskspec's partitioning (num SHARDS/root partitions) with the existing segments for that interval, or use a higher minor version replace that covers the full root partition range.
  2. Reset/redo the interval: kill the old segments and re-ingest with the desired partitioning.
  3. Check the publish/replace spec so it targets the same root partitions as the segments being replaced.
  4. Verify you are not replaying a stale task payload referencing old partitioning.

Example fix

null
Defensive patterns

Strategy: validation

Validate before calling

int oldStart = oldSegments.get(0).getStartRootPartitionId();
int oldEnd   = oldSegments.get(oldSegments.size()-1).getEndRootPartitionId();
int newStart = newSegments.get(0).getStartRootPartitionId();
int newEnd   = newSegments.get(newSegments.size()-1).getEndRootPartitionId();
if (oldStart != newStart || oldEnd != newEnd) {
  throw new IllegalStateException("root partition range mismatch: fix partitioning spec or kill+re-ingest");
}

Try / catch

try { result = action.perform(task, toolbox); } catch (ISE e) { if (e.getMessage().contains("Root partition range")) { /* align partitioning or re-ingest interval */ } else throw e; }

Prevention

When it happens

Trigger: Publishing append/replace segments whose start/end root partition ids differ from the existing segments for the same interval — e.g. an overwrite with different partitioning than the original ingestion, or a task retry with a stale segment set.

Common situations: Re-running compaction with a different partitioning config against an interval already compacted differently; supervisor tasks restarted after the segment layout changed; hand-edited or stale taskspec reusing old segment ids.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at indexing-service/src/main/java/org/apache/druid/indexing/common/actions/SegmentTransactionalInsertAction.java:293

    oldSegmentsMap.values().forEach(TaskLockHelper::verifyRootPartitionIsAdjacentAndAtomicUpdateGroupIsFull);
    newSegmentsMap.values().forEach(TaskLockHelper::verifyRootPartitionIsAdjacentAndAtomicUpdateGroupIsFull);

    oldSegmentsMap.forEach((interval, oldSegmentsPerInterval) -> {
      final List<DataSegment> newSegmentsPerInterval = Preconditions.checkNotNull(
          newSegmentsMap.get(interval),
          "segments of interval[%s]",
          interval
      );
      // These lists are already sorted in groupSegmentsByIntervalAndSort().
      final int oldStartRootPartitionId = oldSegmentsPerInterval.get(0).getStartRootPartitionId();
      final int oldEndRootPartitionId = oldSegmentsPerInterval.get(oldSegmentsPerInterval.size() - 1)
                                                              .getEndRootPartitionId();
      final int newStartRootPartitionId = newSegmentsPerInterval.get(0).getStartRootPartitionId();
      final int newEndRootPartitionId = newSegmentsPerInterval.get(newSegmentsPerInterval.size() - 1)
                                                              .getEndRootPartitionId();

      if (oldStartRootPartitionId != newStartRootPartitionId || oldEndRootPartitionId != newEndRootPartitionId) {
        throw new ISE(
            "Root partition range[%d, %d] of new segments doesn't match to root partition range[%d, %d] of old segments",
            newStartRootPartitionId,
            newEndRootPartitionId,
            oldStartRootPartitionId,
            oldEndRootPartitionId
        );
      }

      newSegmentsPerInterval
          .forEach(eachNewSegment -> oldSegmentsPerInterval
              .forEach(eachOldSegment -> {
                if (eachNewSegment.getMinorVersion() <= eachOldSegment.getMinorVersion()) {
                  throw new ISE(
                      "New segment[%s] have a smaller minor version than old segment[%s]",
                      eachNewSegment,
                      eachOldSegment
                  );
                }

View on GitHub (pinned to 9b90983fd2)