{"record":{"id":"55b333252e707380","repo":"apache/druid","slug":"interval-s-does-not-encapsulate-the-full-range-o","errorCode":null,"errorMessage":"interval[%s] does not encapsulate the full range of timestamps[%s, %s]","messagePattern":"interval\\[(.+?)\\] does not encapsulate the full range of timestamps\\[(.+?), (.+?)\\]","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/segment/IndexMergerBase.java","lineNumber":143,"sourceCode":"  public File persist(\n      final IncrementalIndex index,\n      final Interval dataInterval,\n      File outDir,\n      IndexSpec indexSpec,\n      ProgressIndicator progress,\n      @Nullable SegmentWriteOutMediumFactory segmentWriteOutMediumFactory\n  ) throws IOException\n  {\n    if (index.isEmpty()) {\n      throw new IAE(\"Trying to persist an empty index!\");\n    }\n\n    indexSpec = indexSpec.getEffectiveSpec();\n\n    final DateTime firstTimestamp = index.getMinTime();\n    final DateTime lastTimestamp = index.getMaxTime();\n    if (!(dataInterval.contains(firstTimestamp) && dataInterval.contains(lastTimestamp))) {\n      throw new IAE(\n          \"interval[%s] does not encapsulate the full range of timestamps[%s, %s]\",\n          dataInterval,\n          firstTimestamp,\n          lastTimestamp\n      );\n    }\n\n    FileUtils.mkdirp(outDir);\n\n    log.debug(\"Starting persist for interval[%s], rows[%,d]\", dataInterval, index.numRows());\n    return multiphaseMerge(\n        Collections.singletonList(\n            new IncrementalIndexAdapter(\n                dataInterval,\n                index,\n                indexSpec.getBitmapSerdeFactory().getBitmapFactory()\n            )\n        ),","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/segment/IndexMergerBase.java#L125-L161","documentation":"persist verifies that the caller-supplied dataInterval fully covers the index's actual min and max timestamps. If any row falls outside the declared interval, the resulting segment's metadata would be wrong, so Druid throws IAE. This protects segment interval invariants used by the coordinator and query planning.","triggerScenarios":"Calling persist with a dataInterval that starts after index.getMinTime() or ends before index.getMaxTime() — e.g. event timestamps outside the configured segment granularity window, often due to timezone or UTC-offset mistakes.","commonSituations":"Rolling windows with events arriving outside the shard interval; timestamp parsing that misreads timezone offsets; hand-rolled ingestion code that computes dataInterval from the query range rather than actual data.","solutions":["Compute dataInterval to fully enclose index.getMinTime()/getMaxTime() (e.g. pad to segment granularity) before persisting","Fix timestamp parsing/timezone handling so events land in the expected interval","Sanitize or reject out-of-window events at ingestion instead of relying on the interval to clip them"],"exampleFix":"// before\nInterval interval = new Interval(\"2024-01-01/2024-01-02\");\nmerger.persist(index, interval, outDir, indexSpec, progress, null);\n// after\nInterval interval = new Interval(\n    index.getMinTime().toString().substring(0, 10),\n    index.getMaxTime().plusDays(1).toString().substring(0, 10));\nif (interval.contains(index.getMinTime()) && interval.contains(index.getMaxTime())) {\n  merger.persist(index, interval, outDir, indexSpec, progress, null);\n}","handlingStrategy":"validation","validationCode":"Interval safe = new Interval(\n    index.getMinTime().toString(), index.getMaxTime().plusMillis(1).toString());\nif (!dataInterval.contains(index.getMinTime())\n    || !dataInterval.contains(index.getMaxTime())) {\n  throw new IllegalStateException(\"dataInterval \" + dataInterval\n      + \" does not cover [\" + index.getMinTime() + \", \" + index.getMaxTime() + \"]\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  merger.persist(index, dataInterval, outDir, indexSpec, progress, null);\n} catch (IAE e) {\n  if (e.getMessage().contains(\"does not encapsulate\")) {\n    // widen dataInterval to cover actual timestamps and retry\n  }\n}","preventionTips":["Derive dataInterval from actual min/max timestamps, not the requested query window","Normalize timestamps to UTC before adding to the index","Validate timestamp parsing/timezone config in ingestion specs"],"tags":["druid","index-merger","interval","timestamps"],"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"}