feat(bench): DuckDB driver, Group B only (sub-phase 9.5)#106
Merged
Conversation
Adds the optional `duckdb-rs` driver to the bench suite, wired into
W7 / W8 / W9 (Group B only per the plan's viability section —
DuckDB's columnar / vectorized model is meaningful on read-only
SELECTs, aggregates, GROUP BY, and indexed range scans on read-
only data; misleading on per-row INSERTs / UPDATEs / OLTP-shape
PK probes).
Feature-gated: `cargo build -p sqlrite-benchmarks` stays lean and
fast as before. `make bench-duckdb` (and `cargo bench --features
duckdb`) pulls the heavy `libduckdb` cdylib (~50 MB compiled) and
the duckdb-rs crate into the build.
Driver shape mirrors the SQLite driver — open / execute /
execute_with_params / query_one / query_all. Two engine-specific
quirks worth knowing:
- DuckDB's `Statement::column_count` is only valid AFTER query
execution (it reads from the result struct, not the parse
tree). The driver pulls `cols` from the first `Row` via
`Row::as_ref().column_count()` instead — same numeric answer,
correctly post-execution.
- DuckDB widens `SUM(BIGINT)` to `HugeInt(i128)` defensively
even when the result fits in i64. The driver downcasts with a
range check; bails loudly on a genuine overflow.
Plan-doc + CLAUDE.md + bench README updated to reflect the new
driver. Makefile gains `make bench-duckdb`. The `duckdb` feature
declaration was empty in 9.1's Cargo.toml (placeholder for this
phase); now wired up properly.
Smoke run (M1 Pro, criterion --warm-up-time 1 --measurement-time 1
--sample-size 10):
W7 SUM(v) over 1M rows
sqlrite ~111 ms / sqlite ~31 ms / duckdb ~378 us
DuckDB ~80x faster than SQLite — vectorized columnar wins.
W8 GROUP BY at three cardinalities (10/1k/100k)
duckdb 634 us / 701 us / 20.4 ms
DuckDB 720x / 345x / 12x faster than SQLite. SQLRite × 100k
stays skipped (SQLR-19).
W9 INNER JOIN, per-PK probe (10k×10k)
duckdb ~500 us
~220x SLOWER than SQLite — per-PK-probe single-row JOIN is
OLTP territory; DuckDB's analytical optimizer pays much
higher per-query overhead. Plan flagged this as
"apples-to-oranges"; the directional measurement is the
point.
Verification:
- cargo fmt --all -- --check clean
- cargo clippy -p sqlrite-benchmarks --all-targets clean
- cargo clippy -p sqlrite-benchmarks --features duckdb --all-targets clean
- Full CI-equivalent build / test / doc green
(--exclude desktop / python / nodejs / benchmarks)
- cargo test -p sqlrite-benchmarks (default features) passes
- Smoke `cargo bench --features duckdb -- 'duckdb'` runs all
five DuckDB cells (W7 / W8×3 / W9) end-to-end
Refs: SQLR-16 (parent execution), Q4 (DuckDB inclusion gating).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tasks
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
Sub-phase 9.5 of
docs/benchmarks-plan.md. Builds on 9.1–9.4 (#102 / #103 / #104 / #105). Adds the optionalduckdb-rsdriver, feature-gated and wired into Group B (W7 / W8 / W9) only per the plan's viability section.make benchmake bench-duckdbcargo build -p sqlrite-benchmarkscargo build -p sqlrite-benchmarks --features duckdblibduckdb(~50 MB compiled)Smoke-run numbers (M1 Pro)
Two findings the plan predicted:
Vectorized columnar wins big on aggregate / GROUP BY workloads. W7 / W8 are exactly the shape DuckDB was built for, and it's ~80–720× faster than the same query on SQLite's tuned WAL+NORMAL profile. That's the "what does a different storage model look like?" sister number from the plan's "Why include DuckDB?" rationale.
DuckDB is much slower than SQLite on per-PK-probe single-row JOIN (W9). ~220× slower. Per-PK-probe + single-row OLTP is SQLite's home turf — DuckDB's analytical optimizer pays much higher per-query overhead. Plan flagged this as "apples-to-oranges"; the directional measurement is the point.
Engineering quirks worth knowing
Statement::column_countis only valid AFTER query execution (it reads from the result struct, not the parse tree). The first version of the driver crashed withThe statement was not executed yet; fixed by pullingcolsfrom the firstRowviaRow::as_ref().column_count().SUM(BIGINT)toHugeInt(i128)defensively even when the result fits in i64. The driver downcasts with a range check; bails loudly on genuine overflow.prepare_cachedon duckdb-rs (the C API doesn't have an LRU). Every query reprepares — honest for the comparison since the parser is part of what we're measuring.Test plan
cargo fmt --all -- --checkcleancargo clippy -p sqlrite-benchmarks --all-targetsclean (default features)cargo clippy -p sqlrite-benchmarks --features duckdb --all-targetscleancargo build / test / doc --workspace --exclude {desktop,python,nodejs,benchmarks}green (CI never sees the bench crate; this verifies nothing in the engine broke)cargo test -p sqlrite-benchmarks(default features) passes — 5 testscargo bench -p sqlrite-benchmarks --features duckdb -- 'duckdb'runs all five DuckDB cells (W7 / W8×3 / W9) end-to-endFollow-ups still open from prior sub-phases
What's left
9.6 — Reporting + first published run.
scripts/compare.pyto render any two JSONs into a Markdown diff table, the first official pinned-host JSON committed underbenchmarks/results/, and the canonicaldocs/benchmarks.mdreference + top-level README "Benchmarks" section.References
docs/benchmarks-plan.md— Q4 (DuckDB gating decision)benchmarks/src/drivers/duckdb.rs🤖 Generated with Claude Code