Back to the blog
PostgreSQLAIPerformance

Query plans 81% faster than Postgres with a 4B model

October 10, 2026·6 min read·Diego Horvatti

A language model with only 4 billion parameters learned to build query plans that run 81% faster than the ones Postgres picks on its own. Rohan Bansal shares this in a post about the QORL project. Before you celebrate or write it off as hype, it's worth understanding what a query plan is, why the optimizer gets it wrong, and what this result changes for people who write SQL every day.

What a query plan is and why Postgres gets it wrong

When you send a SELECT with five JOINs, Postgres doesn't run it in the order you wrote it. It builds a plan. It decides which table to read first, whether to use an index or a seq scan, and whether each join will be a nested loop, hash or merge.

To choose, it estimates how many rows each step will return. The estimate comes from the statistics that ANALYZE collects. The problem is that these statistics assume columns are independent. In real life they almost never are.

Classic example: an address table with city and state. Postgres thinks filtering by city = 'Campinas' and state = 'SP' cuts the rows down twice. In practice, anyone in Campinas is already in SP. The estimate comes out way too low. Postgres picks a nested loop expecting to process 50 rows and ends up processing 500 thousand.

You've seen this happen:

EXPLAIN ANALYZE
SELECT ...
-- Nested Loop (rows=12) (actual rows=480312)

When the estimated rows and the actual rows are three orders of magnitude apart, the plan was wrong from the start. This isn't a Postgres bug. It's a genuinely hard problem. There's even a famous 2015 paper, "How Good Are Query Optimizers, Really?", that built the JOB benchmark (Join Order Benchmark) on top of IMDb specifically to expose these cardinality errors in every major database.

What QORL did differently

Using machine learning to optimize query plans isn't new. Neo (2019) and Bao (2021) already tried it. Bao, for example, doesn't replace the optimizer. It picks a set of "hints" (turn off nested loop here, force a hash join there) and lets Postgres build the plan within those rules.

What stands out about QORL is that it uses a small language model, 4B, trained for this task. It's not a giant GPT called through an API for every query. It's a model that runs on regular hardware and learned to look at a query and propose a better plan than the native optimizer.

The most common way to apply an external plan in Postgres is the pg_hint_plan extension, which accepts hints inside a comment:

/*+ Leading((t mi ci)) HashJoin(t mi) IndexScan(ci ci_movie_id_idx) */
SELECT ...
FROM title t
JOIN movie_info mi ON mi.movie_id = t.id
JOIN cast_info ci ON ci.movie_id = t.id
WHERE ...;

The model generates this kind of thing and the database follows it. You don't replace Postgres. You just give it a second opinion.

Read the original post for the details on the metric, because "81% faster" can mean very different things: mean, median, or total time across a whole benchmark. In benchmarks like JOB, a few pathological queries usually dominate the total time. Fixing three of them already moves the final number a lot.

The Postgres optimizer isn't dumb. It just trusts its own statistics too much.

Why a small model matters more than a big one

This is the part I find most interesting, and it's not the 81%.

Planning a query in Postgres takes microseconds or a few milliseconds. If you put a 70B LLM in front of every query, inference time eats the whole gain on any OLTP query. Nobody will wait 800 ms to save 40 ms.

A 4B model changes the math. It fits on a modest GPU, or even on a CPU with quantization. It's still not fast enough to run on every query in your app, but it starts to make sense for:

  • analytical reports that take seconds or minutes
  • nightly ETL jobs
  • dashboards with heavy, repetitive queries
  • queries you already know cause trouble

In those cases, spending 200 ms thinking to save 20 seconds is a done deal.

What changes in your code today

In practice, almost nothing changes tomorrow. And that's fine. Don't put a model in front of your production database because you read a post. But the result shows where the money is, and you can attack the same problem with tools that already exist.

1. Measure before you guess. Enable pg_stat_statements and find out which queries consume the most total time. Usually it's five or six.

SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

2. Look for estimation errors. Run EXPLAIN (ANALYZE, BUFFERS) on those queries and compare rows with actual rows. Where the gap is big, the optimizer is guessing.

3. Teach Postgres the correlation. The city and state problem has had a native fix since Postgres 10:

CREATE STATISTICS address_city_state (dependencies, ndistinct)
ON city, state FROM addresses;

ANALYZE addresses;

A lot of people have never used CREATE STATISTICS. It's the cheapest way to fix a good share of bad plans, and it needs no AI at all.

4. Increase the sample where it matters. Columns with skewed distributions benefit from more sampling:

ALTER TABLE orders ALTER COLUMN status SET STATISTICS 1000;
ANALYZE orders;

5. Use hints only as a last resort. pg_hint_plan works, but it freezes a decision. When the data changes, the forced plan can become the worst possible plan. And nobody will remember that magic comment a year from now.

The objections worth taking seriously

"So the optimizer will be replaced by AI?" Not anytime soon. The traditional optimizer has a quality no model has yet: it's predictable. You can explain why it chose a plan. When a model proposes a weird plan, debugging gets much harder.

"What if the model gets it badly wrong?" That's the real risk. A network that's right 95% of the time and makes things worse the other 5% can take production down. That's why approaches like Bao work with hints and keep Postgres in control. The worst case matters more than the average. And yes, that applies even if you've never had a 3 a.m. incident because of a nested loop. Yet.

"Does it work on my schema?" It depends. Models trained on a benchmark tend to learn the benchmark. Your e-commerce app with 40 tables and messy data isn't IMDb. Generalization is the question every work like this needs to answer, and it's the first thing I'd check before trusting it.

My take

I like this kind of result because it's honest about where AI can help. It's not a chatbot writing SQL for you. It's a small model tackling an old, well-defined problem with a clear metric: the query got faster or it didn't.

I think the future here is hybrid. The traditional optimizer stays in charge and a lightweight model suggests fixes for heavy analytical queries, where the cost of inference pays for itself. Until that becomes a stable Postgres extension, the best investment is still doing the basics well: pg_stat_statements, EXPLAIN ANALYZE and CREATE STATISTICS. That alone solves more problems than you'd think.

If you like seeing this kind of optimization applied to real projects, take a look at what I've been building in my projects.

LinkedIn summary

A 4B model built query plans 81% faster than the ones Postgres picks on its own.

The optimizer isn't dumb. It just trusts its own statistics too much and assumes city and state are independent columns.

What caught my attention wasn't even the number. It was the model size: 4B runs on regular hardware and already pays off for heavy reports, ETL and dashboards.

But before you put AI in front of your database, do the basics: pg_stat_statements, EXPLAIN ANALYZE and CREATE STATISTICS. That alone fixes more bad plans than you'd think.

I wrote the full step by step on the blog. What's the worst query you ever had to hunt down?

#PostgreSQL #SQL #Performance #ArtificialIntelligence #BackendDev