feat: add DevOptimizeDB flag to control SQLite optimization

Added a new DevOptimizeDB configuration flag (default true) that controls
whether SQLite PRAGMA OPTIMIZE and ANALYZE commands are executed. This allows
disabling database optimization operations for debugging or testing purposes.

The flag guards optimization commands in:
- db/db.go: Initial connection, post-migration, and shutdown optimization
- persistence/library_repository.go: Post-scan optimization
- db/migrations/migration.go: ANALYZE during forced full rescans

Set ND_DEVOPTIMIZEDB=false to disable all database optimization commands.
This commit is contained in:
Deluan
2025-11-25 19:48:53 -05:00
parent dc07dc413d
commit ca83ebbb53
4 changed files with 23 additions and 11 deletions
+3 -1
View File
@@ -131,6 +131,7 @@ type configOptions struct {
DevEnablePluginsInsights bool DevEnablePluginsInsights bool
DevPluginCompilationTimeout time.Duration DevPluginCompilationTimeout time.Duration
DevExternalArtistFetchMultiplier float64 DevExternalArtistFetchMultiplier float64
DevOptimizeDB bool
} }
type scannerOptions struct { type scannerOptions struct {
@@ -427,7 +428,7 @@ func validatePurgeMissingOption() error {
} }
} }
if !valid { if !valid {
err := fmt.Errorf("Invalid Scanner.PurgeMissing value: '%s'. Must be one of: %v", Server.Scanner.PurgeMissing, allowedValues) err := fmt.Errorf("invalid Scanner.PurgeMissing value: '%s'. Must be one of: %v", Server.Scanner.PurgeMissing, allowedValues)
log.Error(err.Error()) log.Error(err.Error())
Server.Scanner.PurgeMissing = consts.PurgeMissingNever Server.Scanner.PurgeMissing = consts.PurgeMissingNever
return err return err
@@ -609,6 +610,7 @@ func setViperDefaults() {
viper.SetDefault("devenablepluginsinsights", true) viper.SetDefault("devenablepluginsinsights", true)
viper.SetDefault("devplugincompilationtimeout", time.Minute) viper.SetDefault("devplugincompilationtimeout", time.Minute)
viper.SetDefault("devexternalartistfetchmultiplier", 1.5) viper.SetDefault("devexternalartistfetchmultiplier", 1.5)
viper.SetDefault("devoptimizedb", true)
} }
func init() { func init() {
+6 -1
View File
@@ -45,11 +45,13 @@ func Db() *sql.DB {
if err != nil { if err != nil {
log.Fatal("Error opening database", err) log.Fatal("Error opening database", err)
} }
if conf.Server.DevOptimizeDB {
_, err = db.Exec("PRAGMA optimize=0x10002") _, err = db.Exec("PRAGMA optimize=0x10002")
if err != nil { if err != nil {
log.Error("Error applying PRAGMA optimize", err) log.Error("Error applying PRAGMA optimize", err)
return nil return nil
} }
}
return db return db
}) })
} }
@@ -99,7 +101,7 @@ func Init(ctx context.Context) func() {
log.Fatal(ctx, "Failed to apply new migrations", err) log.Fatal(ctx, "Failed to apply new migrations", err)
} }
if hasSchemaChanges { if hasSchemaChanges && conf.Server.DevOptimizeDB {
log.Debug(ctx, "Applying PRAGMA optimize after schema changes") log.Debug(ctx, "Applying PRAGMA optimize after schema changes")
_, err = db.ExecContext(ctx, "PRAGMA optimize") _, err = db.ExecContext(ctx, "PRAGMA optimize")
if err != nil { if err != nil {
@@ -114,6 +116,9 @@ func Init(ctx context.Context) func() {
// Optimize runs PRAGMA optimize on each connection in the pool // Optimize runs PRAGMA optimize on each connection in the pool
func Optimize(ctx context.Context) { func Optimize(ctx context.Context) {
if !conf.Server.DevOptimizeDB {
return
}
numConns := Db().Stats().OpenConnections numConns := Db().Stats().OpenConnections
if numConns == 0 { if numConns == 0 {
log.Debug(ctx, "No open connections to optimize") log.Debug(ctx, "No open connections to optimize")
+4 -1
View File
@@ -7,6 +7,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/consts"
) )
@@ -21,11 +22,13 @@ func notice(tx *sql.Tx, msg string) {
// Call this in migrations that requires a full rescan // Call this in migrations that requires a full rescan
func forceFullRescan(tx *sql.Tx) error { func forceFullRescan(tx *sql.Tx) error {
// If a full scan is required, most probably the query optimizer is outdated, so we run `analyze`. // If a full scan is required, most probably the query optimizer is outdated, so we run `analyze`.
if conf.Server.DevOptimizeDB {
_, err := tx.Exec(`ANALYZE;`) _, err := tx.Exec(`ANALYZE;`)
if err != nil { if err != nil {
return err return err
} }
_, err = tx.Exec(fmt.Sprintf(` }
_, err := tx.Exec(fmt.Sprintf(`
INSERT OR REPLACE into property (id, value) values ('%s', '1'); INSERT OR REPLACE into property (id, value) values ('%s', '1');
`, consts.FullScanAfterMigrationFlagKey)) `, consts.FullScanAfterMigrationFlagKey))
return err return err
+2
View File
@@ -179,7 +179,9 @@ func (r *libraryRepository) ScanEnd(id int) error {
// https://www.sqlite.org/pragma.html#pragma_optimize // https://www.sqlite.org/pragma.html#pragma_optimize
// Use mask 0x10000 to check table sizes without running ANALYZE // Use mask 0x10000 to check table sizes without running ANALYZE
// Running ANALYZE can cause query planner issues with expression-based collation indexes // Running ANALYZE can cause query planner issues with expression-based collation indexes
if conf.Server.DevOptimizeDB {
_, err = r.executeSQL(Expr("PRAGMA optimize=0x10000;")) _, err = r.executeSQL(Expr("PRAGMA optimize=0x10000;"))
}
return err return err
} }