apache/druid · error · IllegalArgumentException

maxChannelsPerMerger

Error message

maxChannelsPerMerger[%d] < 2

What it means

SuperSorter's constructor validates that maxChannelsPerMerger, the maximum number of input channels merged in one merge step, is at least 2. A value below 2 would make merging impossible (a merger needs at least two inputs to combine), so the constructor throws IllegalArgumentException immediately. This is a fail-fast guard against a nonsensical configuration.

Solutions

  1. Set maxChannelsPerMerger to at least 2 when constructing SuperSorter.
  2. Check the originating configuration source (worker/task tuning config) and raise the value to 2 or more.
  3. If the value is computed at runtime, add a lower bound of 2 where it is calculated (Math.max(2, computed)).
  4. Check for typos or unit confusion in config files that feed this parameter.

Example fix

// before
SuperSorter sorter = new SuperSorter(maxActiveProcessors, 1, rowLimit, ...);
// after
SuperSorter sorter = new SuperSorter(maxActiveProcessors, Math.max(2, maxChannelsPerMerger), rowLimit, ...);
Defensive patterns

Strategy: validation

Validate before calling

if (maxChannelsPerMerger < 2) {
  throw new IllegalArgumentException("maxChannelsPerMerger must be >= 2, got " + maxChannelsPerMerger);
}

Type guard

static boolean isValidMaxChannelsPerMerger(int v) {
  return v >= 2;
}

Prevention

When it happens

Trigger: Constructing a SuperSorter (directly or via ClippedQSegmentManager/processor config plumbing) with maxChannelsPerMerger set to 0, 1, or any negative number — typically from a misconfigured tuning parameter (e.g. druid processing config or MultiStageQuery tuning config) that was set too low.

Common situations: Operators lowering 'maxChannelsPerMerger' to reduce memory pressure and accidentally setting it to 1; copy-pasting tuning config from another cluster with a typo; programmatically computing the value from a partition/worker count that came out as 0 or 1 (e.g. dividing by zero-guarded counts); test harnesses passing hardcoded small values.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/frame/processor/SuperSorter.java:273

    this.outputFrameType = outputFrameType;
    this.maxChannelsPerMerger = maxChannelsPerMerger;
    this.maxActiveProcessors = maxActiveProcessors;
    this.rowLimit = rowLimit;
    this.cancellationId = cancellationId;
    this.superSorterProgressTracker = superSorterProgressTracker;
    this.removeNullBytes = removeNullBytes;
    this.combinerFactory = combinerFactory;

    for (int i = 0; i < inputChannels.size(); i++) {
      inputChannelsToRead.add(i);
    }

    if (maxActiveProcessors < 1) {
      throw new IAE("maxActiveProcessors[%d] < 1", maxActiveProcessors);
    }

    if (maxChannelsPerMerger < 2) {
      throw new IAE("maxChannelsPerMerger[%d] < 2", maxChannelsPerMerger);
    }

    if (rowLimit != UNLIMITED && rowLimit <= 0) {
      throw new IAE("rowLimit[%d] must be positive", rowLimit);
    }
  }

  /**
   * Starts sorting. Can only be called once. Work is performed in the {@link FrameProcessorExecutor} that was
   * passed to the constructor.
   *
   * Returns a future containing partitioned sorted output channels.
   */
  public ListenableFuture<OutputChannels> run()
  {
    synchronized (runWorkersLock) {
      if (allDone != null) {
        throw new ISE("Cannot run() more than once.");

View on GitHub (pinned to 9b90983fd2)