default to wal mode

This commit is contained in:
lowcarbdev
2026-01-21 00:07:51 -07:00
parent 9a6bf4ee9d
commit d4d997ea33
6 changed files with 94 additions and 18 deletions
+2
View File
@@ -1,6 +1,8 @@
# Backend
tmp/
*.db
*.db-shm
*.db-wal
messages.db
build-errors.log
sbv
+24
View File
@@ -2,6 +2,30 @@
This guide covers administrative features in SBV.
## Database Journal Mode
SBV uses SQLite with WAL (Write-Ahead Logging) mode by default. WAL mode provides better concurrent access, allowing you to browse messages while imports are running.
If your data directory is on a network filesystem (NFS, SMB/CIFS), WAL mode may not work correctly. In this case, use the `-journal` flag to switch to rollback journal mode:
Docker:
```bash
docker run ... ghcr.io/lowcarbdev/sbv:stable ./sbv -journal
```
Binary:
```bash
./sbv -journal
```
Docker Compose:
```yaml
services:
sbv:
image: ghcr.io/lowcarbdev/sbv:stable
command: ["./sbv", "-journal"]
```
## User Management
### List All Users
+14
View File
@@ -25,6 +25,20 @@ func InitAuthDB(filepath string) error {
return err
}
// Set busy timeout for better concurrent access
_, err = authDB.Exec("PRAGMA busy_timeout=5000;")
if err != nil {
return fmt.Errorf("failed to set busy timeout: %w", err)
}
// Enable WAL mode if requested (better for concurrent reads during writes)
if UseWALMode {
_, err = authDB.Exec("PRAGMA journal_mode=WAL;")
if err != nil {
return fmt.Errorf("failed to enable WAL mode: %w", err)
}
}
createTableSQL := `
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
+7 -3
View File
@@ -145,6 +145,7 @@ func (s *AutoImportService) processFile(userID, filePath, filename string) {
}
logWriter.log("Starting import of %s", filename)
startTime := time.Now()
// Get username from auth database
username, err := GetUsernameByID(userID)
@@ -192,10 +193,13 @@ func (s *AutoImportService) processFile(userID, filePath, filename string) {
completePath = filepath.Join(completeDir, filename)
}
duration := time.Since(startTime)
if parseErr != nil {
logWriter.log("ERROR: Import failed: %v", parseErr)
logWriter.log("File will remain in ingest directory for manual review")
slog.Error("Import failed", "userID", userID, "file", filename, "error", parseErr)
logWriter.log("Import duration: %s", duration)
slog.Error("Import failed", "userID", userID, "file", filename, "error", parseErr, "duration", duration)
} else {
// Move file to complete directory
if err := os.Rename(filePath, completePath); err != nil {
@@ -211,9 +215,9 @@ func (s *AutoImportService) processFile(userID, filePath, filename string) {
slog.Warn("Failed to move log file", "userID", userID, "error", err)
}
logWriter.log("Import completed successfully")
logWriter.log("Import completed successfully in %s", duration)
logWriter.log("File moved to: %s", completePath)
slog.Info("Import completed", "userID", userID, "file", filename)
slog.Info("Import completed", "userID", userID, "file", filename, "duration", duration)
}
}
+43 -15
View File
@@ -19,6 +19,9 @@ var db *sql.DB
var userDBs = make(map[string]*sql.DB)
var userDBsMutex sync.RWMutex
// UseWALMode controls whether WAL journal mode is enabled for databases
var UseWALMode bool
// truncateString truncates a string to maxLen characters for logging
func truncateString(s string, maxLen int) string {
if len(s) <= maxLen {
@@ -58,6 +61,20 @@ func InitDB(filepath string) error {
return err
}
// Set busy timeout for better concurrent access
_, err = db.Exec("PRAGMA busy_timeout=5000;")
if err != nil {
return fmt.Errorf("failed to set busy timeout: %w", err)
}
// Enable WAL mode if requested (better for concurrent reads during writes)
if UseWALMode {
_, err = db.Exec("PRAGMA journal_mode=WAL;")
if err != nil {
return fmt.Errorf("failed to enable WAL mode: %w", err)
}
}
createTableSQL := `
-- Unified table for SMS messages, MMS messages, and call logs
-- record_type: 1 = SMS, 2 = MMS, 3 = call
@@ -152,6 +169,20 @@ func InitUserDB(userID string, filepath string) error {
return err
}
// Set busy timeout for better concurrent access
_, err = userDB.Exec("PRAGMA busy_timeout=5000;")
if err != nil {
return fmt.Errorf("failed to set busy timeout: %w", err)
}
// Enable WAL mode if requested (better for concurrent reads during writes)
if UseWALMode {
_, err = userDB.Exec("PRAGMA journal_mode=WAL;")
if err != nil {
return fmt.Errorf("failed to enable WAL mode: %w", err)
}
}
createTableSQL := `
-- Unified table for SMS messages, MMS messages, and call logs
-- record_type: 1 = SMS, 2 = MMS, 3 = call
@@ -237,32 +268,29 @@ func InitUserDB(userID string, filepath string) error {
return nil
}
// GetUserDB retrieves the database connection for a specific user
// GetUserDB retrieves the database connection for a specific user, creating it if it doesn't exist
func GetUserDB(userID string, username string) (*sql.DB, error) {
userDBsMutex.RLock()
defer userDBsMutex.RUnlock()
userDB, exists := userDBs[userID]
userDBsMutex.RUnlock()
if !exists {
// Try to open the database if it exists
// Database not in cache, try to open or create it
dbPathPrefix := os.Getenv("DB_PATH_PREFIX")
if dbPathPrefix == "" {
dbPathPrefix = "."
}
// Use UUID as database filename instead of sanitized username
filepath := fmt.Sprintf("%s/sbv_%s.db", dbPathPrefix, userID)
if _, err := os.Stat(filepath); err == nil {
// Database file exists, try to open it
userDBsMutex.RUnlock()
if err := InitUserDB(userID, filepath); err != nil {
userDBsMutex.RLock()
return nil, fmt.Errorf("failed to open user database: %w", err)
}
userDBsMutex.RLock()
userDB = userDBs[userID]
} else {
return nil, fmt.Errorf("user database not found for user %s", username)
// InitUserDB will create the database if it doesn't exist
if err := InitUserDB(userID, filepath); err != nil {
return nil, fmt.Errorf("failed to initialize user database: %w", err)
}
userDBsMutex.RLock()
userDB = userDBs[userID]
userDBsMutex.RUnlock()
}
return userDB, nil
+4
View File
@@ -23,8 +23,12 @@ func main() {
// Parse CLI flags
resetPassword := flag.String("reset-password", "", "Reset password for the specified username")
listUsers := flag.Bool("list-users", false, "List all users")
journalMode := flag.Bool("journal", false, "Use rollback journal mode instead of WAL (for network filesystems)")
flag.Parse()
// Use WAL mode by default, unless -journal flag is set
internal.UseWALMode = !*journalMode
// Initialize slog logger
logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,