apache/druid · error · IllegalStateException (ISE)
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
ScanQueryEngine.process obtains a CursorFactory from the segment via segment.as(CursorFactory.class). A null result means the segment no longer exposes cursors, which happens when its memory-mapped data is being unmapped (e.g. the segment was dropped or swapped out while the query ran). Druid throws ISE rather than returning partial results.
Source
Thrown at processing/src/main/java/org/apache/druid/query/scan/ScanQueryEngine.java:86
final Segment segment,
final ResponseContext responseContext,
@Nullable final QueryMetrics<?> queryMetrics
)
{
final Long numScannedRows = responseContext.getRowScanCount();
if (numScannedRows != null && numScannedRows >= query.getScanRowsLimit() && query.getTimeOrder().equals(Order.NONE)) {
return Sequences.empty();
}
if (segment.isTombstone()) {
return Sequences.empty();
}
final boolean hasTimeout = query.context().hasTimeout();
final Long timeoutAt = responseContext.getTimeoutTime();
final CursorFactory cursorFactory = segment.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."
);
}
final List<String> allColumns = new ArrayList<>();
if (query.getColumns() != null && !query.getColumns().isEmpty()) {
// Unless we're in legacy mode, allColumns equals query.getColumns() exactly. This is nice since it makes
// the compactedList form easier to use.
allColumns.addAll(query.getColumns());
} else {
final Set<String> availableColumns = Sets.newLinkedHashSet(
Iterables.concat(
cursorFactory.getRowSignature().getColumnNames(),
Iterables.transform(
Arrays.asList(query.getVirtualColumns().getVirtualColumns()),
VirtualColumn::getOutputNameView on GitHub (pinned to 9b90983fd2)
Solutions
- Retry the query; the segment swap is usually transient
- Increase query concurrency handling / retry policy on the client
- Avoid force-dropping segments while queries run (stagger retention tasks)
- Increase historical process mmapped memory so segments are not evicted under load
Defensive patterns
Strategy: retry
Validate before calling
CursorFactory cf = segment.as(CursorFactory.class);
if (cf == null) { /* defer/requeue query */ } Type guard
CursorFactory cursorFactoryOrNull(Segment s) { return s.as(CursorFactory.class); } Try / catch
try {
engine.process(query, segment, ...);
} catch (IllegalStateException e) {
if (e.getMessage().contains("Null cursor factory")) { /* retry after short backoff */ }
else throw e;
} Prevention
- Avoid dropping/reloading segments while queries are in flight
- Enable query retry at the broker
- Size segment caches to reduce eviction under load
When it happens
Trigger: Running a scan query against a ReferenceCountedSegment/segment whose adapter no longer supports CursorFactory because the underlying files were closed/unmapped concurrently (segment cleanup during query execution).
Common situations: Historical processes reloading or dropping segments while queries are in flight; segments evicted due to cache size limits; race after a segment swap during real-time hand-off.
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
- Null cursor factory found. Probably trying to issue a query
- Column [%s] from 'orderBy' must also appear in 'columns'.
- The __time column must be selected if the results are time-o
- Cannot execute query with orderBy %s
- Cannot provide 'order' incompatible with 'orderBy'
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/6190a11690fc7b91.
Report an issue: GitHub.