apache/druid · error · IllegalStateException

Unable to set %d total mergers for level %d. Level must be n

Error message

Unable to set %d total mergers for level %d. Level must be non-negative

What it means

SuperSorterProgressTracker.setTotalMergersForLevel rejects negative level indices with this IllegalStateException. Levels are 0-indexed in the merge hierarchy, so a negative value is always a caller bug in the level arithmetic. The message includes both the totalMergers and the offending level for diagnosis.

Source

Thrown at processing/src/main/java/org/apache/druid/frame/processor/SuperSorterProgressTracker.java:122

        throw new ISE(
            "Max level found in levelToTotalBatches is %d (0-indexed). Cannot set totalMergingLevels to %d",
            max,
            totalMergingLevels
        );
      }
    });

    this.totalMergingLevels = totalMergingLevels;
  }

  /**
   * 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).

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Clamp or validate the level parameter at the call site before invoking setTotalMergersForLevel (require level >= 0).
  2. Check level arithmetic in the merge scheduler for underflow, especially first-iteration cases like level = currentLevel - 1.
  3. Verify that all level constants use the 0-indexed convention consistently after any refactor.
  4. Log the computed level and its derivation when it is negative to pinpoint the miscomputing caller.

Example fix

// before
tracker.setTotalMergersForLevel(currentLevel - 1, mergers); // currentLevel == 0
// after
if (currentLevel > 0) {
  tracker.setTotalMergersForLevel(currentLevel - 1, mergers);
}
Defensive patterns

Strategy: validation

Validate before calling

if (level >= 0) {
  tracker.setTotalMergersForLevel(level, mergers);
}

Type guard

boolean isNonNegativeLevel(int level) { return level >= 0; }

Prevention

When it happens

Trigger: Calling setTotalMergersForLevel with a level computed as level-1 at level 0, or from a subtraction/index arithmetic that underflows; also passing an uninitialized/sentinel negative value for level.

Common situations: Custom merge drivers computing levels via integer arithmetic that goes one below zero; refactors renaming levels from 1-indexed to 0-indexed convention leaving stray -1 values.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/72c05c823273d7f8. Report an issue: GitHub.