{"record":{"id":"90fa2be3eb676386","repo":"apache/druid","slug":"number-of-segment-descriptors-does-not-equal-numbe","errorCode":null,"errorMessage":"Number of segment descriptors does not equal number of query runners...something went wrong!","messagePattern":"Number of segment descriptors does not equal number of query runners\\.\\.\\.something went wrong!","errorType":"exception","errorClass":"IllegalStateException (ISE)","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/scan/ScanQueryRunnerFactory.java","lineNumber":150,"sourceCode":"                query,\n                intervalsOrdered\n            );\n          }\n          catch (IOException e) {\n            throw new RuntimeException(e);\n          }\n        } else {\n          // Use n-way merge strategy\n          List<Pair<Interval, QueryRunner<ScanResultValue>>> intervalsAndRunnersOrdered = new ArrayList<>();\n          if (intervalsOrdered.size() == queryRunnersOrdered.size()) {\n            for (int i = 0; i < queryRunnersOrdered.size(); i++) {\n              intervalsAndRunnersOrdered.add(new Pair<>(intervalsOrdered.get(i), queryRunnersOrdered.get(i)));\n            }\n          } else if (queryRunners instanceof SinkQueryRunners) {\n            ((SinkQueryRunners<ScanResultValue>) queryRunners).runnerIntervalMappingIterator()\n                                                              .forEachRemaining(intervalsAndRunnersOrdered::add);\n          } else {\n            throw new ISE(\"Number of segment descriptors does not equal number of \"\n                          + \"query runners...something went wrong!\");\n          }\n\n          // Group the list of pairs by interval.  The LinkedHashMap will have an interval paired with a list of all the\n          // query runners for that segment\n          LinkedHashMap<Interval, List<Pair<Interval, QueryRunner<ScanResultValue>>>> partitionsGroupedByInterval =\n              intervalsAndRunnersOrdered.stream()\n                                        .collect(Collectors.groupingBy(\n                                            x -> x.lhs,\n                                            LinkedHashMap::new,\n                                            Collectors.toList()\n                                        ));\n\n          // Find the segment with the largest numbers of partitions.  This will be used to compare with the\n          // maxSegmentPartitionsOrderedInMemory limit to determine if the query is at risk of consuming too much memory.\n          int maxNumPartitionsInSegment =\n              partitionsGroupedByInterval.values()\n                                         .stream()","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/scan/ScanQueryRunnerFactory.java#L132-L168","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!(queryRunners instanceof SinkQueryRunners) && queryRunners.size() != segments.size()) {\n  throw new IllegalStateException(\"runner/segment count mismatch before mergeRunners\");\n}","typeGuard":"boolean runnersAligned(Iterable<?> runners, int expectedCount) {\n  return runners instanceof SinkQueryRunners || Iterables.size(runners) == expectedCount;\n}","tryCatchPattern":"try {\n  merged = factory.mergeRunners(query, runnerList);\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"does not equal number of\")) { /* retry or unwrap custom runners */ }\n  else throw e;\n}","preventionTips":["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."],"tags":["druid","scan-query","internal-invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}