Skip to content

Load a parallel arm beside the serial one, and guard it (#465) - #474

Merged
ChronicallyJD merged 1 commit into
commandprompt:mainfrom
ChronicallyJD:fix/465-bench-parallel-copy
Aug 7, 2026
Merged

Load a parallel arm beside the serial one, and guard it (#465)#474
ChronicallyJD merged 1 commit into
commandprompt:mainfrom
ChronicallyJD:fix/465-bench-parallel-copy

Conversation

@ChronicallyJD

Copy link
Copy Markdown
Collaborator

Closes #465.

bench/ loaded every engine with a single serial COPY, so our own benchmark understated our own loader by seven times: 87.2s serial against 12.4s at 16 workers, identical stored size. Those are #465's measurements, not new ones; this machine has no ClickBench dataset.

The arm

Reported beside the serial number, not instead of it. Serial COPY is the single-connection ingest path and parallel_copy is the bulk path; both are real capabilities.

It is deliberately not in ARMS: the 43 queries have nothing to say about a table that exists only to be loaded, and running them would double the run for no reading. That also means it needs its own row assertion rather than inheriting the one over ARMS, which it has.

PGC_CB_PCOPY_WORKERS=0 skips it entirely.

The guarding is the part that mattered

parallel_copy prepares one transaction per worker and the stock max_prepared_transactions is 0, so every parallel arm errors on its first worker and returns in about no time. #465 records a harness that printed those failures as

0.0s / 0.8s / 1.1s / 1.3s

which is indistinguishable from perfect scaling, and is the shape a reader would publish. Three things now stop that:

  1. the cluster is configured with one slot per worker
  2. a preflight refuses to start when the running cluster cannot supply them, asked of the cluster rather than of the file this script wrote, since an operator may point it at their own
  3. the loaded row count is asserted exactly, and ON_ERROR_STOP plus || die mean the load cannot return quietly

The preflight message names the setting, the value, and that it needs a restart, because the alternative is meeting a per-worker error in a load log and working back to the cause.

Why the guards are in their own file

bench/cb_guards.sh is sourceable, so test/bench_guards.sh can run in the ordinary five-major matrix. The benchmark needs a 15 GB download and a tuned cluster; its arithmetic needs neither, and a decision that could only be tested with the dataset would never be tested.

Both guards proved by removal

weakened to checks that fail
preflight always-true 2
row check as a plain string compare 1

The second proof caught a hole in my own test. The first version asserted only that one missing side is refused, and both of those pass with the numeric check deleted, because an empty string is unequal to a number either way. The case that distinguishes them is both sides missing, which a plain compare calls equal. That is the #418 trap, met while writing the test for it. The suite now asserts that case, and it is the one that goes red on removal.

Gate

PG18  bench_guards       rc=0  checks run: 14  PASSED
PG18  harness_selftest   rc=0  checks run: 40  PASSED
PG19  bench_guards       rc=0  checks run: 14  PASSED
PG19  harness_selftest   rc=0  checks run: 40  PASSED

harness_selftest alongside because this registers a new suite, and #473's sortedness and registration checks are what keep that honest.

Stated plainly: what is not covered

The wiring is not exercised end to end, because there is no ClickBench dataset on this machine. Proven here: the guards' arithmetic, that the source line resolves from another working directory, and that the script parses. The first real run is the first end-to-end exercise, and the preflight is what makes that run fail fast rather than publish a wrong number.

…ndprompt#465)

bench/ loaded every engine with a single serial COPY, so our own benchmark
understated our own loader by seven times: 87.2s serial against 12.4s at 16
workers on 2M ClickBench rows, measured on commandprompt#465, with identical stored size.

The arm is reported BESIDE the serial number rather than replacing it. Serial
COPY is the single-connection ingest path and parallel_copy is the bulk path;
both are real capabilities and both are worth publishing. It is deliberately not
in ARMS, because the 43 queries have nothing to say about a table that exists to
be loaded, and running them would double the run for no reading.

The guarding is the part that mattered. parallel_copy prepares one transaction
per worker and the stock max_prepared_transactions is 0, so every parallel arm
errors on its first worker and returns in about no time. commandprompt#465 records a harness
that printed those failures as

    0.0s / 0.8s / 1.1s / 1.3s

which is indistinguishable from perfect scaling and is exactly the shape someone
would publish. Three things now stop it: the cluster is configured with one slot
per worker, a preflight refuses to start when the RUNNING cluster cannot supply
them (asked of the cluster, not of the file this script wrote, because an
operator may point it at their own), and the loaded row count is asserted
exactly.

The guards live in bench/cb_guards.sh so the matrix can test them. The benchmark
needs a 15 GB download and a tuned cluster; its arithmetic needs neither, and a
decision that cannot be tested without the dataset would never be tested.
test/bench_guards.sh covers them in the ordinary five-major run.

Both guards are verified by removal. Weakening the preflight to always-true fails
two checks; weakening the row check to a plain string compare fails one.

That second proof caught a hole in my own test. The first version asserted only
that ONE missing side is refused, and both of those cases pass with the numeric
check deleted, because an empty string is unequal to a number either way. The
case that distinguishes them is BOTH sides missing, which a plain compare calls
equal -- the commandprompt#418 trap, met while writing the test for it. The suite now asserts
that one.

Not covered here: the wiring itself is not exercised end to end, because this
machine has no ClickBench dataset. What is proven is the arithmetic of the
guards, that the source line resolves from another working directory, and that
the script parses. The 7x figure is commandprompt#465's measurement, not a new one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2DvnWDM7g27ubDCQdXhky
@ChronicallyJD
ChronicallyJD merged commit 15e0de0 into commandprompt:main Aug 7, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bench/ loads serially and understates our own loader by 7x: wire in pgcolumnar.parallel_copy

2 participants