Repository navigation
Keep one handle per attached local database; add SYNCHRONOUS; validate JOURNAL_MODE - #272
Merged
staticlibs merged 2 commits intoOct 27, 2026
Conversation
…ion; validate JOURNAL_MODE Every SQLiteTransaction opens and closes its own sqlite3 handle. For a WAL-mode database that made each transaction the 'last connection', so SQLite ran a checkpoint-on-close after every commit: fsync of the WAL and the main file plus unlink of -wal/-shm. On a DuckLake metadata catalog this is ~5 fsyncs and 3 unlinks per snapshot. SQLiteCatalog now keeps one handle open for the lifetime of the attach (local on-disk files only; :memory: already had one, remote/VFS-backed files are read-only). The final checkpoint runs once, at DETACH, and the -wal file is removed as before. ATTACH gains a SYNCHRONOUS option (OFF|NORMAL|FULL|EXTRA or 0..3), applied to every handle. JOURNAL_MODE and SYNCHRONOUS values are validated against SQLite's keyword lists, which also closes the multi-statement injection that was possible through JOURNAL_MODE.
staticlibs
requested changes
Oct 26, 2026
staticlibs
left a comment
Member
There was a problem hiding this comment.
Hi, thanks for the PR! I think this change is useful, just with SQLite extension being used widely, I suspect some of its usage depends on the "close-and-reopen for every transaction" behaviour. Can we add an extension option whether to close or not the DB on transaction finish? And by default keep the existing behaviour, making the new logic opt-in.
New ATTACH options look good to me.
Nit: use {TEST_DIR} substitution syntax in tests instead of __TEST_DIR__.
To pass the formatting check run make format with these packages installed.
Review feedback: some users may rely on the close-and-reopen behaviour,
so keeping one handle open per attached on-disk database is now behind
a new extension setting, sqlite_keep_database_open (default false). The
setting is read at ATTACH; the default keeps the existing behaviour.
Tests use {TEST_DIR}; attach_wal_lifetime.test now also covers the
default (WAL removed after each transaction) and that changing the
setting does not affect a database that is already attached.
Contributor
Author
|
Thanks for the review! Done in 9b392c1:
|
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.
Requested by Alex
Problem
SQLiteTransactionopens its ownsqlite3*handle and closes it after commit. For an on-disk database in WAL mode, every such close is the last connection, so SQLite runs checkpoint-on-close after every transaction: fsync of the WAL and of the main file, then unlink of-wal/-shm, which the next transaction creates again. On a DuckLake metadata catalog stored in SQLite this means ~5 fsyncs per snapshot. DuckDB 2.0.0 behaves the same way.Related:
synchronous. DuckLake forwardsMETA_SYNCHRONOUS, which is rejected withUnsupported parameter for SQLite Attach: synchronous.JOURNAL_MODEis spliced intoPRAGMA journal_mode=...unquoted, soJOURNAL_MODE 'WAL; PRAGMA synchronous=NORMAL'runs both statements.Change
SET sqlite_keep_database_open = true, read at ATTACH; defaultfalsekeeps the existing behaviour):SQLiteCatalogkeeps one anchor handle open for local on-disk files until DETACH. The handle readssqlite_masteronce, because SQLite counts a connection as a WAL participant only after a read. Auto-checkpoint keeps the WAL bounded, and the final checkpoint and-walremoval happen once, at DETACH. The anchor is not used for:memory:, remote paths or VFS-backed paths.SYNCHRONOUS(OFF|NORMAL|FULL|EXTRAor0..3, case-insensitive), applied to every handle.JOURNAL_MODEandSYNCHRONOUSare validated against SQLite's keyword lists, which closes the multi-statement injection.Tests
test/sql/storage/attach_wal_lifetime.test, plus new cases inattach_options.test.main(DuckDB v2.0.0): all tests passed (3251 assertions in 72 test cases, 18 skipped for httpfs / TPCH env).Benchmark
300 single-row INSERTs into a DuckLake table (300 snapshots), SQLite catalog with
META_JOURNAL_MODE 'WAL', local NVMe,strace -f -e trace=fsync,unlink:-walunlinkssqlite_keep_database_open)META_SYNCHRONOUS 'NORMAL'The same commit applies to
v1.5-variegata(it was written there first), and I can open a backport if this is accepted.👹 Fixed by Goblin