{"record":{"id":"cf990561cfb30827","repo":"apache/druid","slug":"cannot-set-total-mergers-for-level-d-valid-level","errorCode":null,"errorMessage":"Cannot set total mergers for level %d. Valid levels range from 0 to %d","messagePattern":"Cannot set total mergers for level (.+?)\\. Valid levels range from 0 to (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/frame/processor/SuperSorterProgressTracker.java","lineNumber":125,"sourceCode":"            totalMergingLevels\n        );\n      }\n    });\n\n    this.totalMergingLevels = totalMergingLevels;\n  }\n\n  /**\n   * Sets the total mergers for a level. Can be set only once, except for the ultimate level (if total levels are known)\n   * because they get overridden by totalMergersForUltimateLevel\n   */\n  public synchronized void setTotalMergersForLevel(final int level, final long totalMergers)\n  {\n    if (level < 0) {\n      throw new ISE(\"Unable to set %d total mergers for level %d. Level must be non-negative\", totalMergers, level);\n    }\n    if (totalMergingLevels != SuperSorter.UNKNOWN_LEVEL && level >= totalMergingLevels) {\n      throw new ISE(\n          \"Cannot set total mergers for level %d. Valid levels range from 0 to %d\",\n          level,\n          totalMergingLevels - 1\n      );\n    }\n    if (totalMergingLevels != SuperSorter.UNKNOWN_LEVEL\n        && level < totalMergingLevels - 1 // This condition is only present for levels excluding the ultimate level\n        && levelToTotalBatches.containsKey(level)) {\n      throw new ISE(\"Total mergers are already present for the level %d\", level);\n    }\n    levelToTotalBatches.put(level, totalMergers);\n  }\n\n  /**\n   * Sets the number of mergers in the ultimate level (number of mergers = number of output partitions).\n   * Can only be set once\n   */\n  public synchronized void setTotalMergersForUltimateLevel(final long totalMergersForUltimateLevel)","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/frame/processor/SuperSorterProgressTracker.java#L107-L143","documentation":"setTotalMergersForLevel throws this when the total merging level count is already known and the given level is at or beyond it. Once totalMergingLevels is fixed, only levels 0..totalMergingLevels-1 are valid targets for merger totals; anything else means the caller's level numbering no longer matches the defined hierarchy.","triggerScenarios":"Calling setTotalMergersForLevel(level, mergers) with level >= totalMergingLevels after setTotalMergingLevels ran; e.g., the ultimate level's count is being registered as a regular level, or the level count was reduced after merger registration started.","commonSituations":"Off-by-one confusion around the ultimate level (totalMergingLevels-1) in custom sorter code; dynamic partition changes shrinking levels while merge scheduling continues with old indices.","solutions":["Register the ultimate-level merger count via setTotalMergersForUltimateLevel instead of setTotalMergersForLevel for level == totalMergingLevels.","Validate level < totalMergingLevels at the call site before setting.","Recompute totalMergingLevels so it covers all levels the scheduler will touch, and set it before scheduling.","Check off-by-one comparisons against totalMergingLevels - 1 in the merge driver."],"exampleFix":"// before\ntracker.setTotalMergersForLevel(totalMergingLevels, outputPartitions); // ultimate level\n// after\ntracker.setTotalMergersForUltimateLevel(outputPartitions);","handlingStrategy":"validation","validationCode":"int total = tracker.getTotalMergingLevels();\nif (total == SuperSorter.UNKNOWN_LEVEL || level < total) {\n  tracker.setTotalMergersForLevel(level, mergers);\n}","typeGuard":"boolean isRegularLevel(int level, int totalMergingLevels) {\n  return totalMergingLevels != SuperSorter.UNKNOWN_LEVEL && level >= 0 && level < totalMergingLevels - 1;\n}","tryCatchPattern":null,"preventionTips":["Use setTotalMergersForUltimateLevel for the top level, not setTotalMergersForLevel.","Check level bounds against totalMergingLevels before registering.","Beware off-by-one at the ultimate level: its index is totalMergingLevels - 1."],"tags":["internal-state","index-out-of-range","sorting"],"backgroundTag":"argument-out-of-range","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}