apache/druid · error · IllegalStateException
Output partitions are not ready yet
Error message
Output partitions are not ready yet
What it means
getOutputPartitions() reads the partitioning scheme, but only after it has been computed by the partitioning phase. If outputPartitionsFuture is not yet done, it throws IllegalStateException — callers must not read output partitions before the partition-discovery step of the sort completes. It is an internal timing/contract guard: waiting should be done via the future, not by polling this method.
Solutions
- Await outputPartitionsFuture (or the future returned by run()) before reading output partitions; do not poll getOutputPartitions().
- Hook the completion callback: read partitions only after the partition-determination phase future completes (as run() does via its listener).
- If this occurs inside stock Druid code paths, verify no custom extension altered the ordering of runMerger/setTotalMergingLevelsIfPossible calls.
- Upgrade Druid if hit spontaneously — this can indicate an internal scheduling regression fixed in later releases.
Example fix
// before int partitions = superSorter.outputPartitionCount(); // may throw if partitions unknown yet // after ListenableFuture<ClusterByPartitions> partitionsFuture = superSorter.outputPartitionsFuture(); ClusterByPartitions partitions = FutureUtils.getUnchecked(partitionsFuture, true);
Defensive patterns
Strategy: validation
Validate before calling
if (!outputPartitionsFuture.isDone()) {
// wait instead of reading
ClusterByPartitions partitions = FutureUtils.getUnchecked(outputPartitionsFuture, true);
} Type guard
static boolean outputPartitionsReady(ListenableFuture<ClusterByPartitions> f) {
return f.isDone();
} Try / catch
try {
partitions = superSorter.outputPartitionCount();
} catch (IllegalStateException e) {
partitions = FutureUtils.getUnchecked(partitionsFuture, true).size();
} Prevention
- Always await outputPartitionsFuture before querying partition info.
- Trigger dependent work from future-completion callbacks, not eager polling.
- Do not instrument or extend SuperSorter in ways that reorder merger startup.
- If spontaneous, check for Druid version regressions in sorter scheduling.
When it happens
Trigger: Calling getOutputPartitions() (directly or indirectly via partitions(), outputPartitionCount(), or runNextUltimateMerger()) while outputPartitionsFuture is still pending — e.g. invoking outputPartitionCount() right after run() before the partition-determining processors finish, or wiring an ultimate merger before partition info resolves.
Common situations: Custom code extending or instrumenting SuperSorter that reads partition info too early; debugging/instrumentation logging partition counts immediately after starting the sorter; race conditions in modified scheduling code that skips the future-completion callback that normally triggers these reads.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Attempt to add row to swapped-out sink for segment
- Background lookup manager thread could not be cancelled
- Cache reference is null
- Can't find surrogate task
- can't start.
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/4a2a55dcdaec40e6.
Report an issue: GitHub.
Appendix: source
Thrown at processing/src/main/java/org/apache/druid/frame/processor/SuperSorter.java:908
} else {
// Use two levels: no need to have a partitioned penultimate layer.
totalMergingLevels = 2;
}
} else {
totalMergingLevels = level + 1;
}
for (int i = level; i < totalMergingLevels; i++) {
superSorterProgressTracker.setTotalMergersForLevel(i, 1);
}
superSorterProgressTracker.setTotalMergingLevels(totalMergingLevels);
}
private ClusterByPartitions getOutputPartitions()
{
if (!outputPartitionsFuture.isDone()) {
throw new ISE("Output partitions are not ready yet");
}
return FutureUtils.getUnchecked(outputPartitionsFuture, true);
}
@GuardedBy("runWorkersLock")
private long getTotalMergersInLevel(final int level)
{
if (totalInputFrames == UNKNOWN_TOTAL || totalMergingLevels == UNKNOWN_LEVEL) {
return UNKNOWN_TOTAL;
} else if (level >= totalMergingLevels) {
throw new ISE("Invalid level %d", level);
} else if (level == totalMergingLevels - 1) {
if (outputPartitionsFuture.isDone()) {
return totalInputFrames == 0 ? 0 : getOutputPartitions().size();
} else {
return UNKNOWN_TOTAL;
}View on GitHub (pinned to 9b90983fd2)