Skip to content

feat(bench): Group A workloads — W2..W6 (sub-phase 9.2)#103

Merged
joaoh82 merged 1 commit into
mainfrom
bench-9.2-group-a
May 7, 2026
Merged

feat(bench): Group A workloads — W2..W6 (sub-phase 9.2)#103
joaoh82 merged 1 commit into
mainfrom
bench-9.2-group-a

Conversation

@joaoh82

@joaoh82 joaoh82 commented May 6, 2026

Copy link
Copy Markdown
Owner

Summary

Sub-phase 9.2 of docs/benchmarks-plan.md. Stacks on top of #102 (9.1 harness + W1) to land the OLTP-baseline workloads:

ID Shape Status
W2 Range scan — WHERE secondary >= ? AND <= ?, 100/1k/10k width buckets
W3 Bulk insert — BEGIN; INSERT…×100k; COMMIT
W4 Single-row insert — auto-commit per row on a 1k-row preloaded table
W5 Mixed OLTP — 50/50 SELECT-by-PK + UPDATE-by-PK on a 100k-row table
W6 Secondary-index lookup — WHERE secondary = ? (unique probes)

Smoke-run numbers (M1 Pro, criterion --warm-up-time 1 --measurement-time 1-2 --sample-size 10)

Workload SQLRite SQLite (rusqlite, WAL+NORMAL) Ratio
W1 read-by-PK ~19 µs ~2.4 µs ~8×
W2 range-100 ~36.6 ms ~60 µs ~610×
W2 range-1k ~32.2 ms ~594 µs ~54×
W2 range-10k ~33.8 ms ~6.5 ms ~5×
W3 bulk-100k ~1.35 s ~206 ms ~6.6×
W4 single-insert ~7.34 ms ~10.9 µs ~673× ⚠️
W5 mixed OLTP ~71.1 ms ~12.3 µs ~5,800×
W6 index lookup ~14.6 µs ~3.1 µs ~5×

The W2 ratio collapses as the matching set grows — SQLite scales with the result-set size; SQLRite full-scans the table in constant time regardless of width. That's because the engine's tiny optimizer (docs/supported-sql.md:210) only probes the index on <col> = <literal> shape — range predicates fall back to full scan. A future range-scan optimizer is the roadmap unlock.

Plan-spec adherence

  • W2 uses >= ? AND <= ? instead of BETWEEN x AND y — SQLRite doesn't ship BETWEEN yet (it's in supported-sql.md's "Not yet supported" list). Semantically equivalent; both engines parse + execute it cleanly. Bumping to BETWEEN once SQLRite supports it = W2.v2.
  • Q8 versioning preserved — every workload's WorkloadId.version is v1. JSON envelope tags every sample with W{n}.v{v}.
  • Q3 SQLite tuning honored — driver still applies the WAL + synchronous=NORMAL + 64 MB cache + temp_store=MEMORY profile from 9.1.

W4 100× heuristic — investigation follow-up filed

The plan's exit criterion for 9.2:

If W4 shows >100× gap, file a follow-up to investigate the commit path before moving on. (Investigation, not a gate — the gap is informational.)

W4 is at ~673× and W5 (which amplifies the same root cause across a 100×-larger preload) is at ~5,800×. Filed SQLR-18 — "Investigate W4 single-row INSERT gap (~673× vs SQLite)" — informational, not a release gate. Likely culprit per the existing project conventions doc is the bottom-up B-tree rebuild on every COMMIT; the task spec lists a flamegraph + roadmap-cross-link plan.

Also filed SQLR-17 — "Investigate desktop-build CI hang on Tauri Linux deps step" for the 39-min apt-get hang seen on the 9.1 PR's CI run, unrelated to this work.

Methodology choices documented in code

  • W3 uses iter_batched(BatchSize::PerIteration) with a per-sample counter so SQLRite's WAL sidecar from one sample doesn't collide with the next. sample_size(10) so a single-driver run finishes in minutes.
  • W4 preloads 1,000 rows in one transaction before timing starts. Without that, the first iters hit a near-empty table and later iters hit a iters_so_far-sized table — SQLRite's O(N) bottom-up rebuild would create a steep ramp through the bench window. Preload puts the table at a stable size.
  • W5 alternates SELECT/UPDATE deterministically (even iters SELECT, odd iters UPDATE) so the 50/50 mix is repeatable across runs.

Test plan

  • cargo fmt --all -- --check clean
  • cargo clippy -p sqlrite-benchmarks --all-targets clean
  • Full CI-equivalent: cargo build / test / doc --workspace --exclude {desktop,python,nodejs,benchmarks} green
  • cargo test -p sqlrite-benchmarks — 5 tests pass (4 inliner + 1 ymd round-trip)
  • Smoke cargo bench -p sqlrite-benchmarks --bench suite -- --warm-up-time 1 --measurement-time 1-2 --sample-size 10 runs all six workloads end-to-end on both engines
  • Aggregator post-processes the criterion output into a 14-row JSON envelope
  • W2 / W3 / W4 / W5 / W6 correctness gates pass on both drivers

Follow-ups

  • SQLR-18 — flamegraph the W4 hot path on SQLRite; cross-link to the roadmap entry for "Possible extras → Alternate storage engines."
  • SQLR-17 — only act if the desktop-build apt hang recurs.
  • 9.3 — Group B workloads (W7 aggregate, W8 GROUP BY, W9 INNER JOIN). Exercises the SQLR-3 (GROUP BY/aggregates) + SQLR-5 (JOIN) surface that landed in v0.7.0 / v0.8.0.
  • Prepared-statement support in SQLRite is the natural unlock for W1's parse-cost-dominated band; would shrink W1/W6/W2-100 ratios. Post-9.6.

References

🤖 Generated with Claude Code

Lands the OLTP-baseline workloads on top of the 9.1 harness:

  W2 range scan        WHERE secondary >= ? AND <= ?, three width
                       buckets (100/1k/10k). SQLRite full-scans
                       (no range index probe yet, see
                       supported-sql.md:210); SQLite uses the index.
  W3 bulk insert       BEGIN; INSERT..x100k; COMMIT in one txn.
                       iter_batched(PerIteration) so each criterion
                       sample starts from a fresh DB.
  W4 single-row        Auto-committed INSERTs against a 1k-row
                       preloaded table. Preload bounds SQLRite's
                       O(N) bottom-up rebuild at a stable table size.
  W5 mixed OLTP        50/50 SELECT-by-PK + UPDATE-by-PK on 100k
                       rows; even iters SELECT, odd iters UPDATE.
  W6 index lookup      WHERE secondary = ? on the unique secondary
                       index — both engines hit the index fast-path.

The plan calls for `BETWEEN x AND y` on W2; SQLRite doesn't ship
BETWEEN yet (`docs/supported-sql.md` "Not yet supported"), so the
workload uses the equivalent `>= AND <=` form. Bumping to BETWEEN
once the engine supports it = `W2.v2`.

Suite wiring:
  - benches/suite.rs grew per-workload register_w* fns sharing a
    db_path / tempdir_for helper. W2 fans into three criterion
    groups (one per width bucket) so each lands as its own
    BenchSample row in the JSON envelope.
  - W3's register_w3 is iter_batched + a per-sample counter so the
    SQLRite WAL sidecar from one sample doesn't collide with the
    next. sample_size(10) so a single-driver run finishes in
    minutes; override at the CLI for sharper estimates.
  - W4 setup pre-loads 1k rows in one BEGIN/COMMIT before the
    timed loop so per-iter cost reflects a stable table size,
    not a 0..N ramp.

Datasets:
  - data.rs gains GroupADataset (100k rows, secondary = unique
    permutation of 1..=100k; pre-shuffled pk_probes,
    secondary_probes, range_probes_{100,1k,10k}) seeded with
    GROUP_A_SEED=43.
  - W4 reuses a tiny stable payload (W4_PAYLOAD).

Smoke run (M1 Pro, criterion --warm-up-time 1 --measurement-time 1-2
--sample-size 10):
  W1 sqlrite ~19 us / sqlite ~2.4 us       — ~8x
  W2 100  sqlrite ~37 ms / sqlite ~60 us   — ~610x
  W2 1k   sqlrite ~32 ms / sqlite ~594 us  — ~54x
  W2 10k  sqlrite ~34 ms / sqlite ~6.5 ms  — ~5x
  W3       sqlrite ~1.35 s / sqlite ~206 ms — ~6.6x (per 100k-row txn)
  W4       sqlrite ~7.3 ms / sqlite ~10.9 us — ~673x  ⚠
  W5       sqlrite ~71 ms / sqlite ~12 us  — ~5800x
  W6       sqlrite ~15 us / sqlite ~3.1 us  — ~5x

Filed SQLR-18 ("Investigate W4 single-row INSERT gap") per the
plan's "if W4 shows >100x gap, file an investigation follow-up
before moving on" exit criterion. Likely culprit is the bottom-up
B-tree rebuild on every COMMIT (CLAUDE.md "B-tree commit
strategy") — informational, not a release gate.

Filed SQLR-17 ("Investigate desktop-build CI hang on Tauri Linux
deps step") for the unrelated 39-min apt-get hang seen on the 9.1
PR's CI run.

Verification:
  - cargo fmt --all -- --check clean
  - cargo clippy -p sqlrite-benchmarks --all-targets clean
  - Full CI-equivalent build / test / doc green
    (--exclude desktop / python / nodejs / benchmarks)
  - Smoke run end-to-end across W1..W6 + aggregator → JSON envelope

Refs: SQLR-16 (parent execution task), SQLR-18 (W4 follow-up).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant