Our optimisations stop at the scan and the aggregate directly above it. A join is core's
node, and we get no say in join order, join method, or what reaches the columnar side.
Confirmed structurally: we install set_rel_pathlist_hook, create_upper_paths_hook,
build_simple_rel_hook (get_relation_info_hook below 19), object_access_hook and
ProcessUtility_hook. There is no set_join_pathlist_hook, so we never offer a join
path.
Measured, a columnar-to-heap join today:
Finalize GroupAggregate
-> Gather Merge (Workers Planned: 6)
-> Partial HashAggregate
-> Hash Join
Hash Cond: (c.hostname = d.hostname)
-> Parallel Custom Scan (ColumnarScan) on cpu_pgc c
-> Hash -> Seq Scan on hosts_dim d
That works, and it works because we are a table access method rather than a foreign data
wrapper. Cross-format joins have never needed anything from us.
First, what this is not
It will not help the cross-engine numbers. All eight TSBS queries in
docs/benchmarks.md are single-table:
$ grep -lciE '\bjoin\b' queries/tsbs/*.sql | wc -l
0
$ grep -hiE '^\s*FROM' queries/tsbs/*.sql | sort | uniq -c
8 FROM %T
There is no join in that benchmark to accelerate. Anyone reaching for this as a way to
close the TimescaleDB gap should reach for the clustering result instead, which is
measured and large.
The actual gap, which is a measurement gap
We have never measured a join-heavy workload on pgColumnar. Our whole performance story
is single-table scans and aggregates, which is the shape TSBS has. Star schemas and
dimension joins are the shape columnar storage is usually bought for, and we do not know
what we do on them. Not "we know and it is bad" -- we do not know.
That is the prerequisite. Proposing a join implementation before measuring whether we lose,
and where, is committing to work with no evidence behind it.
The mechanism worth evaluating, which is not join ordering
If there is a win available inside a table access method, the literature says it is
runtime filter pushdown, not join order. When the build side of a hash join produces a
small key set, pushing that set into the probe side's scan lets it skip data before
decoding it.
We are unusually well placed for this. The scan already skips chunk groups on zone maps and
already does equality skipping through pgcolumnar.enable_bloom_filter. What is missing is
a way to hand it a key set discovered at runtime rather than one written in the query.
Whether PostgreSQL's executor lets an extension do that cleanly is exactly the unknown.
Suggested sequence
- Build a join-heavy fixture and measure it. A star schema: one columnar fact table,
several heap dimensions, and the ordinary selective-dimension-join shape. Compare against
heap and against TimescaleDB, the same way the cross-engine page does, and publish the
result whichever way it goes.
- Read the plans, not just the timings. The question is where the time goes: decoding
rows the join then discards, or the join itself. Those lead to different work, and the
chunk-group counters already tell us which.
- Only then decide whether runtime filter pushdown is reachable, and only then whether
set_join_pathlist_hook is needed for it.
Step 1 is worth doing on its own merits regardless of what follows, because a columnar
extension with no join benchmark has a hole in its evidence.
Related
design/ROADMAP.md carries this as an open question, "which join-acceleration technique
returns the most inside a table access method, and how does it interact with the planner
and executor hooks". This issue is that question with the measurement step in front of it.
See also #395: that question living only in a design document is why it has sat unexamined.
Our optimisations stop at the scan and the aggregate directly above it. A join is core's
node, and we get no say in join order, join method, or what reaches the columnar side.
Confirmed structurally: we install
set_rel_pathlist_hook,create_upper_paths_hook,build_simple_rel_hook(get_relation_info_hookbelow 19),object_access_hookandProcessUtility_hook. There is noset_join_pathlist_hook, so we never offer a joinpath.
Measured, a columnar-to-heap join today:
That works, and it works because we are a table access method rather than a foreign data
wrapper. Cross-format joins have never needed anything from us.
First, what this is not
It will not help the cross-engine numbers. All eight TSBS queries in
docs/benchmarks.mdare single-table:There is no join in that benchmark to accelerate. Anyone reaching for this as a way to
close the TimescaleDB gap should reach for the clustering result instead, which is
measured and large.
The actual gap, which is a measurement gap
We have never measured a join-heavy workload on pgColumnar. Our whole performance story
is single-table scans and aggregates, which is the shape TSBS has. Star schemas and
dimension joins are the shape columnar storage is usually bought for, and we do not know
what we do on them. Not "we know and it is bad" -- we do not know.
That is the prerequisite. Proposing a join implementation before measuring whether we lose,
and where, is committing to work with no evidence behind it.
The mechanism worth evaluating, which is not join ordering
If there is a win available inside a table access method, the literature says it is
runtime filter pushdown, not join order. When the build side of a hash join produces a
small key set, pushing that set into the probe side's scan lets it skip data before
decoding it.
We are unusually well placed for this. The scan already skips chunk groups on zone maps and
already does equality skipping through
pgcolumnar.enable_bloom_filter. What is missing isa way to hand it a key set discovered at runtime rather than one written in the query.
Whether PostgreSQL's executor lets an extension do that cleanly is exactly the unknown.
Suggested sequence
several heap dimensions, and the ordinary selective-dimension-join shape. Compare against
heap and against TimescaleDB, the same way the cross-engine page does, and publish the
result whichever way it goes.
rows the join then discards, or the join itself. Those lead to different work, and the
chunk-group counters already tell us which.
set_join_pathlist_hookis needed for it.Step 1 is worth doing on its own merits regardless of what follows, because a columnar
extension with no join benchmark has a hole in its evidence.
Related
design/ROADMAP.mdcarries this as an open question, "which join-acceleration techniquereturns the most inside a table access method, and how does it interact with the planner
and executor hooks". This issue is that question with the measurement step in front of it.
See also #395: that question living only in a design document is why it has sat unexamined.