apache/druid · error · IllegalStateException (ISE)
Number of segment descriptors does not equal number of…
Error message
Number of segment descriptors does not equal number of query runners...something went wrong!
What it means
During mergeRunners, Druid aligns query runners with the query's segment intervals one-to-one. If the Iterable of runners is neither the expected ordered list size nor a SinkQueryRunners instance, it cannot partition runners by interval and throws this IllegalStateException, indicating an internal invariant violation.
Solutions
- Verify any custom QueryRunnerFactory/wrapping preserves the mapping of one runner per segment descriptor.
- Remove or fix extensions that change the runner list size (e.g. caching or retry wrappers that duplicate/drop runners).
- Retry the query; if it persists on a stock Druid setup, capture the query and segment list and report with logs — this indicates a genuine internal bug.
Defensive patterns
Strategy: try-catch
Validate before calling
if (!(queryRunners instanceof SinkQueryRunners) && queryRunners.size() != segments.size()) {
throw new IllegalStateException("runner/segment count mismatch before mergeRunners");
} Type guard
boolean runnersAligned(Iterable<?> runners, int expectedCount) {
return runners instanceof SinkQueryRunners || Iterables.size(runners) == expectedCount;
} Try / catch
try {
merged = factory.mergeRunners(query, runnerList);
} catch (IllegalStateException e) {
if (e.getMessage().contains("does not equal number of")) { /* retry or unwrap custom runners */ }
else throw e;
} Prevention
- Do not wrap/unwrap query runners between segment selection and mergeRunners.
- Test custom runner factories against time-ordered scans.
- Keep broker and historical versions consistent.
When it happens
Trigger: mergeRunners invoked (time-ordering scan queries) where queryRunners.size() != segments.size() and runners are not SinkQueryRunners — e.g. custom query runner wrappers, caching layers, or broker-local segment/runner bookkeeping returning inconsistent collections.
Common situations: Custom query runner factories or historical-node wrappers that decorate runners without preserving the one-runner-per-segment contract; running time-ordered scans with non-standard tiers or extensions.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Can only handle [ ], got [ ]
- Cannot apply limit[ ] with offset[ ] due to overflow
- Cannot execute query with orderBy
- Cannot provide 'order' incompatible with 'orderBy'
- Column [ ] from 'orderBy' must also appear in 'columns'.
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/90fa2be3eb676386.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/query/scan/ScanQueryRunnerFactory.java:150
query,
intervalsOrdered
);
}
catch (IOException e) {
throw new RuntimeException(e);
}
} else {
// Use n-way merge strategy
List<Pair<Interval, QueryRunner<ScanResultValue>>> intervalsAndRunnersOrdered = new ArrayList<>();
if (intervalsOrdered.size() == queryRunnersOrdered.size()) {
for (int i = 0; i < queryRunnersOrdered.size(); i++) {
intervalsAndRunnersOrdered.add(new Pair<>(intervalsOrdered.get(i), queryRunnersOrdered.get(i)));
}
} else if (queryRunners instanceof SinkQueryRunners) {
((SinkQueryRunners<ScanResultValue>) queryRunners).runnerIntervalMappingIterator()
.forEachRemaining(intervalsAndRunnersOrdered::add);
} else {
throw new ISE("Number of segment descriptors does not equal number of "
+ "query runners...something went wrong!");
}
// Group the list of pairs by interval. The LinkedHashMap will have an interval paired with a list of all the
// query runners for that segment
LinkedHashMap<Interval, List<Pair<Interval, QueryRunner<ScanResultValue>>>> partitionsGroupedByInterval =
intervalsAndRunnersOrdered.stream()
.collect(Collectors.groupingBy(
x -> x.lhs,
LinkedHashMap::new,
Collectors.toList()
));
// Find the segment with the largest numbers of partitions. This will be used to compare with the
// maxSegmentPartitionsOrderedInMemory limit to determine if the query is at risk of consuming too much memory.
int maxNumPartitionsInSegment =
partitionsGroupedByInterval.values()
.stream()View on GitHub (pinned to 9b90983fd2)