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
- Set maxChannelsPerMerger to at least 2 when constructing SuperSorter.
- Check the originating configuration source (worker/task tuning config) and raise the value to 2 or more.
- If the value is computed at runtime, add a lower bound of 2 where it is calculated (Math.max(2, computed)).
- 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
- Clamp tuning params with Math.max(2, value) at config-load time.
- Never hardcode 0/1 for merge fan-in.
- Document the >= 2 constraint next to the config key.
- Add a unit test asserting constructor rejects values < 2.
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
- rowLimit[ ] must be positive
- At least one of baseDir or files should be specified
- bucketSize must be a power of two (from 1 up to 128) but…
- Cannot have a null/empty columns
- Cannot set kafka property [auto.offset.reset]. Property…
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)