Repository navigation
Commit d3df3ba
Part of #23182 (row 5)
Row 5 of the test-budget card:
`packages/objectql/src/bulk-write-per-row-hooks.test.ts`, project
`local`, 16.1 s in the hourly battery's reading (run 38325245250). This
PR is that file's fix and nothing else; the card stays open for its
other rows. Test-only: one file, +37 / -15, no production file, no
changeset (`skip-changeset` applied).
## Where the time went (measured, not guessed)
The file's test bodies cost 3.1 to 4.0 s on a quiet local box, and the
module line is the number the budget reads. Instrumenting `seedTasks`
inside the real file gave, in file order, 453, 392, 636, 585 and 687 ms
for the five calls that push 10 001 (or 10 000) rows through
`engine.insert`: 2.75 s of 3.69 s, about 74 percent. Those five calls
belong to the ceiling cases (section 4 and D6). The last three are
slower than the first two as the heap climbs from 481 to 604 MB across
them. The remaining cost is the case that admits a batch exactly at the
ceiling (about 0.5 to 0.7 s: 10 000 rows, so 20 000 real per-row hook
dispatches) and about 0.25 s for the other 58 cases. There is no sleep,
no timer, no wait and no per-test kernel boot in this file; the cost is
row volume through `engine.insert`.
## What changed
The ceiling is judged on what the driver's `find` returns, so how those
rows got into the stub store is not what the five cases pin. The stub
driver gains a synchronous `seed(object, rows)` that stores the very
rows `create()` stores (`create` and `seed` now share one `put`), and a
helper `seedCeilingRows(driver, count, status)` lays down the matched
set. The five ceiling cases seed through it instead of `engine.insert`.
Every other seed in the file (2 to 4 rows) still goes through
`engine.insert`.
Why the rows are the same ones: the engine adds nothing to a row on
insert (a two-row probe gave `{title, status, id}` from `engine.insert`
and from the driver alike). One-off check, not committed: for 10 001
`todo` rows, 10 000 `todo` rows and 10 001 `stale` rows, `driver.find`
after `engine.insert` and after `seedCeilingRows` returned deep-equal
arrays, and `engine.count` read the same total.
## Before / after, wall of the file (module line, default reporter, file
alone, `--maxWorkers=2`, shared box, three runs each)
| reading | base file | this branch |
|:--|:--|:--|
| re-taken after a container restart, head eb84ae2 | 3149 / 3368 /
3426 ms | 963 / 871 / 837 ms |
| before the restart | 3987 / 3905 / 3598 ms (and 3635 ms in a one-off)
| 962 / 971 / 940 ms |
| CI-shaped package run, before the restart: whole `local` project, 429
files, `--maxWorkers=8` on 4 cores, the base file copied under a
temporary name into the same run | 8212 ms | 1780 ms |
Same 59 tests, same titles, same order. Slowest case after: "ADMITS a
batch exactly AT the ceiling, in both phases", 682 ms (its 20 000
dispatches are the behaviour it pins, so it cannot shrink without
weakening the pin); the next slowest is 50 ms. The card's 16.1 s was the
slowest of 20 CI runs, about 4.4 times this box's 3.6 s reading; the
same ratio puts this branch near 4 s in CI. That is a projection, not a
measurement: NOT MEASURED until the next refresh PR's list is read. The
single-file run's own `Duration` line (12 to 17 s) is dominated by the
cold import and transform phase (about 11 s), which is on no module
line, the same shape rows 1 and 4 noted.
## Pins preserved
No test deleted, no `.skip` / `.todo`, no timeout touched (the `60_000`
on the at-ceiling case is unchanged), no project change, every assertion
and every row count unchanged (10 001 and 10 000 still come from
`MAX_BULK_PER_ROW_HOOK_ROWS`). Per-row hooks on bulk writes are still
proven per row: the before and after hook counts at the ceiling are
still asserted at 10 000 each.
## Ablation (on-disk proof via `scripts/ablation-replace.mjs`, wrap
mode, restore proven: blob equals HEAD and `git diff HEAD` empty)
Each leg ran the branch file and a temporary copy of the base file in
one vitest invocation under the same mutation, and compared red sets by
full test name.
| leg | mutation | base file red | branch file red | red sets |
|:--|:--|:--|:--|:--|
| control | none | 0 / 59 | 0 / 59 | identical |
| A | `engine.ts` ceiling check never refuses (`if (verdict.kind)
return;`) | 3 / 59 | 3 / 59 | identical: the section 4 refusal, the D6
before-update refusal, the D6 before-delete refusal |
| B | `engine.ts` ceiling check also refuses at the ceiling (a guard
that refuses once `matched` reaches the limit) | 1 / 59 | 1 / 59 |
identical: the at-ceiling admit case |
| C | `engine.ts` predicate-update before-hooks never dispatched
(`rows.length > 99999999`) | 13 / 59 | 13 / 59 | identical |
| D | the new helper seeds one row short (`count - 1`) | n/a | 5 / 59 |
the five ceiling cases, so none of them is satisfied by an empty or
short set |
The subject (`engine.ts`) is imported by relative source path, so there
is no dist leg; the mutation reached disk (anchor 1 to 0, blob changed)
and every restore was verified.
## Verification (head eb84ae2)
- `pnpm --filter @objectstack/objectql typecheck`: exit 0, including
`check:test-typecheck` (39 files / 233 errors / 64 pinned signatures,
unchanged; 0 errors in this file; the file is in `tsconfig.test.json`'s
program, which is the one that covers it, and not in the default
`tsconfig.json` program). Re-taken after the final edit.
- eslint `--no-inline-config` on the changed file: 0 errors, 0 warnings
(1 file).
- Whole `local` project of the package, before the restart, on this
change: 429 files / 8222 tests passed (that count includes the temporary
base copy of this file).
- `node scripts/pm/dispatch-gates.mjs --commands`: 53 derived, 53 run,
all exit 0; `--ran` reconciliation: 53 derived, 53 run, 0 NOT-MEASURED,
0 UNRUN. Beyond that, the 11 declared wide-population families and the
52 further artifact-roster families (one of the 53 roster entries is
also in the commands list): 63 commands run, 59 exit 0. Four roster
gates need a pull-request or workflow-event context and exited non-zero
with their own "NOT MEASURED / NOT WIRED, no PR context" text, so they
judged nothing in that run: `check-closing-target-claim`,
`check-partof-closing-keyword`, `check-single-claim-paths`,
`pm/check-governed-queue-guard`. `check-partof-closing-keyword` was then
run on this body's own text (`PR_BODY` set): exit 0. The other three
stay NOT MEASURED locally; CI supplies their context.
`check:dual-build-cjs-loads` and `check:lean-entry-closure` first exited
3 (no dist); after a full workspace build (75 of 75 tasks) both exited
0.
## Acceptance notes
- The `60_000` timeout on the at-ceiling case could now be lowered; it
is left alone on purpose (the card forbids touching timeouts, and a
lowered one is a separate decision).
- `engine.insert` costs about 40 microseconds a row on a batch of 10 000
(about 0.4 s), growing as the heap fills. That is production cost, an
optimisation and not a defect, so no card.
- The pin for this row is the next refresh PR's list no longer naming
the file; that read is not available yet.
## Readings by provenance
A container restart killed the first run mid-flow (the worktree and
commit survived). Re-taken after it: the before/after module-line runs
in the first table row, typecheck, eslint, the full workspace build and
the whole gate run on head eb84ae2. From before the restart and still
valid because the head did not move: the first three-run set, the
in-file seed instrumentation, the CI-shaped package run, the equivalence
check and all ablation legs (their JSON and restore proofs are on disk).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent a69a40a commit d3df3ba
1 file changed
Lines changed: 37 additions & 15 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
296 | 296 | | |
297 | 297 | | |
298 | 298 | | |
299 | | - | |
| 299 | + | |
300 | 300 | | |
301 | 301 | | |
302 | | - | |
| 302 | + | |
303 | 303 | | |
304 | 304 | | |
305 | 305 | | |
| |||
322 | 322 | | |
323 | 323 | | |
324 | 324 | | |
325 | | - | |
| 325 | + | |
326 | 326 | | |
327 | | - | |
| 327 | + | |
328 | 328 | | |
329 | 329 | | |
330 | 330 | | |
| |||
870 | 870 | | |
871 | 871 | | |
872 | 872 | | |
873 | | - | |
874 | | - | |
| 873 | + | |
| 874 | + | |
875 | 875 | | |
876 | 876 | | |
877 | 877 | | |
| |||
890 | 890 | | |
891 | 891 | | |
892 | 892 | | |
893 | | - | |
894 | | - | |
| 893 | + | |
| 894 | + | |
895 | 895 | | |
896 | 896 | | |
897 | 897 | | |
| |||
908 | 908 | | |
909 | 909 | | |
910 | 910 | | |
911 | | - | |
| 911 | + | |
912 | 912 | | |
913 | 913 | | |
914 | 914 | | |
915 | 915 | | |
916 | | - | |
| 916 | + | |
917 | 917 | | |
918 | 918 | | |
919 | 919 | | |
| |||
1186 | 1186 | | |
1187 | 1187 | | |
1188 | 1188 | | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
| 1206 | + | |
1189 | 1207 | | |
1190 | 1208 | | |
1191 | 1209 | | |
| |||
1194 | 1212 | | |
1195 | 1213 | | |
1196 | 1214 | | |
| 1215 | + | |
| 1216 | + | |
| 1217 | + | |
| 1218 | + | |
| 1219 | + | |
1197 | 1220 | | |
1198 | 1221 | | |
1199 | 1222 | | |
| |||
1217 | 1240 | | |
1218 | 1241 | | |
1219 | 1242 | | |
1220 | | - | |
1221 | | - | |
1222 | | - | |
1223 | | - | |
1224 | | - | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
1225 | 1247 | | |
1226 | 1248 | | |
1227 | 1249 | | |
| |||
0 commit comments