apache/druid · error · IllegalStateException
Null cursor factory found. Probably trying to issue a query
Error message
Null cursor factory found. Probably trying to issue a query against a segment being memory unmapped.
What it means
ScanQueryFrameProcessor.runWithInputChannel maps each input frame as a segment and requires a CursorFactory capability to run the scan query. A null cursor factory means the segment could not be adapted — typically because the underlying memory was unmapped while the query was running — so processing fails fast with this IllegalStateException instead of producing wrong results.
Source
Thrown at multi-stage-query/src/main/java/org/apache/druid/msq/querykit/scan/ScanQueryFrameProcessor.java:398
return ReturnOrAwait.runAgain();
}
}
@Override
protected ReturnOrAwait<Unit> runWithInputChannel(
final ReadableFrameChannel inputChannel,
final FrameReader inputFrameReader
) throws IOException
{
if (cursor == null || cursor.isDone()) {
if (inputChannel.canRead()) {
final Frame frame = inputChannel.readFrame();
final FrameSegment frameSegment = new FrameSegment(frame, inputFrameReader);
final Segment mappedSegment = mapUnmanagedSegment(frameSegment);
final CursorFactory cursorFactory = mappedSegment.as(CursorFactory.class);
if (cursorFactory == null) {
throw new ISE(
"Null cursor factory found. Probably trying to issue a query against a segment being memory unmapped."
);
}
if (!Intervals.ONLY_ETERNITY.equals(query.getIntervals())) {
// runWithInputChannel is for running on subquery results, where we don't expect to see "intervals" set.
// The SQL planner avoid it for subqueries; see DruidQuery#canUseIntervalFiltering.
throw DruidException.defensive("Expected eternity intervals, but got[%s]", query.getIntervals());
}
final CursorHolder nextCursorHolder =
cursorFactory.makeCursorHolder(ScanQueryEngine.makeCursorBuildSpec(query, null));
final Cursor nextCursor = nextCursorHolder.asCursor();
if (nextCursor == null) {
// no cursor
nextCursorHolder.close();
return ReturnOrAwait.returnObject(Unit.instance());View on GitHub (pinned to 9b90983fd2)
Solutions
- Re-run the query after segment activity settles; transient unmapping usually resolves it.
- Pin or disable segment dropping/replacement for the duration of large scans (adjust drop/skip schedules).
- Reduce query concurrency/footprint on the historical to avoid mmap eviction.
- Check historical logs for segment unload events around the failure and increase segment cache capacity if evictions are frequent.
Defensive patterns
Strategy: retry
Try / catch
try {
result = runMsqScan(scanQuery);
} catch (IllegalStateException e) {
if (e.getMessage().contains('Null cursor factory')) {
// Transient segment unmapping; wait and retry with backoff
Thread.sleep(retryDelayMs);
result = runMsqScan(scanQuery);
} else throw e;
} Prevention
- Avoid killing/dropping/replacing segments while large scans run.
- Keep adequate segment cache capacity to limit mmap eviction.
- Retry transient scan failures with backoff.
- Schedule heavy scans away from segment cleanup windows.
When it happens
Trigger: Reading an input frame whose mapped segment no longer exposes CursorFactory, usually because the historical segment was memory-unmapped/dropped mid-query (segment cleanup, replacement, or kill during execution).
Common situations: Long-running MSQ scans overlapping with segment dropping/replication changes; historicals under memory pressure releasing mmaps; querying a datasource being re-ingested or overwritten concurrently.
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
- Row too large to add to frame (max frame size = %,d)
- NotEnoughMemoryFault
- FrameTooLarge
- TooManyRowsInAWindow
- FrameTooLarge
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/782509de058bc6bc.
Report an issue: GitHub.