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

  1. Re-run the query after segment activity settles; transient unmapping usually resolves it.
  2. Pin or disable segment dropping/replacement for the duration of large scans (adjust drop/skip schedules).
  3. Reduce query concurrency/footprint on the historical to avoid mmap eviction.
  4. 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

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


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/782509de058bc6bc. Report an issue: GitHub.