bench(W13): Phase 11.11b — concurrent-writers workload (SQLR-22 / SQLR-16)#133
Merged
Conversation
…R-16) Adds the headline Phase-11 differentiator workload to the SQLR-16 benchmark harness. Splits the original 11.11 scope: this slice ships the bench workload only; the Go SDK cross-pool sibling shape is now Phase 11.11c (a separate slice because it touches the Go binding architecture, not the bench harness). Workload shape (`benchmarks/src/workloads/concurrent_writers.rs`): CREATE TABLE counters (id INTEGER PRIMARY KEY, n INTEGER NOT NULL); -- preload 1_000 rows with n=0 -- 4 worker threads × 50 BEGIN/UPDATE/COMMIT cycles each -- BEGIN <CONCURRENT|IMMEDIATE>; -- UPDATE counters SET n = n + 1 WHERE id = ?; -- random in 1..=1000 -- COMMIT; -- retry on Busy/Locked Per-engine specifics flow through four new `Driver` trait methods, each with a sensible default so existing workloads (W1..W12) and drivers are untouched: - `connect_sibling(primary, path)` — SQLRite overrides to call `Connection::connect()` so workers share the primary's `Arc<Mutex<Database>>` (SQLRite's `Connection::open` takes `flock(LOCK_EX)`, so workers can't re-open). Default opens a fresh connection at `path` — works for SQLite (separate connections coexist under WAL). - `enable_concurrent_mode(conn)` — SQLRite runs `PRAGMA journal_mode = mvcc;`. Default no-op (SQLite's WAL + busy_timeout setup happens at `open`). - `concurrent_begin_sql()` — SQLRite returns `"BEGIN CONCURRENT"`, SQLite returns `"BEGIN IMMEDIATE"`. Default `"BEGIN"`. - `is_retryable_busy(err)` — SQLRite downcasts to `SQLRiteError::is_retryable()`; SQLite walks the anyhow chain for rusqlite's `DatabaseBusy` / `DatabaseLocked`. Default false. SQLite driver gains a `busy_timeout = 5s` pragma at open so its `BEGIN IMMEDIATE` blocks rather than fails on contention. SQLRite driver's `.map_err(|e| anyhow::anyhow!(...))` pattern in `execute` / `execute_with_params` was dropping the typed source — switched to `.map_err(anyhow::Error::new).with_context(...)` so the `SQLRiteError` survives anyhow wrapping and `is_retryable_busy` can downcast. Correctness gate: after a 4×10 burst, `SUM(n)` over the counters table must equal `n_workers × txs_per_worker`. Catches lost commits, double-counted retries, mis-handled Busy boundaries. Two new tests (`w13_sqlrite_correctness`, `w13_sqlite_correctness`) exercise the gate without the criterion harness overhead — useful because the full criterion suite has a long setup gauntlet for W1-W12 even when filtered to W13 only. Registered in `benches/suite.rs` under sample-size 20; criterion's JSON envelope will pick up `W13.v1` rows for both drivers. Pinned-host headline numbers will land with the first re-publication; v1 ships the workload + correctness gate so any future numbers stand on a verified base. Docs: - `docs/benchmarks-plan.md` — new Group D section with the W13 row; new sub-phase 9.7; post-9.6 ideas section moves to post-9.7 with W13 concurrency-curves + W13b hot-row contention added. - `benchmarks/README.md` — Group D added to the workload taxonomy; W13 section explains the per-engine BEGIN flavour + connection model + correctness gate. - `docs/roadmap.md` — Phase 11.11b promoted to ✅ shipped; new Phase 11.11c entry for the deferred Go SDK cross-pool sibling shape. Workspace: 615/615 Rust tests still pass; cargo fmt + clippy + doc all clean. `cargo test -p sqlrite-benchmarks --lib w13` passes both drivers in ~1.2s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
W13— concurrent writers, mostly-disjoint rows) to the SQLR-16 benchmark harness under a new Group D.1..=1000(~ 0.4% collision per op). SQLRite drivesBEGIN CONCURRENTacross siblingConnection::connecthandles; SQLite drivesBEGIN IMMEDIATEacross separaterusqlite::Connectionhandles serializing through the WAL writer lock.Drivertrait grows four optional methods (each with a sensible default so W1..W12 are untouched):connect_sibling,enable_concurrent_mode,concurrent_begin_sql,is_retryable_busy. SQLRite overrides all four; SQLite uses the defaults plus a newbusy_timeout = 5spragma at open.SUM(n)over the counters table must equaln_workers × txs_per_worker. Two new lib tests (w13_sqlrite_correctness,w13_sqlite_correctness) exercise it in ~1.2s without the criterion harness overhead.Why this is split from Phase 11.11
The original 11.11 bundled the REPL
.spawnwork, this bench workload, and a Go SDK cross-pool sibling shape into one slice. They diverged in scope:.spawnfor interactive demos.W13bench workload.database/sqldriver architecture and is genuinely independent of either.Per-engine specifics
connect_sibling(primary, path) → open(path)primary.connect()(in-process sibling, sharedArc<Mutex<Database>>)rusqlite::Connection::open(path)enable_concurrent_mode(conn) → ()PRAGMA journal_mode = mvcc;openconcurrent_begin_sql() → "BEGIN""BEGIN CONCURRENT""BEGIN IMMEDIATE"is_retryable_busy(err) → falseSQLRiteError::is_retryable()DatabaseBusy/DatabaseLockedBoth engines run the same retry-on-busy outer loop. Only SQLRite actually exercises the retry path under this workload's collision rate; SQLite's
busy_timeoutmakes its BEGIN block rather than fail.Subtle pitfalls hit during implementation
.map_err(|e| anyhow::anyhow!("…{e}…"))pattern inexecute/execute_with_paramswas dropping the typedSQLRiteErrorsource —is_retryable_busycouldn't downcast. Switched to.map_err(anyhow::Error::new).with_context(...)so the typed source survives. (Other callers don't depend on the downcast; the change is invisible to them.)Connection::opentakesflock(LOCK_EX)on the WAL sidecar. Workers 2-N can'topen()the same path — they'd fail to acquire the lock. Solved viaconnect_sibling, which SQLRite overrides to callprimary.connect(). Default opens a fresh connection (works for SQLite under WAL).PRAGMA journal_modedoesn't persist to disk; every freshConnection::openresets to the default (Wal). The W13 setup enables it once, thenrun_concurrentre-opens the file at each criterion sample — so we re-enable on every primary acquisition before any worker issues a BEGIN CONCURRENT. (Caught by the correctness gate failing with "BEGIN CONCURRENT requiresPRAGMA journal_mode = mvcc;first".)Docs
docs/benchmarks-plan.md— new Group D section; sub-phase 9.7; post-9.7 ideas list with W13 concurrency-curve sweep + W13b hot-row contention follow-ups.benchmarks/README.md— Group D entry; new W13 section explaining per-engine BEGIN flavour + connection model + correctness gate.docs/roadmap.md— Phase 11.11b promoted to ✅ shipped; new Phase 11.11c entry for the deferred Go SDK work.Test plan
cargo build --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --exclude sqlrite-benchmarks --all-targetscargo test --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --exclude sqlrite-benchmarks— 615/615cargo test -p sqlrite-benchmarks --lib w13— both drivers' correctness gates pass in ~1.2scargo build -p sqlrite-benchmarks --benches— W13 registers cleanlycargo fmt --all -- --checkcargo clippy --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --exclude sqlrite-benchmarks --all-targetscargo clippy -p sqlrite-benchmarks --all-targets— no new errorscargo doc --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --exclude sqlrite-benchmarks --no-depsmake benchlocally on the owner's host; lands in a follow-up PR alongside the regular bench-republish cadence)🤖 Generated with Claude Code