Many relational optimizers stop doing exhaustive join-order search once a query exceeds a certain number of joined tables and switch to a greedy or randomized/genetic algorithm. Why, and what do you give up?
answer
- DP is 3^n time / 2^n memory
- Threshold usually ~8-12 relations
- Greedy: fuse smallest-result pair, O(n^3), deterministic
- Genetic: population + crossover, bounded time, stochastic
- Lose optimality guarantee; randomized also loses determinism
basics
~20 sExhaustive dynamic programming is exponential — roughly 3^n time and 2^n memory — so past about a dozen tables the planning time and memory exceed any benefit. Engines switch to greedy construction or randomized/genetic search. You give up the guarantee of the cheapest plan under the cost model, and randomized search also gives up plan determinism.
solid answer
~50 sDynamic programming is `O(3^n)` time and `O(2^n)` memory in the general case. Somewhere around 10–15 relations that stops being affordable: planning can take longer than execution, and the memo table can consume serious memory. So engines define a **join-count threshold**. Below it, exact DP; above it, a heuristic. The two common fallbacks are **greedy** — repeatedly join the pair whose estimated result is smallest (or cheapest), roughly `O(n^3)`, deterministic — and **randomized/genetic** — encode orderings as candidate solutions, apply crossover and mutation, score by estimated cost, keep the fittest for a bounded number of generations. What you lose: the DP guarantee that the plan is cheapest **under the cost model**. Greedy is myopic — a locally smallest intermediate can lead into a bad order. Genetic search is stochastic, so the same query can plan differently across runs or servers, producing confusing latency variance. That is why heavily-joined hot queries usually get decomposed or given a fixed plan rather than left to the fallback.
go deeper
Know that very wide joins are not searched exhaustively and that the engine takes a shortcut past a limit.
Give the exponential complexity, name greedy and randomized/genetic fallbacks, and state that optimality is no longer guaranteed.
Add the operational consequences — planning time inside the latency budget, plan instability from stochastic search — and the mitigations: reduce join count, reuse plans, fix statistics.
Frame it as a planning-budget policy per workload class: exact search for OLTP with few relations, bounded heuristics plus plan reuse or pinning for wide analytics, and a decision about whether such queries belong in this engine.
## Why exhaustive search has to stop Dynamic programming over subsets examines `3^n` combinations and stores `2^n` memo entries. Concretely: n=12 is a few hundred thousand memo entries and low-millions of combinations — fine. n=20 is a million memo entries and 3.5 billion combinations — not fine. And optimization time is not free: it is paid on every plan-cache miss, inside the user's latency budget. A query that executes in 40 ms should not spend 900 ms being planned. So engines carry a configurable **join-count threshold**. Under it, exact search; over it, something cheaper. The threshold is usually in the 8–12 range by default. Note that the count is of *relations after flattening* — inlined views and subqueries pull their base tables into the same optimization region, so a query that visually joins six things can easily cross the threshold. ## Fallback 1 — greedy construction Start with each relation as its own unit. Repeatedly pick the pair of units connected by a join predicate whose combination looks best — usually smallest estimated output cardinality, sometimes lowest estimated cost — and fuse them. Repeat until one unit remains. - Complexity is roughly `O(n^3)` (n-1 fusion steps, each scanning O(n^2) pairs). - **Deterministic**: the same inputs and statistics produce the same plan, which matters enormously for operational predictability. - **Myopic**: it never reconsiders. Choosing the smallest intermediate now can force an expensive join later — the classic failure of any greedy algorithm on a problem without matroid structure. ## Fallback 2 — randomized / genetic search Encode a join order as a chromosome (for example, a permutation of relation ids interpreted as a left-deep order). Generate a random initial population, evaluate each with the ordinary cost model as the fitness function, then iterate: select fitter individuals, recombine them with a crossover operator that preserves permutation validity, mutate occasionally, and keep the best found after a fixed number of generations or a time budget. - Explores a broad, non-local sample of the space, so it can find good plans well outside greedy's reach. - **Stochastic**: unless the seed is fixed, repeated planning of the same query can produce different plans. Some engines pin a seed precisely to restore determinism. - Bounded cost by construction — you choose the population size and generation count, so planning time is capped. Other randomized approaches exist in the literature (iterative improvement, simulated annealing, two-phase combinations); they share the same profile: bounded time, no optimality guarantee. ## What you actually give up 1. **The optimality guarantee.** DP promises the cheapest plan *under the cost model and estimates*. Heuristics promise only a plan. The practical gap is usually modest but has a long tail: occasionally a heuristic misses an order that is 100x better. 2. **Determinism (for randomized search).** Two application servers, or the same server after a restart, can plan the same statement differently. Latency graphs then show bimodal behaviour with no code change to blame — an unpleasant on-call experience. 3. **Explainability.** With DP you can reason about why a plan won on cost. With a genetic search, "it is what the search happened to find" is the honest answer. ## What you keep Bounded planning time, and therefore a bounded worst case. That is the point of the tradeoff: for very wide queries, the risk of spending unbounded time searching outweighs the value of the last few percent of plan quality. ## Engineering response - **Reduce the effective join count** before it crosses the threshold: split the statement, materialize a stable intermediate, avoid stacking views that flatten into one huge optimization region. - **Raise the threshold deliberately** for a small number of important analytic statements where the planning cost is amortized over a long execution, and measure the planning time you just bought. - **Reuse plans** — prepared statements, plan caching, or an engine-level stored plan — so a heavily-joined query is planned once rather than per execution. - **Fix estimates first.** Past the threshold, the heuristic search is guided by the same cardinality estimates; bad statistics make both the search and the resulting plan worse, and no amount of extra search compensates.
- Why can a query that looks like it joins five things still trip the threshold?Because the count is of base relations after view and subquery flattening. Inlined views, table functions expanded into their bodies, and merged subqueries all contribute their own base tables to the same optimization region, so a visually small statement can present the optimizer with twenty relations.
- How would you detect that a query is being planned by the heuristic fallback rather than exact search?Look at planning time relative to execution time and at plan stability. Heuristic territory typically shows up as planning time that grows sharply with join count, and — for randomized search — as the same statement producing different plans across repeated planning. Comparing plans before and after lowering the join count is the direct confirmation.
- Is raising the join-count threshold a reasonable fix?Sometimes, for a small number of long-running analytic statements where a second of extra planning is amortized over minutes of execution. It is a poor fix for OLTP, where planning happens on every cache miss inside the latency budget, and it worsens the exponential blow-up rather than removing it.
saying these in an interview costs you the question
- Believing the optimizer always finds the optimal join order regardless of join count
- Assuming greedy search means 'no cost model' — it still uses estimates, just myopically
- Treating plan instability under randomized search as a bug in the storage engine or a caching issue
- Proposing to raise the threshold arbitrarily without measuring the planning-time cost
- Thinking more search fixes bad plans caused by bad cardinality estimates