From 012b7f6fba49e9e752aa88a47f6e4c166ea5ba4e Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Wed, 25 Mar 2026 18:48:44 +0200 Subject: [PATCH] fix: reduce SQLITE_BUSY errors under concurrent load - Increase busy_timeout from 5s to 15s - Set synchronous=NORMAL (safe with WAL, reduces fsync) - Limit MaxOpenConns to 4 to reduce write lock contention - Explicit wal_autocheckpoint=1000 Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/storage/sqlite.go | 11 ++++++++++- internal/storage/sqlite_test.go | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/internal/storage/sqlite.go b/internal/storage/sqlite.go index fbe1c30..adcfb9a 100644 --- a/internal/storage/sqlite.go +++ b/internal/storage/sqlite.go @@ -39,10 +39,19 @@ func New(ctx context.Context, dataDir string) (*DB, error) { // Configure SQLite pragmas pragmas := []string{ "PRAGMA journal_mode=WAL", - "PRAGMA busy_timeout=5000", + "PRAGMA busy_timeout=15000", "PRAGMA foreign_keys=ON", + "PRAGMA synchronous=NORMAL", + "PRAGMA wal_autocheckpoint=1000", } + // Limit connection pool to reduce write contention. + // SQLite allows one writer at a time; multiple connections competing + // for the write lock cause SQLITE_BUSY errors. Keeping MaxOpenConns + // low reduces lock contention while still allowing concurrent reads. + db.SetMaxOpenConns(4) + db.SetMaxIdleConns(2) + for _, pragma := range pragmas { if _, err := db.ExecContext(ctx, pragma); err != nil { db.Close() diff --git a/internal/storage/sqlite_test.go b/internal/storage/sqlite_test.go index 2406cfc..d2f2e0c 100644 --- a/internal/storage/sqlite_test.go +++ b/internal/storage/sqlite_test.go @@ -68,8 +68,8 @@ func TestNew(t *testing.T) { if err != nil { t.Fatalf("failed to query busy_timeout: %v", err) } - if timeout != 5000 { - t.Errorf("busy_timeout = %d, want 5000", timeout) + if timeout != 15000 { + t.Errorf("busy_timeout = %d, want 15000", timeout) } // Verify database is usable