Skip to content
About
Main content

Morsel-driven parallelism

How do the cores of one machine share a query without waiting on each other?

This design is about ownership, which is why it belongs with execution models, although most of its consequences concern parallelism (Where parallelism comes from).

In exchange-based parallelism, the plan is parallelized at plan time. are inserted, the (DOP) is fixed, and each plan instance owns a thread.

Morsel-driven parallelism turns that upside down. There is one thread per core, permanently. The work is a queue of input chunks, called . A thread takes a morsel, runs the entire pipeline on it, and comes back for another.

Three consequences follow mechanically:

  1. The degree of parallelism becomes a run-time property. It can change in the middle of a query without re-planning.
  2. Imbalance at the scan is bounded by one morsel's duration instead of one 's duration. This is the most important sentence of this section: it converts the penalty from proportional to partition size into proportional to morsel size.
  3. Scheduling becomes local. A thread takes from its own NUMA-local queue (the queue of data stored in the memory nearest to its core) and only steals work from others when its own queue is empty.

Morsel size is the same division as batch size, one level up. Dispatching a morsel costs a queue operation plus warm-up: hundreds of nanoseconds to a few microseconds. You want a morsel to run for about 100 µs to 1 ms, so that dispatch is under 1% of the time. At about 10 ns per , that is 10⁴ to 10⁵ tuples (100 µs ÷ 10 ns = 10,000; 1 ms ÷ 10 ns = 100,000). From the other side, you want at least tens of morsels per core, so at the end of the stage is a small fraction of the total. Both bounds are granularity arguments. The window between them is about three orders of magnitude wide, which is why the parameter is not sensitive and nobody tunes it.

What morsel-driven does not solve: the pipeline breaker. All threads must finish the build side of a before any thread probes, so the barrier survives. The shared they build into is a contended resource: the queue explosion near full load (1/(1−ρ)) arrives inside the execution model. The usual remedy is to partition the table per thread and merge.