{"record":{"id":"ef10cdff9f196e34","repo":"apache/druid","slug":"no-shuffle-for-stage-d","errorCode":null,"errorMessage":"No shuffle for stage[%d]","messagePattern":"No shuffle for stage\\[(.+?)\\]","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"multi-stage-query/src/main/java/org/apache/druid/msq/kernel/StageDefinition.java","lineNumber":401,"sourceCode":"  }\n\n  /**\n   * Generate partition boundaries for {@link ShuffleKind#GLOBAL_SORT} shuffles.\n   *\n   * @param collector     statistics collector, to be provided if {@link #mustGatherResultKeyStatistics()}\n   * @param maxPartitions maximum number of partitions to generate. On the controller, this is the value of\n   *                      {@link MultiStageQueryContext#getMaxPartitions(QueryContext)}. On workers, this method\n   *                      is only used when the number of partitions is determined ahead of time by the\n   *                      {@link ShuffleSpec}, so {@link Integer#MAX_VALUE} is typically provided for this parameter\n   *                      out of convenience.\n   */\n  public Either<Long, ClusterByPartitions> generatePartitionBoundariesForShuffle(\n      @Nullable ClusterByStatisticsCollector collector,\n      int maxPartitions\n  )\n  {\n    if (shuffleSpec == null) {\n      throw new ISE(\"No shuffle for stage[%d]\", getStageNumber());\n    } else if (shuffleSpec.kind() != ShuffleKind.GLOBAL_SORT) {\n      throw new ISE(\n          \"Shuffle of kind [%s] cannot generate partition boundaries for stage[%d]\",\n          shuffleSpec.kind(),\n          getStageNumber()\n      );\n    } else if (mustGatherResultKeyStatistics() && collector == null) {\n      throw new ISE(\"Statistics required, but not gathered for stage[%d]\", getStageNumber());\n    } else if (!mustGatherResultKeyStatistics() && collector != null) {\n      throw new ISE(\"Statistics gathered, but not required for stage[%d]\", getStageNumber());\n    } else {\n      return ((GlobalSortShuffleSpec) shuffleSpec).generatePartitionsForGlobalSort(collector, maxPartitions);\n    }\n  }\n\n  public ClusterByStatisticsCollector createResultKeyStatisticsCollector(\n      final FrameType frameType,\n      final int maxRetainedBytes","sourceCodeStart":383,"sourceCodeEnd":419,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/kernel/StageDefinition.java#L383-L419","documentation":"generatePartitionBoundariesForShuffle() computes shuffle partition boundaries from gathered cluster-by statistics and is only meaningful for stages with a GLOBAL_SORT shuffle spec. If the stage's shuffleSpec is null — the stage does not shuffle — ISE(\"No shuffle for stage[%d]\") is thrown with the stage number.","triggerScenarios":"WorkerStageKernel calling generatePartitionBoundariesForShuffle(...) on a stage whose definition has no shuffle spec (leaf/source stage), or test code invoking it directly on a non-shuffling StageDefinition.","commonSituations":"Custom worker logic or tooling that assumes every stage needs partition boundaries; unit tests reusing a builder without setting a shuffle spec.","solutions":["Only call generatePartitionBoundariesForShuffle on stages whose shuffle spec is non-null (check getShuffleSpecForDiagnostics or the definition)","Ensure the stage graph assigns a GLOBAL_SORT shuffle spec to stages that must produce partition boundaries","Fix the controller's stage-builder so leaf stages are not asked to generate boundaries"],"exampleFix":"// before\nboundaries = stageDef.generatePartitionBoundariesForShuffle(collector, maxParts);\n// after\nif (stageDef.getProcessor() != null && stageDef.hasShuffle()) {\n  boundaries = stageDef.generatePartitionBoundariesForShuffle(collector, maxParts);\n}","handlingStrategy":"validation","validationCode":"if (stageDef.getProcessor() == null || /* no shuffle spec */ true) {\n  throw new IllegalStateException(\"stage has no shuffle; skip boundary generation\");\n}","typeGuard":"boolean canGenerateBoundaries(StageDefinition d) {\n  try { return d.getShuffleSpecForDiagnostics() != null; } catch (Exception e) { return false; }\n}","tryCatchPattern":"try { boundaries = d.generatePartitionBoundariesForShuffle(c, maxP); } catch (IllegalStateException e) { boundaries = Either.value(0L); }","preventionTips":["Only call boundary generation for shuffle stages","Ensure the controller assigns GLOBAL_SORT specs to stages that need boundaries","Cover leaf stages in kernel unit tests"],"tags":["msq","shuffle","partitioning"],"backgroundTag":"unsupported-operation","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"}