fix(db): serialize SQLite WAL establishment before pooling

Switching a brand-new DB file to WAL is a one-time file-level change; letting
multiple pooled connections perform it concurrently during warm-up races the
switch and can surface a spurious lock (observed as intermittent 500s under the
integration harness). Open ONE connection to establish WAL before the pool
opens, so every pooled connection thereafter only re-asserts an already-WAL
file. Keeps per-connection foreign_keys + busy_timeout.
This commit is contained in:
OpenFUT Agent
2026-08-13 20:03:40 +00:00
parent 0360135322
commit 75b183077f
+16 -6
View File
@@ -1,7 +1,7 @@
use anyhow::Result;
use sqlx::{
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions},
SqlitePool,
ConnectOptions, Connection, SqlitePool,
};
use std::str::FromStr;
use std::time::Duration;
@@ -11,16 +11,26 @@ pub type Pool = SqlitePool;
pub async fn init_pool(database_url: &str, max_connections: u32) -> Result<Pool> {
info!("Connecting to database: {}", database_url);
// Per-connection options so EVERY pooled connection gets them (a one-off
// `PRAGMA` on the pool only configures whichever connection served it):
// WAL for reader/writer concurrency, foreign keys on, and a busy_timeout so
// a transient SQLITE_BUSY under concurrent access waits-and-retries instead
// of failing the transaction.
// Per-connection options so EVERY pooled connection gets them: WAL for
// reader/writer concurrency, foreign keys on, and a busy_timeout so a
// transient SQLITE_BUSY under concurrent access waits-and-retries.
let opts = SqliteConnectOptions::from_str(database_url)?
.create_if_missing(true)
.journal_mode(SqliteJournalMode::Wal)
.foreign_keys(true)
.busy_timeout(Duration::from_secs(5));
// Establish WAL on the file via ONE connection BEFORE the pool opens.
// Switching a fresh DB to WAL is a one-time file-level change; letting
// several pooled connections do it concurrently at warm-up races that
// switch and can surface a spurious lock. Serialize it here so every
// pooled connection thereafter only re-asserts an already-WAL file.
{
let mut conn = opts.clone().connect().await?;
sqlx::query("PRAGMA journal_mode=WAL")
.execute(&mut conn)
.await?;
conn.close().await?;
}
let pool = SqlitePoolOptions::new()
.max_connections(max_connections)
.connect_with(opts)