perf(qwp): avoid repeated validation, row promises, and frame planning - #65
glasstiger wants to merge 10 commits into
Conversation
A nullish call spelled like another column's case-folded key finds that entry by key. Cover a staged name whose key grows past the UTF-8 limit, so weakening the exact-spelling check in omitsNullish() fails the test.
A flush keeps non-decimal columns in the staged schema, so the existing post-flush lookalike checks never reached the published-schema fallback. Add a decimal case, whose scale lock a flush drops, and correct the comments that described the lookups.
Review:
|
| Benchmark | Base | Head | Change |
|---|---|---|---|
| trades | 40.0 | 49.9 | +25% |
| full dictionary | 43.4 | 53.1 | +22% |
| delta dictionary, steady state | 53.8 | 65.2 | +21% |
| sparse | 28.1 | 31.7 | +13% |
Tradeoff worth mentioning in the PR description. The null-value lookup in omitsNullish only speeds up a column the table already knows under exactly the spelling given, and in practice only all-lowercase names can match.
| Workload (20 null columns per row) | Base | Head | Change |
|---|---|---|---|
| All-lowercase columns populated once | ≈21 | ≈63 | about 3× faster |
| camelCase columns, or columns never populated | ≈20.5–21.3 | ≈18.4–20.9 | about 0–10% slower |
- The slowdown is about 8 ns per null cell, from two failed
Map.getcalls before the same validation as base. It mostly doesn't show up once network I/O is involved, so I did not report it as a finding. - The PR's own "validate nullish names and publish" bench is on that slower path, so it is flat: 9.38 at base, 9.22 at head.
- I tried looking up by the case-folded key instead (
qwpColumnNameKey). It was worse on every workload, so the current design is the better tradeoff.
Minor scope note, not a finding: the PR also adds the unrelated .agents/skills/review-pr/SKILL.md tooling file. The description says so.
The writer auto-flush test relied on the 100 ms interval default never firing, so a stall on a slow runner could add a time-based flush and change the asserted batch sizes. Disable the interval as the other auto-flush tests do.
addColumn() built the case-folded column key before validating the name, so rejecting a multi-megabyte invalid name cost heap in proportion to its length (about 300 MB for a 10M-character name). Names longer than maxNameLength are now rejected first, with the same error as before. Also covers writer.row() rejecting with the auto-flush it starts, which no test exercised.
Review — level 3 (full pass)Reviewed base Out of scope: CriticalNone. ModerateNone. Minor
What was verified
Summary
|
Review — level 3Verdict: approve. Reviewed Validation in isolated worktrees: all 1,111 tests passed (using the unchanged interop fixture), along with 39 Tradeoff: never-populated null columns now incur extra schema lookups. Two short in-process benchmark comparisons showed differences too small to establish a material regression. The local sender benchmarks supported the performance improvement's direction, but I did not reproduce the PR's live-server throughput figures. Submodules: none changed. |
Summary
.agents/skills/review-pr/SKILL.md, a port of thereview-prreview skill to Pi. It is tooling only and does not affect the published packages.Validation
pnpm format:check,pnpm typecheck,pnpm typecheck:test,pnpm eslintpnpm vitest run test/qwp/core.test.ts test/qwp/sender.test.ts(163 passed)pnpm test:dist(39 passed), andpnpm typecheck:dist.BATCH=50000) against the same base commit: builder median 511,684 → 655,621 rows/s (+28.1%); row API median 590,226 → 667,622 rows/s (+13.1%). All runs verified row counts. The container was stopped afterward.