Files
synapbus/internal/storage/sqlite.go
T
Algis DumbrisandClaude Opus 4.6 012b7f6fba 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) <noreply@anthropic.com>
2026-03-25 18:48:44 +02:00

81 lines
1.9 KiB
Go

// Package storage provides SQLite storage layer for SynapBus.
package storage
import (
"context"
"database/sql"
"fmt"
"log/slog"
"os"
"path/filepath"
_ "modernc.org/sqlite"
)
// DB wraps a *sql.DB with SynapBus-specific configuration.
type DB struct {
*sql.DB
}
// New opens a SQLite database with WAL mode, busy_timeout, and foreign keys enabled.
// If dataDir is empty or ":memory:", an in-memory database is used.
func New(ctx context.Context, dataDir string) (*DB, error) {
var dsn string
if dataDir == "" || dataDir == ":memory:" {
dsn = ":memory:"
} else {
if err := os.MkdirAll(dataDir, 0o755); err != nil {
return nil, fmt.Errorf("create data directory: %w", err)
}
dsn = filepath.Join(dataDir, "synapbus.db")
}
db, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
}
// Configure SQLite pragmas
pragmas := []string{
"PRAGMA journal_mode=WAL",
"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()
return nil, fmt.Errorf("execute %s: %w", pragma, err)
}
}
// Verify settings
var journalMode string
if err := db.QueryRowContext(ctx, "PRAGMA journal_mode").Scan(&journalMode); err != nil {
db.Close()
return nil, fmt.Errorf("verify journal_mode: %w", err)
}
slog.Info("database opened",
"dsn", dsn,
"journal_mode", journalMode,
)
return &DB{DB: db}, nil
}
// Close closes the database connection.
func (db *DB) Close() error {
return db.DB.Close()
}