#401 (closed, completed) did the measuring and the fix was never filed. Its own final
number is the case for this issue:
|
columnar |
heap |
|
avg(m1), no join, vectorized aggregate on |
624 ms |
1,206 ms |
1.9x faster |
| the same via a join |
2,337 ms |
1,833 ms |
1.27x slower |
Same rows, same bytes, same encoding. Storage on that fixture was 52 MB against heap's
1,776 MB. The whole join shape family was behind:
| shape |
columnar vs heap |
| S1 selective dimension join |
1.27x slower |
| S2 unselective join |
1.07x slower |
| S3 multi-dimension star |
1.73x slower |
| S4 wide projection under a join |
2.65x slower |
(Those are #401's corrected numbers on realistic quantised data. Its first table used
random() float8 with 20M distinct values per column and overstated the gap; it should not
be quoted.)
The structural reason, from #401
We install set_rel_pathlist_hook, create_upper_paths_hook, build_simple_rel_hook,
object_access_hook and ProcessUtility_hook. There is no set_join_pathlist_hook, so
we never offer a join path and our optimisations stop at the scan and the aggregate directly
above it.
The cheap version, which reuses machinery that already exists
A full join path is not the only option and probably not the first one. The scan already
skips chunk groups using per-chunk bloom filters, wired into
pgcolumnar_native_group_can_match, for hashable columns with deterministic collation. A
star-schema join builds a hash table on the dimension side and then reads every chunk group
of the fact table, because nothing tells the scan which keys survived.
Passing the build side's key set down as a runtime filter would let the fact-table scan skip
groups on exactly the mechanism already in place. That is sideways information passing, and
it is the standard answer for this shape in every columnar engine.
What to settle first
Measure before building, on #401's fixture so the numbers are comparable to the table above.
#401 (closed, completed) did the measuring and the fix was never filed. Its own final
number is the case for this issue:
avg(m1), no join, vectorized aggregate onSame rows, same bytes, same encoding. Storage on that fixture was 52 MB against heap's
1,776 MB. The whole join shape family was behind:
(Those are #401's corrected numbers on realistic quantised data. Its first table used
random()float8 with 20M distinct values per column and overstated the gap; it should notbe quoted.)
The structural reason, from #401
We install
set_rel_pathlist_hook,create_upper_paths_hook,build_simple_rel_hook,object_access_hookandProcessUtility_hook. There is noset_join_pathlist_hook, sowe never offer a join path and our optimisations stop at the scan and the aggregate directly
above it.
The cheap version, which reuses machinery that already exists
A full join path is not the only option and probably not the first one. The scan already
skips chunk groups using per-chunk bloom filters, wired into
pgcolumnar_native_group_can_match, for hashable columns with deterministic collation. Astar-schema join builds a hash table on the dimension side and then reads every chunk group
of the fact table, because nothing tells the scan which keys survived.
Passing the build side's key set down as a runtime filter would let the fact-table scan skip
groups on exactly the mechanism already in place. That is sideways information passing, and
it is the standard answer for this shape in every columnar engine.
What to settle first
Those want different implementations and We have no join-heavy benchmark, and no columnar-specific join optimisation; measure before deciding on set_join_pathlist_hook #401's S4 row, a wide projection under a join at
2.65x, hints the second matters too.
set_join_pathlist_hook, which is a larger commitment.Measure before building, on #401's fixture so the numbers are comparable to the table above.