{"record":{"id":"406e83854adbef51","repo":"apache/druid","slug":"total-mergers-are-already-present-for-the-level-d","errorCode":null,"errorMessage":"Total mergers are already present for the level %d","messagePattern":"Total mergers are already present for the level (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/frame/processor/SuperSorterProgressTracker.java","lineNumber":134,"sourceCode":"   * 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)\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","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/frame/processor/SuperSorterProgressTracker.java#L116-L152","documentation":"For non-ultimate levels (level < totalMergingLevels-1), setTotalMergersForLevel is write-once: if levelToTotalBatches already holds a total for that level, a second call throws. The ultimate level is exempt because its count gets overridden by totalMergersForUltimateLevel. Repeated registration of the same level's merger count indicates duplicated scheduling logic.","triggerScenarios":"Calling setTotalMergersForLevel twice for the same non-ultimate level — e.g., both a direct-merger path and a middle-merger path register totals for level 0, or merge scheduling re-runs after a partial failure.","commonSituations":"Retry logic re-invoking the merger-registration block without checking prior state; shared trackers reused across sorter phases; tests that call the setter in setup and again in the test body.","solutions":["Guard the call with a membership check (levelToTotalBatches already has the level) or track registration on the caller side.","Make registration idempotent: skip levels whose totals were already set instead of recomputing and re-setting.","Use a fresh SuperSorterProgressTracker when re-driving a sort after failure.","For the ultimate level, prefer setTotalMergersForUltimateLevel, which is the intended override mechanism."],"exampleFix":"// before\ntracker.setTotalMergersForLevel(level, mergers); // runs each scheduling pass\n// after\nif (!tracker.hasTotalMergersForLevel(level)) {\n  tracker.setTotalMergersForLevel(level, mergers);\n}","handlingStrategy":"validation","validationCode":"// Idempotent registration\nif (!tracker.getLevelToTotalBatches().containsKey(level)) {\n  tracker.setTotalMergersForLevel(level, mergers);\n}","typeGuard":null,"tryCatchPattern":"try {\n  tracker.setTotalMergersForLevel(level, mergers);\n} catch (IllegalStateException e) {\n  if (!e.getMessage().contains(\"already present\")) {\n    throw e;\n  } // duplicate registration — safe to ignore if value unchanged\n}","preventionTips":["Make merger registration idempotent with a membership check.","Don't re-run registration blocks on retry without checking prior state.","Use a fresh tracker when re-driving a sort after failure."],"tags":["internal-state","write-once","sorting"],"backgroundTag":"invalid-state-transition","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"}