apache/druid · error · IllegalStateException
Total mergers are already present for the level
Error message
Total mergers are already present for the level %d
What it means
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.
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.
Example fix
// before
tracker.setTotalMergersForLevel(level, mergers); // runs each scheduling pass
// after
if (!tracker.hasTotalMergersForLevel(level)) {
tracker.setTotalMergersForLevel(level, mergers);
} Defensive patterns
Strategy: validation
Validate before calling
// Idempotent registration
if (!tracker.getLevelToTotalBatches().containsKey(level)) {
tracker.setTotalMergersForLevel(level, mergers);
} Try / catch
try {
tracker.setTotalMergersForLevel(level, mergers);
} catch (IllegalStateException e) {
if (!e.getMessage().contains("already present")) {
throw e;
} // duplicate registration — safe to ignore if value unchanged
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Cannot set mergers for final level more than once
- Cannot add merged batches for level
- Cannot set total mergers for level
- Invalid level
- Max level found in levelToMergedBatches is
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/406e83854adbef51.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/frame/processor/SuperSorterProgressTracker.java:134
* Sets the total mergers for a level. Can be set only once, except for the ultimate level (if total levels are known)
* because they get overridden by totalMergersForUltimateLevel
*/
public synchronized void setTotalMergersForLevel(final int level, final long totalMergers)
{
if (level < 0) {
throw new ISE("Unable to set %d total mergers for level %d. Level must be non-negative", totalMergers, level);
}
if (totalMergingLevels != SuperSorter.UNKNOWN_LEVEL && level >= totalMergingLevels) {
throw new ISE(
"Cannot set total mergers for level %d. Valid levels range from 0 to %d",
level,
totalMergingLevels - 1
);
}
if (totalMergingLevels != SuperSorter.UNKNOWN_LEVEL
&& level < totalMergingLevels - 1 // This condition is only present for levels excluding the ultimate level
&& levelToTotalBatches.containsKey(level)) {
throw new ISE("Total mergers are already present for the level %d", level);
}
levelToTotalBatches.put(level, totalMergers);
}
/**
* Sets the number of mergers in the ultimate level (number of mergers = number of output partitions).
* Can only be set once
*/
public synchronized void setTotalMergersForUltimateLevel(final long totalMergersForUltimateLevel)
{
if (this.totalMergersForUltimateLevel != SuperSorter.UNKNOWN_TOTAL) {
throw new ISE("Cannot set mergers for final level more than once");
}
this.totalMergersForUltimateLevel = totalMergersForUltimateLevel;
}
/**
* This method is designed to be called during the course of the sorting. The batches once merged for a particularView on GitHub (pinned to 9b90983fd2)