{"record":{"id":"b48a1d2d4f10634a","repo":"apache/druid","slug":"partitions-must-all-abut-each-other","errorCode":null,"errorMessage":"Partitions must all abut each other","messagePattern":"Partitions must all abut each other","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/frame/processor/FrameChannelMerger.java","lineNumber":123,"sourceCode":"      final WritableFrameChannel outputChannel,\n      final FrameWriterFactory frameWriterFactory,\n      final List<KeyColumn> sortKey,\n      @Nullable final FrameCombiner combiner,\n      @Nullable final ClusterByPartitions partitions,\n      final long rowLimit\n  )\n  {\n    if (inputChannels.isEmpty()) {\n      throw new IAE(\"Must have at least one input channel\");\n    }\n\n    final ClusterByPartitions partitionsToUse =\n        partitions == null ? ClusterByPartitions.oneUniversalPartition() : partitions;\n\n    if (!partitionsToUse.allAbutting()) {\n      // To simplify merging logic, when frames we only look at the earliest and latest key in \"partitions\". To ensure\n      // correctness, we need to verify that there are no gaps.\n      throw new IAE(\"Partitions must all abut each other\");\n    }\n\n    if (!sortKey.stream().allMatch(keyColumn -> keyColumn.order().sortable())) {\n      throw new IAE(\"Key is not sortable\");\n    }\n\n    this.inputChannels = inputChannels;\n    this.outputChannel = outputChannel;\n    this.frameReader = frameReader;\n    this.frameWriterFactory = frameWriterFactory;\n    this.sortKey = sortKey;\n    this.partitions = partitionsToUse;\n    this.rowLimit = rowLimit;\n    this.currentFrames = new FramePlus[inputChannels.size()];\n    this.remainingChannels = new IntAVLTreeSet(IntSets.fromTo(0, inputChannels.size()));\n    this.tournamentTree = new TournamentTree(\n        inputChannels.size(),\n        (k1, k2) -> {","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/frame/processor/FrameChannelMerger.java#L105-L141","documentation":"FrameChannelMerger optimizes merging by only inspecting the earliest and latest key of the supplied ClusterByPartitions. That shortcut is only correct if the partition ranges have no gaps between them, so the constructor verifies partitionsToUse.allAbutting() and throws IAE otherwise. Callers must supply contiguous partition bounds.","triggerScenarios":"Constructing a FrameChannelMerger with a ClusterByPartitions whose ranges do not abut, e.g. partitions [0,10) and [20,30) with a gap (10,20), typically from manually built partition bounds or bounds filtered after generation.","commonSituations":"Custom partition-bound construction in stage/worker assignment code; dropping intermediate partitions when a worker fails; passing user-derived partition boundaries that skip ranges.","solutions":["Use partition bounds produced by the standard clustering/partitioning code (e.g. from a previous ClusterBy stage) so ranges are contiguous.","Validate ClusterByPartitions.allAbutting() before constructing the merger and repair gaps or use the full set.","If partitions came from a filtered set, merge adjacent ranges or reject the set instead of passing it to the merger."],"exampleFix":"// before\nFrameChannelMerger merger = new FrameChannelMerger(allocator, sortKey, channels, null, filteredPartitions, rowLimit);\n// after\nif (!filteredPartitions.allAbutting()) {\n  throw new IllegalStateException(\"Partitions have gaps; cannot merge: \" + filteredPartitions);\n}\nFrameChannelMerger merger = new FrameChannelMerger(allocator, sortKey, channels, null, filteredPartitions, rowLimit);","handlingStrategy":"validation","validationCode":"if (partitions != null && !partitions.allAbutting()) {\n  throw new IllegalArgumentException(\"Partitions have gaps: \" + partitions);\n}","typeGuard":"boolean mergeable(ClusterByPartitions p) { return p == null || p.allAbutting(); }","tryCatchPattern":"try {\n  merger = new FrameChannelMerger(allocator, sortKey, channels, null, partitions, rowLimit);\n} catch (IllegalArgumentException e) {\n  // regenerate partition bounds contiguously\n}","preventionTips":["Always derive partition bounds from the standard partitioning stages.","Never filter partitions without re-checking contiguity.","Call allAbutting() as a precondition before merge operations."],"tags":["java","argument-validation","partitioning"],"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-14T11:17:12.474Z"}