Re-measure the cross-engine benchmarks on current main (#367) - #377
Conversation
|
Updated per your steer on #376: the q7 row is published with the skip scan available Your argument is the same one that put the index set per engine into the method, and
That is your blast-radius finding written for a reader who does not have the issue One thing this PR does not yet reflect: #378 bounds the penalty and q7 takes the |
Completes the 1.0-alpha benchmark pass (commandprompt#367). Four engines on the same 100,000,000 TSBS rows loaded from one file, at eb5c7ef, with the index set and the parallelism stated per row. Citus is measured rather than omitted. The previous table dropped it with a note saying it had not been re-measured. Both it and TimescaleDB turned out to be unreproducible from the box: cpu_ts held zero rows and the Citus cluster had no bench database at all, so those fixtures were rebuilt from the TSBS source before anything was measured. Every query is given at max_parallel_workers_per_gather 0 and 4. The serial number measures the storage format; the parallel number is closer to what an installation gets, and after commandprompt#362 the difference between them can be a different plan rather than only a different speed. Against the previous table, q2 improves 123,546 ms to 494 and q3 161,972 to 861. That is column projection, the fetch cache work and the planner fixes compounding. pgColumnar leads q5 and q6 with workers. TimescaleDB still leads every host-filtered query because its columnstore segments by hostname. Three things the table now records rather than hides: TimescaleDB cannot run the parallel arm on this host. A query that gets a parallel plan fails in a parallel worker, unable to read blocks 0..0 of a relation file. Those cells say so. They are not reported as the time the failed statement printed, which is small enough to look like a very fast query. Temp spill is reported beside every number, per commandprompt#358. Five cells spill. They are not the unstable ones: their spread across five runs is 1.00x to 1.04x, while the widest spread in the tables belongs to a cell that does not spill. q7 is a regression and says so. The planner declines an index scan the query wants, because the cost model charges the index path for the rows it returns rather than the rows the query reads. The same query runs in 769 ms with pgcolumnar.enable_index_fetch_penalty off, against 44,058 ms with the default. That is commandprompt#376, found by this pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pt#376) Per jdatcmd on commandprompt#376: publish the q7 row with the skip scan available and say what it depends on, rather than suppressing it. The table describes what a user gets, and which extensions are loaded is part of that, the same argument that put the index set per engine in the method. The 57x needs a consumer that reads part of an index path and then prices itself again. PostgreSQL has no such node, so a plain install takes the other plan and the penalty is correct for it. TimescaleDB supplies a skip scan, which is why this host meets it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous tables were eb5c7ef, which predates two merges that move rows: commandprompt#375 changed how the grouped node is priced, and commandprompt#378 bounded the index-fetch penalty. Patching one row would put two builds in one table, so the whole run was repeated: 384 timings and 64 spill cells on aeb7882. q7 is the change. It was 133,759 ms in serial and is 767 ms. The cost model charged an index path for the rows it returns rather than the rows the query reads, so a DISTINCT ON that reads one row per host was priced out of the index. That was commandprompt#376, found by this benchmark pass and fixed in commandprompt#378. The narrative now records it as fixed rather than as a live defect with a workaround. Two spilling cells also disappeared with it, because a sort of the whole table became a skip scan and a skip scan sorts nothing. Three cells still spill and are listed. Everything else moved within noise: no cell in either table has a spread wider than 1.04 times between its fastest and slowest of five warm runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The single-engine sections were eb5c7ef and the cross-engine ones are now aeb7882, which put two commits in one document. commandprompt#378 in particular touches the index-fetch penalty, and this half measures a point lookup and an index-only scan, so it had to be re-run rather than assumed unaffected. It was unaffected in direction. Every number moved down, heap included, because the box is quieter than it was during the 100M run: heap count(*) 172 ms to 136, heap filtered agg 206 to 154. The ratios hold. Projection is 20.6x against 21.2x, index-only 109x against 106x, and the point lookup is 11.90 ms against 15.64. Storage is byte for byte the same, which is the expected result for a change that touches only the planner. The whole document is now one commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
16d3efa to
99c434e
Compare
Re-measured end to end on merged main. The whole document is now one commit.
Why the whole thing rather than the q7 row. #378 merged, and the tables were Verified before launching, since three separate wrong results today came from build q7 is the change
174x, and it is #378 doing it. The narrative now records q7 as a defect that was found Two spilling cells disappeared with it. The old table had five; it has three. q7 on Everything else moved within noiseNo cell in either table has a spread wider than 1.04x between its fastest and The single-engine numbers all fell, heap included — heap Method unchangedFour engines, same 100M rows from one file, same
|
Almost. One factual gap on the headline table, and it is the check you raised yourself.Everything I can verify checks out:
The gapThe cross-engine setup list says: heap's entry names its indexes and pgColumnar's does not -- but pgColumnar's q7 The page proves it knows the difference matters: the parallel-scan section says This is the check you flagged on #367 -- I would merge on one sentence: what index set each of the four engines carries. I Nothing else blocking. Push that and I will merge without another round. |
The setup list named heap's indexes and not pgColumnar's, so it read as though pgColumnar had none. pgColumnar's q7 number depends entirely on a skip scan over (hostname, time DESC), which makes that the wrong thing to leave to inference. Queried rather than recalled. All four engines carry one btree on (hostname, time DESC). TimescaleDB also carries the (time DESC) index that create_hypertable makes, and 53 chunk indexes below the two. This is the check raised on commandprompt#367, where cpu_heap carried the index and cpu_pgc did not. The published table is the one place it should not be missing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Fixed in Queried rather than recalled, since you said guessing there is the wrong move: So: all four carry the same
That is the sentence the parallel-scan section already had going one way and the
|
Answered from the bench itself. Parity holds, so this is a wording fix, not a re-measure.jd gave me access to the bench host, so I stopped guessing and looked. What the four
Every engine has the same So the q7 comparison is fair and the table stands as measured. The only problem was I also confirmed the q7 plan is what the page claims, on the bench, right now: 3,348.75 against the 3,349 #378 reported. The bound is live on the bench and q7 is Merging. Since I now have the facts and you do not need another round for one |
|
Merged, and the index-parity line was already in the branch when I pulled -- you beat me to it, so no follow-up commit from me. Independently verified against the bench: pgColumnar, heap and Citus each carry That clears the last item on #367. |
|
Do not push the follow-up — it is already in. Our comments crossed: I pushed One occurrence of that paragraph, so a second commit would duplicate it. Your table and mine agree, which is the useful part: independently queried from the And thank you for checking the q7 plan live rather than taking the page's word for it. That closes the benchmark item on #367. Everything on that checklist is now done |
The narrative I wrote for #377 read more favourably than the numbers support, and one claim rested on a competitor crash. TimescaleDB is faster than pgColumnar on every query it completes: 1.6x on q4 and q5, 2.1x on q8, 2.7x on q7, 6.5x on q6, and 101x, 442x and 679x on q1, q2 and q3. The page did not say so. It said TimescaleDB "leads every query that filters one host", which is true and reads as though the rest were even. The claim that pgColumnar "leads the wide aggregate shapes with workers" was technically true and misleading. pgColumnar is first on q5 and q6 at four workers because TimescaleDB's parallel arm fails on this host. In serial, where TimescaleDB runs, it is ahead on both. The method section disclosed the failure; the paragraph making the claim did not repeat it, and that is where a reader forms the impression. The setup list said pgColumnar's storage is "in load order, which is time ascending". It is not. Measured correlation with physical order is 0.013 for time and -0.004 for hostname, so neither key is sorted. That last fact is also the explanation, which the page did not give. Measured on q2: 667 of 667 row groups read, none removed by filter, 99,995,680 rows filtered to return 4,320. Every stripe holds all 4,000 hosts, so each group's hostname minimum and maximum covers the whole set and no group can be skipped. TimescaleDB excludes all but one chunk on time and reads one hostname segment through an index, in about 2 ms. So the table measures pgColumnar in the layout that suits it least, and now says so. A user with this query shape would cluster on hostname, which is what segmentby does for TimescaleDB. That configuration is not measured, and the page now asks the reader not to treat the q1 to q3 gap as a property of columnar storage until it is. No numbers changed. This is the reading of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Commit 2: the cross-engine section, which completes this PR
1ba0686. Four engines on the same 100,000,000 TSBS rows, ateb5c7ef, with theindex set and the parallelism stated per row. 384 timing measurements plus a 64-cell
spill pass.
Citus is measured rather than omitted. The old table dropped it with "Citus was
not re-measured and is omitted rather than carried forward". Both it and TimescaleDB
turned out to be unreproducible from the box —
cpu_tsheld zero rows and theCitus cluster had no
benchdatabase at all — so both fixtures were rebuilt fromthe TSBS source before anything was measured. All four engines now carry the same
(hostname, time DESC)btree; Citus columnar accepts one, so this is genuine parityrather than a footnote.
What moved
q2 and q3 are 250x and 188x — column projection, the fetch cache work and the planner
fixes compounding. pgColumnar leads q5 and q6 with workers. TimescaleDB still leads
every host-filtered query, because its columnstore segments by
hostnameandpgColumnar stores in load order.
q7 went the wrong way, and that is #376
368 ms to 44,058 ms. It is not a storage property: the planner declines an index scan
this query wants, because the cost model charges the index path for the rows it
returns rather than the rows the query reads.
DISTINCT ONreads one row per host.Same query,
pgcolumnar.enable_index_fetch_penalty = off: 769 ms.Filed as #376, it is mine from #355/#362/#363, and it is on by default. The table
gives both numbers and points at the issue rather than presenting 44 s as a property
of columnar storage. I would not cut the release without deciding about it — that
is on #367 and it is your call.
Two things the table records rather than hides
TimescaleDB cannot run the parallel arm on this host. Any query that gets a
parallel plan fails in a parallel worker. Those cells say
fails. They are notreported as the time the failed statement printed — my harness was doing exactly that
until I noticed q5 reading 10,500 ms serial against 11 ms parallel, which is not a
speedup four workers can produce. psql prints a
Time:line for a failed statement;the harness now checks for
ERROR:as a distinct outcome.Spill is reported beside every number, per your #358 requirement — and it
qualifies the inference rather than confirming it. Five cells spill. Their spread
across five runs is 1.00x to 1.04x, while the widest spread in either table
(1.18x) belongs to a cell that does not spill. So "the spilling plan is the
unstable one" was a property of spilling at
work_mem = 4MB, where you measured the1.43x, not of spilling as such. At 256 MB the sort has room and the spill is cheap.
Your instruction stands and is followed; it is the inference the data corrects.
I nearly wrote "nothing spilled" off the first nineteen cells, which were all the small
host-filtered queries. The five that spill are the heavy shapes at the end.
Not re-measured, and stated
The read-stream section needs a PG18 built
--with-liburingand no such build existson this machine, so it keeps its earlier numbers and the preamble says so.
test/ste_check.pypasses on every user-facing document.