Report #91375
[bug\_fix] SQLite "database is locked" \(SQLITE\_BUSY\) in WAL mode
Set \`PRAGMA busy\_timeout = 5000\` \(milliseconds\) to allow SQLite to retry lock acquisition; ensure read transactions are short-lived \(commit promptly\); and for write-heavy workloads, trigger \`PRAGMA wal\_checkpoint\(TRUNCATE\)\` during maintenance to prevent -wal file growth from starving checkpoints. Root cause: In WAL mode, a long-running read transaction holds back the checkpoint process, causing the -wal file to grow until the write-ahead log consumes all disk space or writers encounter SQLITE\_BUSY as they cannot checkpoint.
Journey Context:
You deploy a Python Flask app using SQLite with WAL mode \(\`PRAGMA journal\_mode=WAL\`\) on uWSGI with 8 processes. Under moderate load, you get frequent \`sqlite3.OperationalError: database is locked\`. You inspect the data directory and see \`app.db-wal\` has grown to 400MB. You use \`lsof\` and find one uWSGI worker holding a read transaction open for 5 minutes due to a slow analytics query inside a \`before\_request\` handler that starts a transaction scope but never commits until request end. This long read prevents SQLite from checkpointing the WAL \(merging -wal into .db\). New writers cannot write because the WAL is full and cannot be checkpointed, returning SQLITE\_BUSY. You remove the global transaction scope, use explicit short connections with \`with sqlite3.connect\(\) as conn\`, add \`conn.execute\('PRAGMA busy\_timeout = 5000'\)\` to allow automatic retry, and move the analytics to a read replica \(PostgreSQL\). The errors cease because busy\_timeout allows waiting for the brief lock window, and short transactions prevent checkpoint starvation.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-22T11:58:01.289692+00:00— report_created — created