{"record":{"id":"ffd760fac21e176f","repo":"apache/druid","slug":"shuffle-of-kind-s-cannot-generate-partition-bou","errorCode":null,"errorMessage":"Shuffle of kind [%s] cannot generate partition boundaries for stage[%d]","messagePattern":"Shuffle of kind \\[(.+?)\\] cannot generate partition boundaries 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":403,"sourceCode":"  /**\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\n  )\n  {","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/kernel/StageDefinition.java#L385-L421","documentation":"generatePartitionBoundariesForShuffle() only supports shuffle specs of kind GLOBAL_SORT, because partition boundaries are derived from global sort-key statistics. If shuffleSpec.kind() is any other ShuffleKind (e.g. HASH or BROADCAST), ISE(\"Shuffle of kind [%s] cannot generate partition boundaries for stage[%d]\") is thrown.","triggerScenarios":"Calling generatePartitionBoundariesForShuffle() on a stage whose shuffle spec is a hash/broadcast shuffle rather than GlobalSortShuffleSpec.","commonSituations":"Custom query tooling that applies boundary generation uniformly to all shuffle stages, or a query plan mixing hash shuffles with global-sort boundary logic.","solutions":["Check shuffleSpec.kind() == ShuffleKind.GLOBAL_SORT before calling; skip boundary generation for hash/broadcast shuffles","If global partitioning is required, replace the shuffle spec with GlobalSortShuffleSpec","Route hash-shuffled stages through a different code path that does not need cluster-by statistics"],"exampleFix":"// before\nboundaries = stageDef.generatePartitionBoundariesForShuffle(collector, maxParts);\n// after\nif (stageDef.getShuffleSpecForDiagnostics().kind() == ShuffleKind.GLOBAL_SORT) {\n  boundaries = stageDef.generatePartitionBoundariesForShuffle(collector, maxParts);\n}","handlingStrategy":"type-guard","validationCode":"if (stageDef.getShuffleSpecForDiagnostics().kind() != ShuffleKind.GLOBAL_SORT) {\n  throw new IllegalStateException(\"boundary generation requires GLOBAL_SORT shuffle\");\n}","typeGuard":"boolean isGlobalSort(StageDefinition d) {\n  return d.getShuffleSpecForDiagnostics().kind() == ShuffleKind.GLOBAL_SORT;\n}","tryCatchPattern":"try { boundaries = d.generatePartitionBoundariesForShuffle(c, maxP); } catch (IllegalStateException e) { /* route hash shuffles elsewhere */ }","preventionTips":["Gate boundary generation on ShuffleKind.GLOBAL_SORT","Handle hash/broadcast shuffles in separate code paths","Add kernel tests per shuffle kind"],"tags":["msq","shuffle","unsupported-operation"],"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"}