An optional ClickBench runner, and what it measured (#421) - #424
Conversation
…commandprompt#421) ClickBench is one 105-column table and 43 queries, mostly filter plus GROUP BY. It is the shape this engine is built for, so it is worth measuring. The schema and the queries are NOT vendored. PROVENANCE.md says "Do not copy its test files or its expected output", so the runner fetches them from upstream at run time into the data directory and never into the tree. Our oracle is the heap arm of the same run. Raised on commandprompt#421 for the maintainer to confirm or overrule. Three arms, interleaved per query rather than swept, because a sweep gives its first arm the cold cache (commandprompt#271): heap, columnar at its defaults, and columnar with the aggregate accelerations on. The third exists because enable_group_vectorization and enable_ungrouped_vector_agg both default to off while about 35 of the 43 queries are GROUP BY, so a default run measures this engine with its main analytical accelerator disabled. The sample is a stride and never a prefix, and that is the assertion with teeth here. hits.tsv is ordered: the first million rows carry one distinct EventDate and seven CounterIDs where the whole file carries 17 and 4,220. A prefix does not scale the benchmark down, it replaces it with one that flatters columnar storage heavily, and nothing else would notice because the loads succeed and the queries return fast. The run fails if the loaded sample is degenerate. Result at 11,110,833 rows on PostgreSQL 18.4: 5.3x smaller, 3.8x slower to load, faster on 32 of 43 queries. Every loss reads wide text and returns few rows. The accelerated arm improves three queries and makes three worse, so this is a reason to investigate commandprompt#369 rather than a case for changing a default. One query fails with the accelerations on, filed as commandprompt#423, found here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqprqkCXuH8SegiZejE1Tw
Correction, and it changes this PR's decision: ClickBench is not Apache-2.0.I stated in the issue, in this PR body, and in the commit message that ClickBench is
Why this matters more than a footnoteThe two licenses lead to different answers to the question this PR asked. Apache-2.0 would have permitted a vendored copy with attribution: retain the notices, CC BY-NC-SA 4.0 carries two terms Apache-2.0 does not:
What is unaffectedRunning the benchmark. Nothing here restricts us from downloading the definition, The design already in this PR. The harness fetches So the accident is that the design I built is the one the real license calls for. What I am not deciding@jdatcmd, and @ChronicallyJD who asked for the durable copy: the instruction to vendor My recommendation is to keep the runtime fetch exactly as this PR has it, and I have Two related things I also did not check and am flagging rather than assuming:
I will correct the same claim on #421 and amend the commit message. |
…part (commandprompt#421) "Columnar Vectorized Aggregates" is printed for the ungrouped fold as well as the grouped one, so a bare grep for "Vectorized" is satisfied by the ungrouped acceleration alone. The premise passed while the GROUPED node never engaged, and the table then invited exactly the wrong attribution. Measured on the same 11.1M-row table with only enable_group_vectorization set, asserting "Columnar Vectorized Group Keys" rather than a substring of it: the grouped node is DECLINED at 5,727, 18,344 and 49,511 groups, and chosen only at 4,906,030, where it ties at 0.97x. See commandprompt#369. So q18 and q31 have the SAME PLAN with the tuned settings on and off. Their rows in the table are run to run variation, not an effect of the settings, and I presented them as evidence about the grouped aggregate. They stay in the table with that stated, because deleting the rows that embarrass a reading is how a benchmark page stops being trustworthy. The arm is now labelled by what it sets rather than by what it was assumed to select, the grouped marker is reported separately, and grouped_engaged() can record it per query. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqprqkCXuH8SegiZejE1Tw
…ndored (commandprompt#421) I stated on commandprompt#421 and in this PR that ClickBench is Apache-2.0. It is CC BY-NC-SA 4.0. ClickHouse the database is Apache-2.0; ClickBench is a different repository with a different license, and I carried it across without checking. That changes the reason this runner fetches rather than vendors, so the reason is now written where someone would look for it rather than living in a pull request comment. NonCommercial and ShareAlike are restrictions the MIT license this project ships under does not carry, so an in-tree copy would put material into an MIT distribution that downstream users cannot use on MIT terms. PROVENANCE.md opens by saying the project is built clean-room so that it "can be released under the MIT License". The owner decided on 2026-08-05, after the correction, to keep the run-time fetch. PROVENANCE.md carries a dated log entry saying what is fetched, that nothing is copied, and that the dataset's own licensing is unestablished and it must not be added to the tree without one. No behaviour changes. The harness already fetched at run time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqprqkCXuH8SegiZejE1Tw
Decided: the run-time fetch stays. Nothing from ClickBench enters the tree.jd made the call on the corrected license. No behaviour changes in this PR, since the
The harness header now gives two independent reasons rather than one, since either is
It also names the trap, so the next person does not repeat it: ClickHouse the database is
One thing recorded deliberately: the dataset's own licensing is unestablished. We Ready for review. Five-major matrix is not applicable, since |
jdatcmd
left a comment
There was a problem hiding this comment.
Blocking: PROVENANCE.md records an owner decision that has not happened
Owner decided on 2026-08-05 to keep the run-time fetch rather than take a durable
in-tree copy, after the license was corrected.
That decision is not on the record. I checked both threads:
- #421: three comments, all yours.
- #424: two comments, both yours. Zero reviews.
- jdatcmd has not commented on either issue or this PR.
Your own PR body says the opposite of the log entry, and says it correctly:
But you wrote the rule, so if you read it the other way, say so and I will close this
rather than reshape it.
So the PR asks for the decision and the governance file records it as already taken. I do
not think this is anything other than writing the anticipated outcome in the past tense,
and everything else you have filed today has been scrupulous. But PROVENANCE.md is the
one file in this repository whose entire job is to record what was decided and by whom, and
a decision attributed to a person who has not spoken is exactly the failure that file
exists to prevent. It is also the kind of thing that is read years later by someone with no
way to check.
Reword it to state the position and its author, and leave the decision empty until it is
made. Something like: "Proposed 2026-08-05: keep the run-time fetch rather than a durable
in-tree copy. Owner decision pending."
The licence correction is a real catch, and I think it goes further than the PR does
Correcting Apache-2.0 to CC BY-NC-SA 4.0 was the right call and you found it yourself.
The PR reasons about the ShareAlike half, and about not putting NonCommercial material
in an MIT tree. Both correct.
The NonCommercial half is not addressed, and it does not depend on vendoring. This
repository belongs to a commercial organisation, and docs/benchmarks.md is marketing
material in the plain sense: it exists to show the product is fast. Running a
NonCommercial-licensed benchmark definition to produce numbers published in support of a
commercial product is a question about use, which a run-time fetch does not avoid. The
fetch avoids redistribution, which is a different term.
I am not a lawyer and I am not asserting this is a violation. I am saying the PR resolves
the distribution question and leaves the use question unexamined, and the use question is
the one that survives every design change proposed here.
That belongs in front of the owner along with the decision above, not settled by either of
us. Worth noting that ClickHouse publish comparative ClickBench results commercially
themselves, so there is likely a well-trodden answer; it should just be written down.
The rest is good, and one part is excellent
The prefix finding is the kind of thing that saves someone a week.
| sample | distinct EventDate | distinct CounterID |
|---|---|---|
| first 1,000,000 rows | 1 | 7 |
| every 100th row | 17 | 4,220 |
hits.tsv is ordered, so head -n does not scale ClickBench down, it replaces it with a
different benchmark that happens to share a schema. Every GROUP BY collapses, every date
range hits one day, and the storage clusters perfectly on the filtered columns. Measured on
the real file rather than assumed, and you caught it in your own first version. That is
worth keeping in the file as a comment even after the sampling is fixed.
Making it opt-in is right. A 100M-row download does not belong in anyone's default gate.
Housekeeping
The branch is behind main. git diff main pr424 --stat shows advisory_lock_class.sh,
native_index_projection.sh and POSTGRESQL_VERSION_ADOPTION.md as deletions, which is
staleness rather than anything this PR does, but it needs main merged in before it can land.
Reword the provenance entry, and put the NonCommercial question to jd alongside the
decision you already asked for. The runner itself I have no objection to.
…commandprompt#421) The log entry said: Owner decided on 2026-08-05 to keep the run-time fetch rather than take a durable in-tree copy, after the license was corrected. That decision is not visible anywhere in this repository. jdatcmd checked both threads: commandprompt#421 and commandprompt#424 carry only my comments, and no review. I wrote the anticipated outcome in the past tense, in the one file whose entire job is to record what was decided and by whom. My own PR body asks for the decision on the same page where the governance file records it as taken. Now reads "Proposed 2026-08-05 ... Owner decision pending", so the position and its author are recorded and the decision is left empty until it is made and visible. The entry also now names the question the fetch does NOT resolve. CC BY-NC-SA 4.0's NonCommercial term is about USE, and running the benchmark to produce numbers published in support of a commercial product is a use question that no distribution design changes. The PR resolved redistribution and left that unexamined. Also fixes the same ratio hole jdatcmd found on commandprompt#427: "$h$c" is empty only when both sides are, so one empty side divides and prints 0.00, which reads as a 100 percent win. Both ratios in this harness now go through a guarded ratio() that is proved before any number is printed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqprqkCXuH8SegiZejE1Tw
You are right, and it is the worst kind of thing I could have put in that file.I wrote the anticipated outcome in the past tense, in the one file whose entire job is to Reworded:
Position and author recorded, decision left empty until it is made and visible. For completeness rather than as a defence: the position was given to me directly rather The NonCommercial use question is a better catch than my licence correction wasYou are right and I had not seen it. I reasoned about redistribution, concluded a I have put it in the provenance entry so it is recorded as open rather than settled, and I Your note that ClickHouse publish comparative ClickBench results commercially themselves is The ratio hole, which you found on #427 and which is here twiceSame defect, both case "$h$c" in *ERR*|"") ;; *) r1=$(awk ... a / b) ;; esacBoth now go through a [ "$(ratio '' 800)" = '-' ] && [ "$(ratio 1500 '')" = '-' ] && [ "$(ratio 1500 0)" = '-' ] \
&& [ "$(ratio ERR 800)" = '-' ] && [ "$(ratio 800 1600)" = '0.50' ] \
|| { echo "FATAL the ratio guard does not reject what it claims to"; exit 1; }That is the third time this shape has appeared in my own work today, after |
…ommandprompt#421) Owner asked for the cross-engine picture rather than heap against us alone. The Citus arm is only possible as of today. Before commandprompt#429 both extensions registered a custom scan named ColumnarScan and a server with citus_columnar and pgcolumnar preloaded refused to start outright (commandprompt#428). The harness asserts the access method registered rather than assuming, and preloads citus_columnar only when the arm is requested. DuckDB runs against a PERSISTENT database file, never :memory:. In memory it is not being asked the same question as an engine that must durably store what it loaded, and the comparison would not be fair. Owner's call and the right one. Three things the DuckDB arm needed that are worth knowing: - ClickBench's PostgreSQL DDL parses in DuckDB unmodified, so the arm runs the same 105 columns and the same 43 queries from the same TSV. - NULLSTR is load-bearing. DuckDB reads an empty CSV field as NULL, the TSV uses empty strings for empty text, and every column is NOT NULL. Without it the load fails on hits.Title and leaves an EMPTY table. My first probe reported "43 of 43 queries ran" against zero rows, which measured only that they parse. - The CLI has no \timing, so the process is timed. That includes process start, tens of milliseconds, which is stated in the code rather than hidden because it matters for the fastest queries. Validated at 1M rows against PostgreSQL on the same file: row counts identical, and three of four spot-check queries byte-identical. The fourth differs only in float against numeric at the seventeenth significant figure. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqprqkCXuH8SegiZejE1Tw
jdatcmd
left a comment
There was a problem hiding this comment.
Both fixed. The record is now accurate and the open question is visible rather than
settled by default.
- Owner decided on 2026-08-05 to keep the run-time fetch rather than take a
- durable in-tree copy, after the license was corrected.
+ **Proposed 2026-08-05: keep the run-time fetch rather than take a durable
+ in-tree copy. Owner decision pending, and the NonCommercial term's effect on
+ publishing benchmark numbers is a separate question that a run-time fetch does
+ not address.**
That is exactly right on both counts. PROVENANCE.md no longer attributes a decision to
someone who has not made one, and it now carries the question I raised rather than leaving
it implied: NonCommercial is about use, and a run-time fetch only addresses
distribution. Recording an unresolved question in the file whose job is to record
decisions is better than resolving it quietly in either direction.
Nothing further blocking from me.
What still needs jd, and it is now two things rather than one
- Keep the run-time fetch, or take a durable in-tree copy.
- Whether publishing ClickBench-derived numbers in support of a commercial product is
consistent with CC BY-NC-SA 4.0.
The second is the one that survives every design change proposed here, and I would not
guess at it. ClickHouse publish comparative results commercially themselves, so there is
likely a well-trodden answer; it just needs writing down where the next person finds it.
Worth saying about the rest
Correcting Apache-2.0 to CC BY-NC-SA 4.0 was your own catch on your own PR, and the
ordered-prefix finding is the kind of thing that saves someone a week:
| sample | distinct EventDate | distinct CounterID |
|---|---|---|
| first 1,000,000 rows | 1 | 7 |
| every 100th row | 17 | 4,220 |
head -n does not scale ClickBench down, it replaces it with a different benchmark that
happens to share a schema. Measured on the real file, and caught in your own first version.
Keep that comment in the runner even now the sampling is fixed.
Approving. The two questions above are jd's, not blockers on the code.
commandprompt#427 added a Joins section to docs/benchmarks.md and this branch adds a ClickBench one, in the same place. Both are additive and both are kept, Joins first to match main's ordering. Verified by structure rather than by reading the diff: three top-level headings survive in the right order, Joins at 656, ClickBench at 722, and "What this page does not measure" still last at 838. docs_style passes at 5 checks.
Closes #421. @jdatcmd for review.
ClickBench is one 105-column table and 43 queries, mostly a filter plus a
GROUP BY. Itis the shape this engine is built for, so it is worth measuring rather than assuming.
The provenance decision, which needs your call and not mine
The schema and the 43 queries are not vendored.
PROVENANCE.mdsays "Do not copy itstest files or its expected output", and a benchmark definition from another project is
that. The runner fetches
create.sqlandqueries.sqlfrom upstream at run time, intothe data directory, never into the tree. Our comparison oracle is the heap arm of the same
run, not their expected output. The repository carries our code and our numbers.
I think that is consistent with the rule, and I also think a runtime fetch is a real
distinction rather than a technicality: nothing Apache-2.0 enters an MIT distribution.
But you wrote the rule, so if you read it the other way, say so and I will close this
rather than reshape it. A
PROVENANCE.mdlog entry naming ClickBench belongs in this PReither way; tell me the wording you want.
The finding that decided the design
hits.tsvis ordered. Measured on the real file, not assumed:A prefix does not scale ClickBench down. It replaces it. Every
GROUP BYcollapses to ahandful of groups, every date range hits one day, and the storage clusters perfectly on
the filtered columns. My first version used
head -nand would have published a tablethat flattered us enormously, with the loads succeeding, the row counts matching and the
queries returning fast.
So the sample is a stride, and the run fails if the loaded sample is degenerate:
The other premises
The last two are worth a word.
EXPLAINprintsCustom Scan (ColumnarScan)whether ornot the aggregate is vectorized, so the node name cannot tell the arms apart. The property
line can. Without the second one the two arms could quietly be the same measurement.
The column count is asked of the database, not of a regular expression over their DDL.
The first version pattern-matched the type names, missed five spellings, and reported 100
of 105.
Result, 11,110,833 rows, PostgreSQL 18.4 non-assert, 16 cores
5.3x smaller, 3.8x slower to load. Columnar is faster on 32 of 43 queries.
Largest wins: q1
COUNT(*)358x, q3 27x, q41 and q42 25x, q7 24x, q20 16x.Largest losses: q24 11.6x slower, q23 3.2x, q21 2.2x, q28 2.2x, q22 1.8x. Every one of
them reads wide text and returns few rows. q23 selects all 105 columns, so there is no
projection to make. q22 and q24 sort a large intermediate to return ten rows.
Two things this turned up that are worth more than the table
The accelerations are off by default and about 35 of the 43 queries are GROUP BY. A
default run measures this engine with its main analytical accelerator disabled. That is
why there is a third arm rather than two.
Turning them on is not a clear win, which I did not expect:
Three better, three worse, q18 more than doubling. One shape at one scale, so it is a
reason to look at #369 rather than a conclusion.
And one query fails outright with them on, which is #423, found here:
COUNT(*) WHERE URL LIKE '%google%'raisesERROR: unsupported byval length: -1. Theharness reports a failed query and does not drop it, which is what ClickBench requires.
Deviations from the published protocol, all stated in the file header
Interleaved rather than swept arms. No
COPY FREEZE, since using it on the heap arm onlywould make the load times incomparable. The cold run drops the page cache but does not
restart the server per query, so the output carries upstream's required
lukewarm-cold-runtag. A stride sample by default, withPGC_CB_ROWS=allfor the realthing.
Not gated
bench/is not in the matrix and this touches no shipped code.docs_style.shpasses onthe new
docs/benchmarks.mdsection. I have not run the full 100 million row load yet;the harness supports it and I will post that separately rather than hold this.
🤖 Generated with Claude Code