Skip to content

Opportunities from Abadi's materialization strategies and MonetDB/X100, with what we already implement #405

Description

@ChronicallyJD

Two more papers, both execution-layer where #403 was storage-layer.

  • Abadi, Myers, DeWitt, Madden. Materialization Strategies in a Column-Oriented DBMS.
    ICDE 2007.
  • Boncz, Zukowski, Nes. MonetDB/X100: Hyper-Pipelining Query Execution. CIDR 2005.

Same rules as #403: worked from the papers, anything we build is ours. Ranked, with what
we already have beside it.

First, the thing we already got right

X100's central argument is that neither extreme works. Tuple-at-a-time Volcano iteration
spends its time in interpretation, and MonetDB/MIL's full column materialization becomes
memory-bandwidth bound. Their answer is vectors sized to stay in CPU cache.

Their measured motivation, TPC-H Q1:

system time
MySQL 4.1 26.6 s
MonetDB/MIL 3.7 s
MonetDB/X100 0.50 s
hand-coded C 0.22 s

MySQL achieved an IPC of 0.7 and spent under 10 percent of its time on the actual
arithmetic.

We already do this. src/columnar.h:47:

#define COLUMNAR_NATIVE_VECTOR_LENGTH 1024	/* values per vector (fixed) */

Storage chunk group defaults to 10,000 rows, and the execution and skip vector is 1024
values. That is the separation X100 argues for, and 1024 is the size range they land on.
Recording it because it is worth knowing we are aligned, and because the next person to
read this paper should not file it as a gap.

1. Position-list intersection before materialization

The item worth measuring. Abadi's late materialization works like this:

first scan R.a and output the positions in R.a that satisfy the predicate. Repeat with
R.b and R.c. Next, use position-wise AND operations to intersect the position lists.
Finally, re-access R.a, R.b, and R.c and extract the values of the records that
satisfy all predicates.

Position lists are cheap to intersect, 32 or 64 at a time when held as bit-strings.

We have the ingredients: vector-level skipping (nativeSkipVec, the
Columnar Vectors Skipped counter), column projection, and deferred slot decode. What I
cannot tell from reading is whether multiple predicates on different columns intersect
their surviving positions before any values are materialized, or whether we materialize the
projected columns for surviving vectors and let a residual qual filter rows afterwards.

The q2 plan from today's work is suggestive but not conclusive:

Columnar Chunk Groups Read: 118 of 667
Columnar Vectors Skipped: 40
Rows Removed by Filter: 17,295,680

17 million rows removed by a filter means 17 million rows reached a filter. Whether they
were materialized first is the question. That is a measurement, not a claim, and it is
the first thing I would establish.

Abadi is also explicit about when late materialization loses:

the column reaccess cost at tuple reconstruction time is high

and worse when positions are not sorted, which is what a join does to them. So this is a
trade with a real downside, not a free win. The paper gives an analytical cost model for
choosing, which is more useful to us than the strategy itself.

2. Branch predication in selection

X100 Figure 2 measures the difference directly:

/* branch version */
if (src[i] < V) out[j++] = i;

/* predicated version */
bool b = (src[i] < V);
out[j] = i;
j += b;

The branch version has a worst case around 50 percent selectivity, where the predictor
cannot win. The predicated version is flat across selectivity and slightly more expensive
on average.

Our filter evaluation runs over 1024-value vectors, which is exactly where this applies.
Whether it is worth doing is a microbenchmark, and the paper says why the answer depends on
selectivity rather than being uniformly yes.

3. Vector size against column count

An open question rather than a finding. 1024 values of an 8 byte type is 8 KB per column.
A vectorized aggregate touching ten columns is working over 80 KB of live vector, which is
past L1 on most parts and into L2.

X100's argument is about staying in cache, so the right vector length may depend on how many
columns an operator touches at once. Ours is a fixed format constant, so this is not a knob
to turn casually. Worth knowing before anyone proposes widening a vectorized operator, and
it connects to the lesson in my own #289 work that eager per-group work regressed narrow
scans.

What I would break out

Only item 1, and only as a measurement first: establish whether multiple predicates on
different columns intersect positions before materializing. If they already do, this issue
closes with a note. If they do not, Abadi's cost model is the right basis for deciding
whether to, because the paper is equally clear about when the strategy loses.

Items 2 and 3 I would leave recorded here. Both are microbenchmark questions, and neither
should be touched without one.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions