{"record":{"id":"b9f148525868ec48","repo":"apache/druid","slug":"null-cursor-factory-found-probably-trying-to-issu-b9f148","errorCode":null,"errorMessage":"Null cursor factory found. Probably trying to issue a query against a segment being memory unmapped.","messagePattern":"Null cursor factory found\\. Probably trying to issue a query against a segment being memory unmapped\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/groupby/GroupingEngine.java","lineNumber":530,"sourceCode":"   */\n  public Sequence<ResultRow> processCursorHolder(\n      GroupByQuery query,\n      CursorFactory cursorFactory,\n      CursorHolder cursorHolder,\n      @Nullable TimeBoundaryInspector timeBoundaryInspector,\n      NonBlockingPool<ByteBuffer> bufferPool,\n      @Nullable GroupByQueryMetrics groupByQueryMetrics\n  )\n  {\n    validateForProcess(query, cursorFactory);\n    final CursorBuildSpec buildSpec = makeCursorBuildSpec(query, groupByQueryMetrics);\n    return processWithCursorHolder(query, cursorFactory, cursorHolder, timeBoundaryInspector, bufferPool, buildSpec);\n  }\n\n  private static void validateForProcess(GroupByQuery query, @Nullable CursorFactory cursorFactory)\n  {\n    if (cursorFactory == null) {\n      throw new ISE(\n          \"Null cursor factory found. Probably trying to issue a query against a segment being memory unmapped.\"\n      );\n    }\n\n    final List<Interval> intervals = query.getQuerySegmentSpec().getIntervals();\n    if (intervals.size() != 1) {\n      throw new IAE(\"Should only have one interval, got[%s]\", intervals);\n    }\n  }\n\n  private Sequence<ResultRow> processWithCursorHolder(\n      GroupByQuery query,\n      CursorFactory cursorFactory,\n      CursorHolder cursorHolder,\n      @Nullable TimeBoundaryInspector timeBoundaryInspector,\n      NonBlockingPool<ByteBuffer> bufferPool,\n      CursorBuildSpec buildSpec\n  )","sourceCodeStart":512,"sourceCodeEnd":548,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/groupby/GroupingEngine.java#L512-L548","documentation":"GroupByQuery processing requires a non-null CursorFactory to read segment data. The engine throws this ISE when the cursor factory supplied to validateForProcess is null, which in Druid indicates the segment's underlying data was memory-unmapped (dropped) while a query was in flight. It is a guard against querying a segment that no longer has readable data.","triggerScenarios":"Calling GroupingEngine.process / makeCursorHolderAsync / processCursorHolder with a null CursorFactory, typically when the segment backing the query is unmapped (e.g. historical segment dropped, cache entry evicted, or real-time task swapped the segment) between query start and execution.","commonSituations":"Historical servers loading/dropping segments under load; queries racing with segment hand-off in streaming ingestion; over-aggressive cache unloading; coordination rebalancing mid-query.","solutions":["Retry the query; the segment is usually re- or already re-loaded by a different server","Check cluster segment availability/coordination logs around the failure time for drop/load races","Increase query retries/timeouts so transient unmapping is absorbed (RetryQueryException handling)","Reduce segment churn: adjust load/drop periods, replica counts, or coordinator balancing thresholds","If triggered by code, never pass null cursorFactory; validate segment state before issuing the query"],"exampleFix":"// before\nSequence<ResultRow> results = engine.process(query, null, ...);\n// after\nif (cursorFactory == null) {\n  throw new QueryInterruptedException(new ResourceLimitException(\"Segment temporarily unavailable; retry\"));\n}\nSequence<ResultRow> results = engine.process(query, cursorFactory, ...);","handlingStrategy":"retry","validationCode":"if (cursorFactory == null) { throw new RetryQueryException(\"segment unmapped; retry\"); }","typeGuard":"boolean isQueryable(CursorFactory f) { return f != null; }","tryCatchPattern":"try { results = engine.process(query, cursorFactory, ...); } catch (ISE e) { if (e.getMessage().contains(\"Null cursor factory\")) { retryQuery(query); } else { throw e; } }","preventionTips":["Wrap query submission in retry logic for transient segment-unmapped errors","Monitor segment load/drop churn in the cluster","Pin queries to replicas with loaded segments (use segment availability checks)"],"tags":["druid","groupby","segment-unmapped","concurrency"],"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"}