{"record":{"id":"bc647f50e57b0eae","repo":"apache/druid","slug":"null-queryrunner-looks-to-be-some-segment-unmappi-bc647f","errorCode":null,"errorMessage":"Null queryRunner! Looks to be some segment unmapping action happening","messagePattern":"Null queryRunner! Looks to be some segment unmapping action happening","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/GroupByMergingQueryRunner.java","lineNumber":250,"sourceCode":"                  );\n              final Grouper<RowBasedKey> grouper = pair.lhs;\n              final Accumulator<AggregateResult, ResultRow> accumulator = pair.rhs;\n              grouper.init();\n\n              final ReferenceCountingResourceHolder<Grouper<RowBasedKey>> grouperHolder =\n                  ReferenceCountingResourceHolder.fromCloseable(grouper);\n              resources.register(grouperHolder);\n\n              List<ListenableFuture<AggregateResult>> futures = Lists.newArrayList(\n                      Iterables.transform(\n                          queryables,\n                          new Function<>()\n                          {\n                            @Override\n                            public ListenableFuture<AggregateResult> apply(final QueryRunner<ResultRow> input)\n                            {\n                              if (input == null) {\n                                throw new ISE(\"Null queryRunner! Looks to be some segment unmapping action happening\");\n                              }\n\n                              final AbstractPrioritizedQueryRunnerCallable<AggregateResult, ResultRow> callable = new AbstractPrioritizedQueryRunnerCallable<>(priority, input)\n                              {\n                                @Override\n                                public AggregateResult call()\n                                {\n                                  try (\n                                      // These variables are used to close releasers automatically.\n                                      @SuppressWarnings(\"unused\")\n                                      Closeable bufferReleaser = mergeBufferHolder.increment();\n                                      @SuppressWarnings(\"unused\")\n                                      Closeable grouperReleaser = grouperHolder.increment()\n                                  ) {\n                                    // Return true if OK, false if resources were exhausted.\n                                    return input.run(queryPlusForRunners, responseContext)\n                                                .accumulate(AggregateResult.ok(), accumulator);\n                                  }","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/groupby/epinephelinae/GroupByMergingQueryRunner.java#L232-L268","documentation":"GroupByMergingQueryRunner builds per-segment futures via a Function applied to query runners obtained from the timeline. If the runner Function yields null — meaning the segment's runner disappeared (segment unmapped/closed) while the query was being fanned out — it throws an ISE naming segment unmapping as the cause.","triggerScenarios":"Query fan-out via FunctionalQueryableRunner/Timeline where a segment is dropped or closed on the historical server concurrently with query execution, so the Function<QueryRunner> apply receives input == null.","commonSituations":"Coordinator-triggered segment drops/balancing during queries; real-time task handoff; replica serving segments being cleaned up; cache unmapping under memory pressure.","solutions":["Retry the query — transient segment churn is expected in a Druid cluster","Inspect coordinator/historical logs for segment drop/load races at the failure time","Increase replica count so at least one replica serves the segment","Tune coordinator load/drop windows and balancing periods to reduce churn","Ensure server-level segment lifecycle code never returns null runners mid-query (guard in server tier)"],"exampleFix":"// before\nListenableFuture<AggregateResult> f = Futures.transform(runnerFn.apply(null-runner), ...);\n// after\nQueryRunner<ResultRow> input = runnerFn.apply(segmentId);\nif (input == null) {\n  throw new ResourceLimitException(\"Segment unavailable, retry query\"); // retryable instead of ISE\n}","handlingStrategy":"retry","validationCode":"if (input == null) { throw new RetryQueryException(\"segment runner disappeared; retry\"); }","typeGuard":"boolean hasRunner(Function<Object, QueryRunner<ResultRow>> fn, SegmentDescriptor d) { return fn.apply(d) != null; }","tryCatchPattern":"try { merge(query); } catch (ISE e) { if (e.getMessage().contains(\"Null queryRunner\")) { retryWithBackoff(query); } }","preventionTips":["Retry broker queries on segment-unmapped style ISEs","Keep replicas >= 2 for query availability during segment churn","Reduce coordinator drop/balance aggressiveness under heavy query load"],"tags":["druid","groupby","segment-unmapped","concurrency","distributed"],"backgroundTag":"null-argument","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}