{"record":{"id":"59766bcfdc3efe2e","repo":"apache/druid","slug":"cannot-add-merged-batches-for-level-d-valid-leve","errorCode":null,"errorMessage":"Cannot add merged batches for level %d. Valid levels range from 0 to %d","messagePattern":"Cannot add merged batches 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":158,"sourceCode":"   * 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)\n  {\n    if (this.totalMergersForUltimateLevel != SuperSorter.UNKNOWN_TOTAL) {\n      throw new ISE(\"Cannot set mergers for final level more than once\");\n    }\n    this.totalMergersForUltimateLevel = totalMergersForUltimateLevel;\n  }\n\n  /**\n   * This method is designed to be called during the course of the sorting. The batches once merged for a particular\n   * level can be marked as such through this.\n   */\n  public synchronized void addMergedBatchesForLevel(final int level, final long additionalMergedBatches)\n  {\n    if (totalMergingLevels != SuperSorter.UNKNOWN_LEVEL && level >= totalMergingLevels) {\n      throw new ISE(\n          \"Cannot add merged batches for level %d. Valid levels range from 0 to %d\",\n          level,\n          totalMergingLevels - 1\n      );\n    }\n    levelToMergedBatches.compute(level, (l, mergedBatchesSoFar) -> mergedBatchesSoFar == null\n                                                                   ? additionalMergedBatches\n                                                                   : additionalMergedBatches + mergedBatchesSoFar);\n  }\n\n  /**\n   * If the SuperSorter is trivially done without doing any work (for eg - empty input), the tracker can be marked as\n   * trivially complete. Once a tracker is marked as complete, the snapshots will always report back the progress\n   * digest as 1. Any modification to the state of the tracker (eg: calling setTotalMergersForLevel()) would proceed\n   * as regular, but\n   */\n  public synchronized void markTriviallyComplete()\n  {","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/frame/processor/SuperSorterProgressTracker.java#L140-L176","documentation":"SuperSorterProgressTracker tracks how many batches have been merged at each sorting level. addMergedBatchesForLevel validates that the level is below totalMergingLevels (when known); passing a level at or above that bound means the sorter's bookkeeping is inconsistent with its configured merging depth. It throws ISE because this should be impossible with a correctly constructed tracker.","triggerScenarios":"Calling addMergedBatchesForLevel with a level >= totalMergingLevels when totalMergingLevels is known (not UNKNOWN_LEVEL); usually from an internal frame-processor pipeline whose per-level merge loop exceeded the configured maxMergingLevels.","commonSituations":"Custom or patched sorter logic miscomputing level counts; misconfigured deep-storage/limit configurations that reduce totalMergingLevels below what the query plan expects; regressions after upgrading Druid where frame-processor defaults changed.","solutions":["Check the sorting configuration (e.g. max merging levels in frame processor / SuperSorter options) so totalMergingLevels matches the actual number of merge levels used","Verify the caller's loop only iterates levels 0..totalMergingLevels-1","Reproduce with a minimal query and file a Druid bug if internal code triggers it; avoid calling this API from external code","If it appears after an upgrade, compare frame-processor configs with the prior version and adjust"],"exampleFix":"// before\ntracker.addMergedBatchesForLevel(level, batches); // level computed beyond totalMergingLevels\n// after\nif (tracker.getTotalMergingLevels() != SuperSorter.UNKNOWN_LEVEL && level < tracker.getTotalMergingLevels()) {\n  tracker.addMergedBatchesForLevel(level, batches);\n}","handlingStrategy":"validation","validationCode":"if (totalMergingLevels != SuperSorter.UNKNOWN_LEVEL && level >= totalMergingLevels) { throw new IllegalArgumentException(\"level out of range: \" + level); }","typeGuard":"boolean isValidLevel(int level, int totalMergingLevels) { return totalMergingLevels == SuperSorter.UNKNOWN_LEVEL || (level >= 0 && level < totalMergingLevels); }","tryCatchPattern":"try { tracker.addMergedBatchesForLevel(level, n); } catch (IllegalStateException e) { log.error(\"level bookkeeping mismatch: {}\", e.getMessage()); }","preventionTips":["Derive levels from totalMergingLevels, never hardcode","Validate level bounds at loop entry","Add unit tests for edge levels (0 and totalMergingLevels-1)"],"tags":["sorting","internal-state","frame-processor"],"backgroundTag":"value-out-of-range","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"}