{"record":{"id":"e0ee242a78737ab5","repo":"apache/druid","slug":"null-cursor-factory-found-probably-trying-to-issu-e0ee24","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/timeboundary/TimeBoundaryQueryRunnerFactory.java","lineNumber":129,"sourceCode":"        final QueryPlus<Result<TimeBoundaryResultValue>> queryPlus,\n        final ResponseContext responseContext\n    )\n    {\n      Query<Result<TimeBoundaryResultValue>> input = queryPlus.getQuery();\n      if (!(input instanceof TimeBoundaryQuery)) {\n        throw new ISE(\"Got a [%s] which isn't a %s\", input.getClass(), TimeBoundaryQuery.class);\n      }\n\n      final TimeBoundaryQuery query = (TimeBoundaryQuery) input;\n\n      return new BaseSequence<>(\n          new BaseSequence.IteratorMaker<>()\n          {\n            @Override\n            public Iterator<Result<TimeBoundaryResultValue>> make()\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              DateTime minTime = null;\n              DateTime maxTime = null;\n\n              if (canUseTimeBoundaryInspector(query, timeBoundaryInspector)) {\n                if (query.needsMinTime()) {\n                  minTime = timeBoundaryInspector.getMinTime();\n                }\n\n                if (query.needsMaxTime()) {\n                  maxTime = timeBoundaryInspector.getMaxTime();\n                }\n              } else {\n                final Pair<DateTime, DateTime> timeBoundary = getTimeBoundary(query, cursorFactory);\n                minTime = timeBoundary.left();","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/timeboundary/TimeBoundaryQueryRunnerFactory.java#L111-L147","documentation":"Inside the timeboundary runner's IteratorMaker.make(), the segment's cursor factory is checked for null before computing min/max times; a null cursor factory means the segment's underlying storage has already been released. Druid throws this IllegalStateException because querying a segment whose memory has been unmapped (e.g. a swappable/mmapped segment evicted while a query is in flight) cannot proceed safely.","triggerScenarios":"Issuing a timeBoundary query against a segment whose CursorFactory is null, which happens when the segment's data is memory-unmapped (unloaded/evicted) between runner construction and iterator creation — typically on historical nodes with aggressive segment drop or during realtime hand-off.","commonSituations":"Segments dropped or swapped out while a query runs (e.g. kill tasks, load rules dropping data, broker retries landing on a historical that just unloaded the segment); low-memory historicals evicting mmapped segments; race between a query in flight and segment cleanup.","solutions":["Retry the query — the broker will typically route to a server still serving the segment","Check historical node logs/memory to ensure segments aren't being dropped mid-query (review load/drop rules and kill tasks)","Increase memory or reduce segment eviction pressure on historicals","Ensure query timeouts and retries are configured so transient unmapping races are retried rather than surfaced to users"],"exampleFix":"// before\n// no retry; a dropped mid-flight segment surfaces ISE to the user\nList<Result<TimeBoundaryResultValue>> r = queryRunner.run(QueryPlus.wrap(tbQuery), ctx).toList();\n// after\ntry {\n  r = queryRunner.run(QueryPlus.wrap(tbQuery), ctx).toList();\n} catch (IllegalStateException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Null cursor factory\")) {\n    r = retryQuery(tbQuery); // broker re-routes to a live segment\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"retry","validationCode":"// Before issuing: check the segment is still served by this server\nif (segment == null || segment.asStorageAdapter() == null) {\n  throw new IllegalStateException(\"Segment no longer available for query\");\n}","typeGuard":"boolean isSegmentQueryable(Segment segment) {\n  try {\n    return segment != null && segment.asStorageAdapter() != null;\n  } catch (Exception e) {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  return runner.run(QueryPlus.wrap(query), ctx).toList();\n} catch (IllegalStateException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Null cursor factory\")) {\n    return retryWithBackoff(query, 3); // broker will re-route to a live segment\n  }\n  throw e;\n}","preventionTips":["Configure query retries/timeouts so broker re-routes on transient segment unavailability","Review drop rules and kill tasks to avoid unloading segments under active query load","Monitor historical memory pressure that triggers aggressive segment unmapping","Prefer broker-mediated queries over direct historical queries to get automatic re-routing"],"tags":["druid","segment-lifecycle","memory-mapped","race-condition"],"backgroundTag":"internal-invariant-violation","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"}