AGI House · Auto Research Summit build session · 2026-07-18

Evolving Postgres's cardinality-combining logic

Start from a faithful port of clauselist_selectivity and the literal statistics Postgres computes. Let an LLM-driven evolutionary loop (openevolve × local claude -p, haiku) rewrite the combining logic — and judge it only on data it never optimized against. Run 1 sprang the trap this harness was built to catch; run 2 re-runs the same search with the objective hardened against the diagnosed exploit.

run1 complete · run2 complete · page generated 2026-07-18 20:31

The problem · thirty seconds of context

Before a database runs your SQL it picks a strategy — which index, which join order, which algorithm. It can't try them all, so it predicts how many rows each step will match and takes the cheapest-looking plan. That prediction is cardinality estimation, and it decides everything downstream: a plan that's perfect for 10 rows is catastrophic for 100,000 — milliseconds versus minutes. The standard finding in the field (Leis et al.): estimation error, not the plan search, is the main reason databases pick bad plans.

Estimates come from small per-column statistics — most-common values with frequencies, plus histograms. One condition is easy: the stats say directly that ~1.5% of rows have city = 'BUFFALO'.

The hard part is combining conditions. Postgres mostly multiplies the fractions — which silently assumes the conditions are independent. Real values aren't: they co-occur (a city pins down its county) or they contradict (a city paired with the wrong county). Multiplication is wrong in opposite directions on those two — and, as the live example shows, per-column statistics cannot tell them apart. Postgres ships hand-written fixes for a few declared column pairs (extended statistics); everything else falls back to multiplication. This project evolves that combining logic.

Same prediction, opposite realities

measured on this build's 12,592,479-row NY vehicle-registration table
city = 'BUFFALO'188,590 rows · 1.5%
county = 'ERIE'778,011 rows · 6.2%
multiply the fractions (independence)→ predicts 11,652
WHERE city='BUFFALO' AND county='ERIE' truth 187,351 · 16× under
WHERE city='BUFFALO' AND county='KINGS' truth 10 · 1,165× over
Identical per-column statistics, identical prediction — truths of 187,351 and 10. Every combining formula is betting on which world it's in; the seed's bet is "independent", run 1 evolved the bet "co-occurring", and both bets lose somewhere.

Run 1 · the trap sprung, twice

−43%*
*claimed: the loop's recorded fitness fell 17.84 → 10.25 — but that number was scored on a 200-query subsample. Full training set: 17.84 → 40.60 (worse than Postgres).
{in-distribution} FAIL unseen queries 11.34→41.29 FAIL control +61%

Run 2 · hardened objective, same search

7.85
vs seed 11.34 on unseen queries · lower is better · rejected by the control gate
{held-out} PASS unseen queries · PASS holdout table · FAIL control

Run 1 · naive objective

1019283746550100200300400pg 17.8pg 11.3pg 10.341.340.610.0iteration (0 = seed)
fitness = GM + 0.5·P95 (train tables) — the loop's recorded objective (seed 17.84 → 10.25) was scored on a 200-query stage-1 subsample (see catches). Chart shows full sets on the fixed measurement formula, all lines.

Run 2 · hardened objective

79111315170100200pg 17.8pg 11.3pg 10.312.47.86.9iteration (0 = seed)
v2: GM + log₁₀P95 + log₁₀P99, + contradiction class in Q_train — the loop's recorded objective (seed 10.39 → 8.18) was scored on a 200-query stage-1 subsample (see catches). Chart shows full sets on the fixed measurement formula, all lines.
train queries (selected against) unseen queries, train tables {held-out} holdout tables {held-out} dashed flat = Postgres seed baseline on that same data ("pg") solid below its own dashed baseline = beating Postgres there · absolute levels differ across lines (train has 4× the queries → deeper error tails) — compare each line to its own baseline

Q-error by query class · geometric mean, log scale · all {held-out} · Run 2 · hardened objective

1x3xunseen queries · train tablesseed 1.53xchampion 1.53xcensus · eq-corrseed 1.31xchampion 1.32xcensus · mixedseed 1.24xchampion 1.24xcensus · range-corrseed 1.11xchampion 1.11xcensus · uncorrseed 3.54xchampion 2.62xdmv · eq-corrseed 1.42xchampion 1.43xdmv · mixedseed 1.20xchampion 1.20xdmv · range-corrseed 1.18xchampion 1.18xdmv · uncorrholdout tablesseed 2.12xchampion 1.41xcovertype · eq-corrseed 1.59xchampion 1.52xcovertype · mixedseed 1.93xchampion 1.93xcovertype · range-corrseed 1.41xchampion 1.41xcovertype · uncorr
seed (Postgres port) evolved champion

What evolution actually wrote · Run 2 · hardened objective

--- seed
+++ champion
@@ -20,7 +20,64 @@
         remaining = remove_clauses_on(remaining, a, b)
 
-    # Step 3: independence fallback for everything left.
+    # Step 3: independence fallback with contradiction detection for remaining clauses.
+    # For equality clause pairs sharing extended stats, check if combination exists in MCV.
+    # If absent but independence predicts non-zero, dampen by unobserved mass ratio.
+    processed = set()
+    eq_clauses = {c.col: c for c in remaining if c.op == "="}
+    
     for c in remaining:
-        sel *= marginal_selectivity(c, S)
+        if c.col in processed:
+            continue
+        s_c = marginal_selectivity(c, S)
+        sel *= s_c
+        processed.add(c.col)
+        
+        # For equality clauses, look for correlated columns in extended stats.
+        if c.op == "=" and s_c > 0.0:
+            for other in remaining:
+                if other.col in processed or other.col == c.col:
+                    continue
+                for g in S.groups():
+                    if c.col in g.cols and other.col in g.cols and g.mcv:
+                        s_other = marginal_selectivity(other, S)
+                        if s_other <= 0.0:
+                            continue
+                        
+                        litmap = {c.col: c.lit}
+                        if other.op == "=":
+                            litmap[other.col] = other.lit
+                        
+                        mcv_sel = 0.0
+                        base_matched = 0.0
+                        total_freq = 0.0
+                        for item in g.mcv:
+                            total_freq += item["frequency"]
+                            vals = dict(zip(g.cols, item["values"]))
+                            # For equality: exact match; for range: check bounds.
+                            if other.op == "=":
+                                if all(vals.get(col) == lit and vals.get(col) is not None
+                                       for col, lit in litmap.items()):
+                                    mcv_sel += item["frequency"]
+                                    base_matched += item["base_frequency"]
+                            else:
+                                # Range clause: check if c's literal matches and other's value
+                                # falls in the range. This is a coarse check; we use MCV to
+                                # detect if the pair co-occurs at all.
+                                if vals.get(c.col) == c.lit and vals.get(c.col) is not None:
+                                    mcv_sel += item["frequency"]
+                                    base_matched += item["base_frequency"]
+                        
+                        # If combination completely absent from MCV but independence predicts it,
+                        # apply dampening based on unobserved mass ratio.
+                        if total_freq > 0.0 and mcv_sel == 0.0 and base_matched > 0.0:
+                            # Combination never observed in training; dampen by unobserved ratio.
+                            unobs_ratio = 1.0 - (base_matched / total_freq)
+                            sel *= s_other * max(0.01, unobs_ratio)
+                        else:
+                            sel *= s_other
+                        
+                        if other.op == "=":
+                            processed.add(other.col)
+                        break
 
     return clamp(sel, 1.0 / S.row_count, 1.0)

The guard stack · every number earns its chip

run 1run 2
G1marginal fidelity vs EXPLAIN
dmv 61/61 · census 61/61 · covertype 59/60 within 1.5x
PASS
G2seed fidelity vs EXPLAIN, multi-clause
40/40 · 40/40 · 39/40 within 2x (after importing mcv.c's leftover-mass cap)
PASS
G3planted cheats rejected by lint
hardcoded row counts, column-name dispatch, banned imports — all caught pre-exec
PASS
G4loop smoke (mock + live claude)
40/40 live iterations; invalid children zeroed by guards
PASS
G5champion beats seed on unseen queries {held-out}
run 1: 11.34→41.29 · run 2: 11.34→7.85
FAILPASS
G6improvement holds on never-seen tables {held-out}
PASSPASS
G7no phantom win on the shuffled control
run 1: 61% swing · run 2: 24301%
FAILFAIL
What run 1 proved: a claimed 43% training win (real only on the loop's 200-query scoring subsample — see catches) was an optimistic co-occurrence prior on uncovered equality pairs — great on correlated combos, catastrophic on contradictory ones (city=Albany AND county=wrong). Unseen-query fitness 11.3→41.3; +61% on the shuffled control. The trait entered the lineage at iteration 10 and selection had no reason to remove it. The fix wasn't a patch — it was teaching the objective about the failure: 600 contradiction-class queries into Q_train and log-tail penalties. Holdout data stayed untouched throughout.

fed the loop

  • Q_train of dmv + census (4,797 queries)
  • run 2 only: +600 contradiction-class train queries
  • worst-case artifacts in mutation prompts

never fed the loop

  • 20% unseen queries on train tables
  • covertype — the never-seen table
  • shuffled-DMV control
  • the phrase "exponential backoff"

Champion anatomy · what run 2's winner actually does (final champion; per-query dissection 2026-07-18)

For some column pairs, Postgres keeps a list of the ~500 most common value combinations it saw while sampling the table. The champion added exactly one rule on top of Postgres:

the one rule it added
the queried combination is in that list → answer exactly like Postgres
the combination is not in the list → guess "about 1 row" (new)
in practice · Postgres → champion, vs reality
record_type=VEH AND reg_class=LTR 10,158 → 1 · really 1
city=JACKSON HGTS AND county=MONROE 41 → 1 · really 1
body_type=SUBN AND reg_class=CME 116 → 1 · really 23 (the downside)

That single rule changed the answers on 190 of 300 trap queries and 103 of 526 correlated-pair queries on the vehicle table (plus a small accidental side-effect on 21 census queries). Every other query — including everything involving ranges — gets the exact same answer as stock Postgres.

Does it make sense? Mostly, yes. For pairs with only a few dozen possible combinations, a 500-item list effectively contains every combination that exists — so "not in the list" really does mean "doesn't exist." Postgres never dares to make that leap (it lowers its guess, but never near zero); the champion does, and that one leap is where all its gains come from. The flaw: for pairs with thousands of combinations (city × county), "not in the list" usually just means "uncommon," and the champion wrongly flattens those to 1 row. On the unseen test data the wins outweighed the losses (−31% and −33%). But on the shuffled control table — where nothing is correlated, so "not in the list" means nothing at all — the same rule collapsed, and that is exactly why the control gate stamped run 2 FAIL.

What the dissection and the last gate caught

The evaluator was lying to the loop. openevolve 0.3.2's cascade recorded stage-1 metrics — the 200-query-per-table screening subsample — as every program's final fitness (verified byte-for-byte: the run-1 champion's stored 10.2493 reproduces exactly on the subsample; the full training set scores 40.60, worse than the seed's 17.84). Selection ran on the subsample all run long. Caught by re-scoring every checkpoint through an independent measurement path — N-version evaluation, the exact defense the eval-integrity literature prescribes for evaluator bugs.
The mirror-image catch (G7). On the independently-shuffled control table the champion's score explodes 2.61 → 637.56 (+24,301%): with independent columns, absence from a 500-item joint-MCV list doesn't mean "contradiction" — it just means "not in the top 500" — and the rule flattens thousands of real rows to 1. Run 1 failed this gate by optimism (+61%); run 2 by pessimism (+24,301%). The archive walk is brutal: all 51 elite programs carry the same rule; the first program that passes the control is the seed itself. Per the pre-registered rule, run 2's reported champion is the seed — the discovered mechanism is a lead for humans, not a shippable formula.
Boundary escape. The winning edit rewrote match_joint_mcv — a helper outside the declared # EVOLVE-BLOCK markers. The markers are a prompt convention; the diff engine enforced nothing, and the content lint (names, constants) had nothing to object to. Run 1's champion stayed inside the block; run 2's escaped. Caught by per-query dissection, not by any gate — "instructions are not controls," demonstrated on ourselves.
Dead code, twice. Both sophisticated-looking "coverage-weighted dampening" branches the model wrote are provably unreachable (their guard needs a matched base-frequency with zero matched frequency, but the two only increment together). What ships is the blunt binary rule. Read what executes, not what's written.
What stands. The held-out gains are real on correlated worlds (−31% unseen queries, −33% never-seen table) and the inference behind them is sound where joint-MCV coverage is high — a coverage-weighted version (exactly the branch the model wrote but couldn't reach) is the obvious human follow-up. And the harness earned its keep three distinct ways: G5 killed run 1's overfit, G7 killed run 2's brittle prior, dissection caught the boundary escape.