From 8c89481d880c2bcd6d3907c16af807ec0a86a7a0 Mon Sep 17 00:00:00 2001 From: Kyle Sanderson Date: Wed, 1 Nov 2023 10:07:16 -0700 Subject: [PATCH] feat(sqlite): implement query planner (#1174) * feat(sqlite): implement query planner * implement Close on SQLite --- internal/database/database.go | 8 ++++++++ internal/database/sqlite.go | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/internal/database/database.go b/internal/database/database.go index 810b3c8..93c9351 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -83,6 +83,14 @@ func (db *DB) Open() error { } func (db *DB) Close() error { + switch db.Driver { + case "sqlite": + if err := db.closingSQLite(); err != nil { + db.log.Fatal().Err(err).Msg("could not run sqlite shutdown tasks") + } + case "postgres": + } + // cancel background context db.cancel() diff --git a/internal/database/sqlite.go b/internal/database/sqlite.go index 53fcd0a..da4eed2 100644 --- a/internal/database/sqlite.go +++ b/internal/database/sqlite.go @@ -37,6 +37,15 @@ func (db *DB) openSQLite() error { return errors.Wrap(err, "enable wal") } + // SQLite has a query planner that uses lifecycle stats to fund optimizations. + // This restricts the SQLite query planner optimizer to only run if sufficient + // information has been gathered over the lifecycle of the connection. + // The SQLite documentation is inconsistent in this regard, + // suggestions of 400 and 1000 are both "recommended", so lets use the lower bound. + if _, err = db.handler.Exec(`PRAGMA analysis_limit = 400;`); err != nil { + return errors.Wrap(err, "analysis_limit") + } + // When Autobrr does not cleanly shutdown, the WAL will still be present and not committed. // This is a no-op if the WAL is empty, and a commit when the WAL is not to start fresh. // When commits hit 1000, PRAGMA wal_checkpoint(PASSIVE); is invoked which tries its best @@ -63,6 +72,21 @@ func (db *DB) openSQLite() error { return nil } +func (db *DB) closingSQLite() error { + if db.handler == nil { + return nil + } + + // SQLite has a query planner that uses lifecycle stats to fund optimizations. + // Based on the limit defined at connection time, run optimize to + // help tweak the performance of the database on the next run. + if _, err := db.handler.Exec(`PRAGMA optimize;`); err != nil { + return errors.Wrap(err, "query planner optimization") + } + + return nil +} + func (db *DB) migrateSQLite() error { db.lock.Lock() defer db.lock.Unlock()