prestodb/presto · error · PrestoException

QUERY_PLANNING_TIMEOUT

QUERY_PLANNING_TIMEOUT

Error message

The query optimizer exceeded the timeout of %s.

What it means

Thrown by Optimizer.validateAndOptimizePlan when the current thread is interrupted between running plan optimizers, indicating the query planning phase exceeded its allowed time (query.analyzer-timeout / planning timeout). Presto interrupts the planning thread once the deadline passes, and the optimizer loop converts the interrupt into a QUERY_PLANNING_TIMEOUT failure.

Source

Thrown at presto-main-base/src/main/java/com/facebook/presto/sql/Optimizer.java:111

        this.planChecker = requireNonNull(planChecker, "planChecker is null");
        this.metadata = requireNonNull(metadata, "metadata is null");
        this.variableAllocator = requireNonNull(variableAllocator, "variableAllocator is null");
        this.idAllocator = requireNonNull(idAllocator, "idAllocator is null");
        this.warningCollector = requireNonNull(warningCollector, "warningCollector is null");
        this.statsCalculator = requireNonNull(statsCalculator, "statsCalculator is null");
        this.costCalculator = requireNonNull(costCalculator, "costCalculator is null");
        this.explain = explain;
    }

    public Plan validateAndOptimizePlan(PlanNode root, PlanStage stage)
    {
        validateIntermediatePlanWithRuntimeStats(root);

        boolean enableVerboseRuntimeStats = SystemSessionProperties.isVerboseRuntimeStatsEnabled(session) || SystemSessionProperties.isVerbosePlannerRuntimeStatsEnabled(session);
        if (stage.ordinal() >= OPTIMIZED.ordinal()) {
            for (PlanOptimizer optimizer : planOptimizers) {
                if (Thread.currentThread().isInterrupted()) {
                    throw new PrestoException(QUERY_PLANNING_TIMEOUT, String.format("The query optimizer exceeded the timeout of %s.", getQueryAnalyzerTimeout(session).toString()));
                }
                long start = System.nanoTime();
                PlanOptimizerResult optimizerResult = optimizer.optimize(root, session, TypeProvider.viewOf(variableAllocator.getVariables()), variableAllocator, idAllocator, warningCollector);
                requireNonNull(optimizerResult, format("%s returned a null plan", optimizer.getClass().getName()));
                if (enableVerboseRuntimeStats || trackOptimizerRuntime(session, optimizer)) {
                    session.getRuntimeStats().addMetricValue(String.format("optimizer%sTimeNanos", getOptimizerNameForLog(optimizer)), NANO, System.nanoTime() - start);
                }
                TypeProvider types = TypeProvider.viewOf(variableAllocator.getVariables());

                collectOptimizerInformation(optimizer, root, optimizerResult, types);
                root = optimizerResult.getPlanNode();
            }
        }
        if (stage.ordinal() >= OPTIMIZED_AND_VALIDATED.ordinal()) {
            // make sure we produce a valid plan after optimizations run. This is mainly to catch programming errors
            validateFinalPlanWithRuntimeStats(root);
        }

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Increase the query.analyzer-timeout (planning timeout) session or config property
  2. Simplify the query (reduce join/union fan-out, split into stages) so planning completes in time
  3. Check cluster CPU load; planning is CPU-bound and contention lengthens it
  4. Profile which optimizer pass is slow (verbose planner runtime stats) and disable/avoid the pattern that triggers it

Example fix

// before
SET SESSION query_analyzer_timeout = '2m';
// after
SET SESSION query_analyzer_timeout = '10m';
Defensive patterns

Strategy: try-catch

Validate before calling

Duration timeout = getQueryAnalyzerTimeout(session);
// estimate planning cost before submitting: column/join count heuristics
if (joinCount > 50 || columnCount > 1000) { /* warn or raise timeout */ }

Try / catch

try { Plan p = planner.plan(...); } catch (PrestoException e) { if (e.getErrorCode() == QUERY_PLANNING_TIMEOUT.toErrorCode()) { /* increase timeout, simplify query, or resubmit */ } else throw e; }

Prevention

When it happens

Trigger: A query whose logical/optimized plan takes longer than the analyzer timeout: running an optimizer detects Thread.currentThread().isInterrupted() and throws. Triggered via plan/getLogicalPlan/createPlan on very large or pathological queries with expensive optimizer passes.

Common situations: Huge SQL with thousands of joins/unions; queries over thousands of columns; low query.analyzer-timeout session setting; cluster under CPU starvation slowing planning.

Understand the failure class

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/571c49d24c7963d6. Report an issue: GitHub.