{"record":{"id":"e8d08147dca6f730","repo":"apache/druid","slug":"found-a-different-granularity-from-knownsegmentgra","errorCode":null,"errorMessage":"Found a different granularity from knownSegmentGranularity[%s] in segments[%s]","messagePattern":"Found a different granularity from knownSegmentGranularity\\[(.+?)\\] in segments\\[(.+?)\\]","errorType":"exception","errorClass":"ISE","httpStatus":null,"severity":"error","filePath":"indexing-service/src/main/java/org/apache/druid/indexing/common/task/TaskLockHelper.java","lineNumber":158,"sourceCode":"      return true;\n    }\n\n    verifySegmentGranularity(segmentsToLock);\n    return tryLockSegments(actionClient, segmentsToLock);\n  }\n\n  /**\n   * Check if segmentGranularity has changed.\n   */\n  private void verifySegmentGranularity(List<DataSegment> segments)\n  {\n    final Granularity granularityFromSegments = AbstractBatchIndexTask.findGranularityFromSegments(segments);\n    if (granularityFromSegments != null) {\n      if (knownSegmentGranularity == null) {\n        knownSegmentGranularity = granularityFromSegments;\n      } else {\n        if (!knownSegmentGranularity.equals(granularityFromSegments)) {\n          throw new ISE(\n              \"Found a different granularity from knownSegmentGranularity[%s] in segments[%s]\",\n              knownSegmentGranularity,\n              segments\n          );\n        }\n        final List<DataSegment> nonAlignedSegments = segments\n            .stream()\n            .filter(segment -> !knownSegmentGranularity.isAligned(segment.getInterval()))\n            .collect(Collectors.toList());\n\n        if (!nonAlignedSegments.isEmpty()) {\n          throw new ISE(\n              \"Non-aligned segments %s for granularity[%s]\",\n              SegmentUtils.commaSeparatedIdentifiers(nonAlignedSegments),\n              knownSegmentGranularity\n          );\n        }\n      }","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/indexing-service/src/main/java/org/apache/druid/indexing/common/task/TaskLockHelper.java#L140-L176","documentation":"TaskLockHelper.verifySegmentGranularity computes the single granularity shared by the existing segments to be locked/replaced and enforces that all segments share one granularity. When a second batch of segments exhibits a granularity different from the already-recorded knownSegmentGranularity, Druid throws ISE because mixed-granularity replacement would make lineage/lock bookkeeping ambiguous.","triggerScenarios":"verifyAndLockExistingSegments is called with segment sets of differing granularities for the same datasource — e.g. replace/compact tasks over intervals that contain both hourly and daily segments, or a granularitySpec segmentGranularity that changed between ingestion runs so the overlapped range holds mixed-granularity segments.","commonSituations":"Compaction over an interval spanning data ingested at different segmentGranularity settings; switching segmentGranularity in a spec and then replacing the old mixed range; reindexing a datasource that historically mixed granularities.","solutions":["Align the datasource by compacting the target interval so all segments share one granularity, then re-run the replace/compact task.","Set the task's segmentGranularity to match the granularity actually present in the segments being replaced.","Split the task into per-granularity intervals so each invocation only overlaps homogeneous-granularity segments."],"exampleFix":"// before\n// task spec with segmentGranularity=P1D over a range containing hourly+daily segments\n\"segmentGranularity\": \"day\"\n// after: first run a compact task to unify granularity, or match existing data\n\"segmentGranularity\": \"hour\" // matches knownSegmentGranularity found in segments","handlingStrategy":"validation","validationCode":"Granularity found = AbstractBatchIndexTask.findGranularityFromSegments(segments);\nif (found != null && !found.equals(expectedSegmentGranularity)) {\n  throw new IllegalArgumentException(\"Segment granularity mismatch: segments=\" + found + \" task=\" + expectedSegmentGranularity);\n}","typeGuard":"boolean hasUniformGranularity(List<DataSegment> segments) {\n  return AbstractBatchIndexTask.findGranularityFromSegments(segments) != null;\n}","tryCatchPattern":"try {\n  lockHelper.verifyAndLockExistingSegments(task, interval, segments);\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"Found a different granularity\")) {\n    log.error(\"Compact the interval to a single granularity before replacing\", e);\n  } else { throw e; }\n}","preventionTips":["Keep segmentGranularity stable across ingestion runs for a datasource","Compact mixed-granularity ranges before running replace/append tasks","Scope tasks to intervals containing only one granularity of segments"],"tags":["druid","segment-granularity","locking","replace-task"],"backgroundTag":"invalid-argument-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}