Skip to content

Citus columnar loads the same data 3x faster than we do, which contradicts why #300 was closed #445

Description

@ChronicallyJD

From the five-arm ClickBench baseline. It contradicts the reasoning #300 was closed on, so
it seemed worth its own issue rather than a comment on a closed one.

The measurement

11,110,833 rows, 105 columns, one cluster, one run, the same TSV file loaded by every
PostgreSQL arm through COPY.

arm load cost over heap size on disk
heap 140.8 s 1.00x 7.82 GB
Citus columnar 184.1 s 1.31x 1.66 GB
pgColumnar 548.7 s 3.90x 1.48 GB
DuckDB 28.2 s 0.20x 3.22 GB

Citus columnar pays 31 percent over heap to write columnar storage. We pay 290 percent.

We are 3.0x slower than Citus on the same file, in the same cluster, through the same core
COPY parser.

Why this contradicts #300's closing reasoning

#300 concluded that the residual gap to heap is dominated by PostgreSQL's own input path:

A profile of the post-#155 load shows that gap is dominated by PostgreSQL's own COPY
input path, not by anything pgColumnar-specific [...] Heap and TimescaleDB pay this
identically, because every engine funnels a text COPY through the same core parser and
per-row executor pipeline.

That reasoning is sound and the profile was real. But it predicts every columnar TAM lands
near the same number, because they all pay the same parse. Citus is a columnar table
access method going through the identical core parser and it lands at 1.31x.

So the parse floor is not what separates us from heap. Whatever is above it, in our own
write path, is now the dominant term. #300 could not see this because it compared against
heap and TimescaleDB. TimescaleDB compresses in a background job after the insert, so its
load number is not the same operation. Citus is the like-for-like comparison and nobody
had run it.

The Citus arm was only possible from today: before #429 a server with both extensions
preloaded refused to start (#428).

The honest caveat

We compress better: 1.48 GB against 1.66 GB, 12 percent smaller. Some of the extra time
buys that. But 12 percent smaller does not explain 3 times slower, and the trade as it
stands is a poor one to offer a user.

I have not profiled where our time goes, and I am not going to guess. #300's own method,
a profile of the load, is the right next step and it now has a specific question to answer:
what do we do per row that Citus does not.

Why it matters beyond the number

Load time is one of two figures ClickBench grades, alongside on-disk size, and it is the
first thing a user measures when trying a storage extension. A 3x deficit against the
nearest comparable extension is a competitive fact, not a micro-optimisation.

Reproducing

bench/run_clickbench.sh with PGC_CB_ARMS=heap,columnar,citus, on the branch in #424.
The Citus arm asserts its access method registered rather than assuming it, so a silent
fallback to heap cannot be mistaken for a fast columnar load.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions