Skip to content

feat(storage): auto-VACUUM trigger after page-releasing DDL (SQLR-10)#91

Merged
joaoh82 merged 1 commit into
mainfrom
sqlr-10-auto-vacuum-trigger
May 5, 2026
Merged

feat(storage): auto-VACUUM trigger after page-releasing DDL (SQLR-10)#91
joaoh82 merged 1 commit into
mainfrom
sqlr-10-auto-vacuum-trigger

Conversation

@joaoh82

@joaoh82 joaoh82 commented May 5, 2026

Copy link
Copy Markdown
Owner

Summary

Compact the database file automatically after DROP TABLE / DROP INDEX / ALTER TABLE DROP COLUMN when the freelist exceeds a configurable fraction of page_count. Builds on SQLR-6 (which added the manual VACUUM; and the persisted freelist) — the freed pages already landed on the freelist, but the file stayed at its high-water mark until someone ran VACUUM; by hand. This trigger closes that gap.

  • Default-on at 0.25 (SQLite parity). Fresh Connections ship with Some(0.25).
  • Tunable via Connection::set_auto_vacuum_threshold(Option<f32>). Pass None to opt out (preserves the prior manual-VACUUM-only behavior). Out-of-range / NaN / Inf values return a typed error rather than silently saturating.
  • Skipped mid-transaction (no save → freelist is stale), on in-memory and read-only databases, and below a 16-page (64 KiB) floor so tiny files don't churn.
  • Hook lives at the end of process_command_with_render immediately after the existing auto-save (the freelist isn't accurate until the bottom-up rebuild populates it during save). Calls pager::vacuum_database directly — bypasses executor::execute_vacuum, whose user-facing status string and in-transaction rejection are wrong for a silent maintenance hook.
  • Per-Connection runtime state — not persisted in the file header. A SQL-level PRAGMA auto_vacuum is tracked separately as a follow-up (SQLR-13) so SDK consumers (Python/Node/Go/WASM/MCP/FFI) can tune the knob without per-binding glue.

Compatibility note

Behavior changes for existing files: a DROP TABLE / DROP INDEX / ALTER TABLE DROP COLUMN that pushes the freelist past 25% of page_count will now pay an extra full-file rewrite that didn't happen before. That's the intended fix for the bloat problem, but embedders who need the prior shape can call set_auto_vacuum_threshold(None) once after open.

Files

File Change
src/sql/db/database.rs New auto_vacuum_threshold: Option<f32> field + DEFAULT_AUTO_VACUUM_THRESHOLD = 0.25 + validating setter
src/sql/pager/freelist.rs New should_auto_vacuum(pager, threshold) heuristic + MIN_PAGES_FOR_AUTO_VACUUM = 16 floor
src/sql/mod.rs Detects page-releasing DDL; consults the threshold after auto-save and calls pager::vacuum_database when triggered
src/connection.rs Pass-through Connection::auto_vacuum_threshold() / set_auto_vacuum_threshold()
src/sql/pager/mod.rs, src/connection.rs (test mods) 8 + 1 regression tests
docs/pager.md, docs/supported-sql.md "Auto-VACUUM trigger (SQLR-10)" subsections; updated stale "file doesn't shrink until VACUUM" claim

Tests

Eight new pager-level regression tests in src/sql/pager/mod.rs cover:

  • Default-on path triggers on DROP TABLE
  • set_auto_vacuum_threshold(None) disables the trigger
  • DROP INDEX and ALTER TABLE DROP COLUMN paths both trigger
  • High-threshold (0.99) suppresses the trigger
  • Mid-transaction DROP does not trigger
  • Floor (MIN_PAGES_FOR_AUTO_VACUUM) suppresses on tiny DBs
  • Setter rejects NaN / Inf / out-of-range values

Plus one Connection-level test confirming the default and setter pass-through.

Test plan

  • cargo build --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --all-targets — clean
  • cargo test --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs — 380 engine + 19 MCP + 12 + 8 + others, 0 failures
  • All 14 VACUUM-touching tests pass (SQLR-6's drop_then_vacuum_shrinks_file regression intact)
  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace ... --all-targets — only pre-existing warnings (no new ones in SQLR-10 code)
  • cargo doc --workspace ... --no-deps — clean

Follow-ups (filed separately)

  • SQLR-13 — PRAGMA dispatcher + PRAGMA auto_vacuum knob, so SDK consumers can tune the threshold via SQL.
  • SQLR issue Pretty architecture overview #1 — Pre-existing CREATE INDEX panic (unknown paged-entry kind tag 0x4) on tables wide enough for the secondary index to need interior-node pages. Surfaced while writing the DROP INDEX test for this PR; unrelated to SQLR-10 itself.

🤖 Generated with Claude Code

Compacts the file in place after DROP TABLE / DROP INDEX / ALTER TABLE
DROP COLUMN whenever the freelist crosses a configurable fraction of
page_count (default 0.25, SQLite parity). The threshold is tunable
per-connection via Connection::set_auto_vacuum_threshold; pass None to
opt out and keep the manual-VACUUM-only behavior. Skipped mid-txn, on
in-memory / read-only DBs, and below a 16-page floor so tiny files
don't churn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@joaoh82 joaoh82 merged commit 470f58a into main May 5, 2026
15 checks passed
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