← Back to Blog

Inner Join: Oleg Smirnov on parallel query execution

MariaDB Server has executed every query on a single thread since the beginning. That is changing. In the first episode of Inner Join, our new video series with the engineers of the MariaDB core team, I sat down with Oleg Smirnov of the Optimizer team to talk about parallel query execution: why it took this long, how the implementation works under the hood, and what the first numbers look like.

Inner Join is the sibling of Outer Join, where contributors from outside the core team talk about their work. The idea behind both is simple. A lot of interesting engineering happens inside MariaDB that nobody outside the team gets to see. These conversations put a light on it, and give anyone who wants to contribute a clearer picture of where help is needed.

The full conversation is on the MariaDB Foundation YouTube channel: watch the episode.

Meet Oleg

Oleg Smirnov
Oleg Smirnov

Oleg has been at MariaDB for five years, in the Optimizer team led by Sergei Petrunia. His work is execution plans: building optimal ones, finding inefficiencies in how queries run, and sometimes reaching into the execution pipeline itself, since optimizer work is never fully isolated from the rest of the server. “It’s not always easy, almost never easy,” he says, “but it brings some joy when we manage to solve something really complex.” The payoff is concrete: users’ databases run faster and their queries finish sooner.

He did not start in database internals. From the beginning of his career he worked with databases from the outside, first Oracle, tuning performance, optimizing query plans, designing schemas and writing stored logic as a developer and DBA. He was always curious how the engine worked inside, but never had a reason to look. About ten years ago he discovered open source databases, MySQL, Percona, Postgres and TimescaleDB, and realised he could simply start contributing. He sent patches to MySQL and to TimescaleDB, the Postgres extension. That path led to MariaDB and, finally, to the job he had been circling for years: database internals developer.

Why databases, of all the places a backend engineer could land? “You have everything in a database,” he says. Algorithms, data structures, memory management, CPU, even networking. Everything from backend development is in there, and it is the backbone of every system, including the AI companies that store their vectors and embeddings somewhere.

Outside work, Oleg tries to keep up with the gym, rides a bicycle, hikes and spends time with family. He also plays guitar. He played electric years ago, put it down for about a decade, and recently bought a classical guitar and started reading sheet music to work through beginner and intermediate classical repertoire. For a programmer, notation is a natural fit: a complete, logical system for encoding data that is nothing like a written language. He wonders aloud what human brains would look like if we had evolved to write in notes instead of letters.

Why parallel execution, and why now

The idea is not new. Everyone in the team understood that running a query on more than one core is the way to break through a performance ceiling, and many modern databases have done it from the start. MariaDB and MySQL historically have not. Every query ran serially on a single thread, and there is only so much a single thread can do, however clever the plan.

The reasons are historical. MySQL was born when servers had far fewer cores. Today a server is built to be used in a heavily parallel, concurrent way, and a database that cannot spread one query across cores leaves most of the machine idle. The conversation between Monty and Sergei that turned into a roadmap did not come out of the blue; the time had simply come, and other priorities that kept the work on the shelf had cleared. This year the team finally started implementing it.

Oleg is careful about the framing. Parallel query execution is still in development and has not shipped. This is a look inside the kitchen, not a release announcement. It is also not a MariaDB first in open source: Postgres has its own parallel execution, so the claim is narrower and more honest. Within the M-ecosystem, nobody has done this for actual query execution before.

How it works: chunks, a queue and workers

The approach is borrowed from MySQL, which implemented a limited form of parallelism for counting rows, collecting statistics and rebuilding indexes, but never for the query itself. MariaDB takes the same mechanism and applies it to real execution: joins, filtering, aggregation.

Picture a typical query: read the orders table, join it to customers on a key, group the result into an aggregation table, return the rows. Serially, that is one pipeline from start to finish. In the parallel version, the orders table is first split into chunks. Each chunk is joined to customers and aggregated by its own worker into its own temporary table, all workers running at the same time. When the workers are done, the partial aggregation tables are gathered into a final one and the result is returned. The simultaneity is the whole performance gain.

The work is split between two engineers. Rex Johnston built the SQL-layer side: the workers, the joins, the aggregations. Oleg built the storage side, and that is where the chunking happens. The team decided to implement InnoDB first, since it is the engine running in most production systems and the one known for reliability, stability and transactions.

Where the chunks come from. An InnoDB table is stored as a clustered index, ordered by primary key and organised as a B-tree: a root page, one or more levels of internal nodes, and leaf pages holding the actual rows. To split the table, the code reads the root page and makes one chunk per entry in it. A root page typically has one or two hundred entries, which is more than enough, because the degree of parallelism is usually 8 or 16, or whatever the core count allows. Parallelising beyond the number of cores makes no sense.

The task queue. The initial chunks go into a queue. Each worker pulls the next chunk and either processes it, or decides the chunk is too coarse and re-splits it. Re-splitting means going one level down the tree: the worker reads the internal node that chunk points to and creates a new, smaller chunk for each entry in it, appending them to the queue in place of the large one.

When to re-split. The trigger is imbalance. Imagine only two initial chunks and four workers: two workers sit idle. Or five chunks and four workers: the fifth one is a signal to break it into ten, twenty or a hundred smaller pieces so the load spreads evenly and no worker stays lazy. Re-splitting is the mechanism that turns a coarse first cut into a uniform distribution of work.

Output order. Workers finish in whatever order they finish, so the result of a parallel scan can come back ordered differently from a serial run. For queries without ORDER BY that is fine; the server is free to return rows in any order. Queries that need sorted results, because of ORDER BY, window functions or similar, are either not parallelised at this stage or, as the team discussed, have the sort applied to the final table. Oleg was candid that Rex, who was unable to join the recording, is the one to ask about exactly where that stands today.

Where it helps, and where it does not

The surprising finding is that parallelising a plain table scan buys almost nothing. If the table is already in the buffer pool, a scan is a read of memory, and modern hardware reads memory fastest in order, thanks to data locality. Split that scan across workers and you get random access, polluted caches and evicted cache lines. Serial was already the right answer.

What parallelises well is CPU work: joins, filtering and above all aggregation. A query computing sums, minimums, maximums or averages touches a lot of rows and updates in-memory structures for each one. That is work a core can own. Each worker takes its chunk, does its aggregation, and the partial results are merged at the end. In the team’s measurements, aggregation is where the biggest improvement showed up.

So the workloads that should notice first are analytical: queries that scan large InnoDB tables and reduce them to aggregates, with or without joins. Transactional point lookups are not the target.

The first numbers

Two sets of preliminary measurements exist, both on laptops rather than production servers, and Oleg presents them with every caveat attached.

The first is a single query with aggregation over a 100 million row table. Serial execution took 20 seconds. With two workers it took almost exactly half. With four workers the time roughly halved again, but a little less than 2x; the same with eight. Each doubling of workers gives a bit less than a doubling of speed, which is what a sensible implementation should show: there is contention, synchronisation, and the fixed cost of merging the partial aggregation tables into the final one. Sixteen workers gave almost no further gain, but Oleg’s laptop has around eight performance cores, so that result says more about the machine than the code.

The second set is TPC-H, the benchmark the team uses as its yardstick, run by Rex. Some queries ran five to seven times faster than serial execution; Q3 and Q6 are the ones Oleg points to as especially promising. For others, such as Q7 and Q8, the benefit is modest, and the team will be looking into why those shapes do not parallelise well. Some queries, including Q1 and Q2, do not appear in the results at all because their shape is not yet supported, so their performance is unchanged. The degree of parallelism in that run was probably no more than eight.

The honest summary: for the query shapes already covered, the gains are real and sometimes large, and MariaDB is getting more competitive on a benchmark where it has had to catch up.

What has to be true before it ships

The plan is to stop deliberately rather than chase every query shape. The team will define a first phase: a set of shapes that are guaranteed to execute correctly in parallel. That phase then goes through proper QA before anything is released.

Oleg will not commit to a date, and the reason is MariaDB’s priorities. Correctness and stability come first, performance second. Parallel execution is new logic in a system full of edge cases that are hard to predict in advance, and regressions are possible: a query that crashes the server or returns a wrong result is far worse than a query that is merely slow. So the gating condition is simple to state and hard to meet: no regressions.

His hope, speaking at the end of 2026, is that some first stage of parallel execution, well tested and guaranteed not to regress, is released in 2027, so that at least some queries start running faster.

Where the community comes in

Parallel execution is something the community has asked for repeatedly. MariaDB has received contributions aimed at it before, including one in 2026. Oleg’s first ask, once the first version is out, is testing: run it against your workloads and your query shapes and report what breaks or what does not speed up. Code contributions, whether improvements or bug fixes, would be welcome too. “If the community joins us,” he says, “we can double our efforts.”

That is exactly why this series exists. If you have been wondering how to contribute to MariaDB, knowing what is being built and where it is still rough is the best starting point.

Watch the full episode on YouTube. Oleg has also written a post on optimizer hints, another Optimizer team feature that many users do not yet know exists; it will appear on mariadb.org shortly.

If you are a MariaDB engineer with a story worth telling, or a contributor working on something you would like to show, get in touch. Inner Join and Outer Join are ongoing, and the next episode is always open.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *