apache/druid · error · ISE

Found different granularities in segments

Error message

Found different granularities in segments %s

What it means

Thrown by TaskLockHelper.verifySegmentGranularity when AbstractBatchIndexTask.findGranularityFromSegments returns null, meaning the input segments passed to a segment-lock batch/compaction task do not share a single inferable segment granularity. The helper must establish one known granularity before locking existing segments for overwrite; if segments in the batch disagree on granularity, none can be inferred and the task aborts. This guard ensures output segments can overshadow all input segments consistently.

Solutions

  1. Set the compaction/batch task input to segments sharing one granularity (filter by interval so all segments align to a single period).
  2. If granularities differ intentionally, run separate compaction/overwrite tasks per granularity group.
  3. Unify the datasource granularity by re-ingesting or compacting so all segments share the same segmentGranularity going forward.
  4. Check the task's granularitySpec/segmentGranularity config matches the granularity actually present in the input segments.

Example fix

// before: mixed segments fed to one compaction task
List<DataSegment> segments = getSegments(ds); // contains day- and hour-granular segments
helper.verifyAndLockExistingSegments(client, segments);

// after: group by inferred granularity and compact each group separately
Map<Granularity, List<DataSegment>> byGran = groupByGranularity(getSegments(ds));
byGran.values().forEach(g -> helper.verifyAndLockExistingSegments(client, g));
Defensive patterns

Strategy: validation

Validate before calling

// Java: verify all segments infer to one granularity before submitting the task
Granularity g = AbstractBatchIndexTask.findGranularityFromSegments(inputSegments);
if (g == null) {
  throw new IllegalStateException("Input segments span multiple granularities; split the batch by granularity");
}

Try / catch

try {
  taskClient.runCompaction(ds, interval);
} catch (IllegalStateException e) {
  if (e.getMessage().contains("Found different granularities")) {
    // split input segments by granularity and run one task per group
  } else { throw e; }
}

Prevention

When it happens

Trigger: verifyAndLockExistingSegments -> verifySegmentGranularity with a list of existing segments whose intervals imply more than one granularity (e.g. a mix of 'day' and 'hour' aligned segments, or segments spanning inconsistent interval boundaries).

Common situations: Compaction or batch overwrite configured against a datasource whose segments were ingested with different segmentGranularity settings over time (e.g. historical data at 'month', newer data at 'day'); manual segment lists assembled from mixed intervals; retuning granularitySpec.segmentGranularity while old segments remain.

Related errors


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

Appendix: source

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

              knownSegmentGranularity,
              segments
          );
        }
        final List<DataSegment> nonAlignedSegments = segments
            .stream()
            .filter(segment -> !knownSegmentGranularity.isAligned(segment.getInterval()))
            .collect(Collectors.toList());

        if (!nonAlignedSegments.isEmpty()) {
          throw new ISE(
              "Non-aligned segments %s for granularity[%s]",
              SegmentUtils.commaSeparatedIdentifiers(nonAlignedSegments),
              knownSegmentGranularity
          );
        }
      }
    } else {
      throw new ISE(
          "Found different granularities in segments %s",
          SegmentUtils.commaSeparatedIdentifiers(segments)
      );
    }
  }

  private boolean tryLockSegments(TaskActionClient actionClient, List<DataSegment> segments) throws IOException
  {
    final Map<Interval, List<DataSegment>> intervalToSegments = SegmentUtils.groupSegmentsByInterval(segments);
    for (Entry<Interval, List<DataSegment>> entry : intervalToSegments.entrySet()) {
      final Interval interval = entry.getKey();
      final List<DataSegment> segmentsInInterval = entry.getValue();
      final boolean hasSameVersion = segmentsInInterval
          .stream()
          .allMatch(segment -> segment.getVersion().equals(segmentsInInterval.get(0).getVersion()));
      Preconditions.checkState(
          hasSameVersion,
          "Segments %s should have same version",

View on GitHub (pinned to 9b90983fd2)