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 driving a local claude -p with the Haiku model) rewrite the combining logic, and judge it only on data it never optimized against. Run 1 fell into 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 · two gates failed

−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

The running champion at each iteration, re-scored on the full query sets with the fixed measurement formula: training queries (amber), unseen queries on the training tables (green), and held-out tables (purple). Dashed lines are the Postgres seed on the same data. Lower is better.
fitness on the fixed measurement formula, lower is better (y)1019283746550100200300400pg 17.8pg 11.3pg 10.341.340.610.0iteration (0 = seed)
What the loop itself saw was different: it scored every candidate on a 200-query screening subsample (seed 17.84 → 10.25 there), not on these full sets. The catches panel explains.

Run 2 · hardened objective

The same search with the hardened objective, plotted the same way: running champion per iteration, full query sets, fixed measurement formula, dashed lines for the Postgres seed. Lower is better.
fitness on the fixed measurement formula, lower is better (y)79111315170100200pg 17.8pg 11.3pg 10.312.47.86.9iteration (0 = seed)
The hardened objective the loop bred against: GM + log₁₀P95 + log₁₀P99, with 600 contradiction-class queries added to the training query set. The loop still scored it on a 200-query screening subsample (seed 10.39 → 8.18 there); the lines above use the full sets.
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

Geometric-mean q-error (the larger of estimate/truth and truth/estimate) per query class, the Postgres seed beside run 2's champion, on data the loop never saw. Lower is better.
geometric-mean q-error, log scale (y)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 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 · the gate behind every 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 before execution
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 the catches panel) was an optimistic co-occurrence prior on uncovered equality pairs: strong 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 was not a patch. It was teaching the objective about the failure: 600 contradiction-class queries into the training query set and log-tail penalties. Holdout data stayed untouched throughout.

fed the loop

  • the training query set: 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

Champion anatomy · what run 2's winner 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 makes 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 (scoring the same programs through a second, independently written scorer), the 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 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 unambiguous: 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 did its job three distinct ways: gate G5 killed run 1's overfit, gate G7 killed run 2's brittle prior, and per-query dissection caught the boundary escape.