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

  1. Verify any custom QueryRunnerFactory/wrapping preserves the mapping of one runner per segment descriptor.
  2. Remove or fix extensions that change the runner list size (e.g. caching or retry wrappers that duplicate/drop runners).
  3. 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

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


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)