Fix various migration issues (#5723)

* Indicate while backing up database
* Close migrate connection to db before optimising
* Don't vacuum post-migration

In most cases is probably not needed and can be an optonal user-initiated step

* Ensure connection close on NewMigrator error
* Perform post-migration using migrator connection

Flush WAL file at end of migration
This commit is contained in:
WithoutPants 2025-03-19 08:04:21 +11:00 committed by GitHub
parent 529e4f6514
commit daed09e487
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 63 additions and 15 deletions

View file

@ -42,8 +42,8 @@ func (s *MigrateJob) Execute(ctx context.Context, progress *job.Progress) error
logger.Infof("Migrating database from %d to %d", schemaInfo.CurrentSchemaVersion, schemaInfo.RequiredSchemaVersion) logger.Infof("Migrating database from %d to %d", schemaInfo.CurrentSchemaVersion, schemaInfo.RequiredSchemaVersion)
// set the number of tasks = required steps + optimise // set the number of tasks = backup + required steps + optimise
progress.SetTotal(int(schemaInfo.StepsRequired + 1)) progress.SetTotal(int(schemaInfo.StepsRequired + 2))
database := s.Database database := s.Database
@ -61,12 +61,20 @@ func (s *MigrateJob) Execute(ctx context.Context, progress *job.Progress) error
} }
} }
progress.ExecuteTask("Backing up database", func() {
defer progress.Increment()
// perform database backup // perform database backup
if err := database.Backup(backupPath); err != nil { err = database.Backup(backupPath)
})
if err != nil {
return fmt.Errorf("error backing up database: %s", err) return fmt.Errorf("error backing up database: %s", err)
} }
if err := s.runMigrations(ctx, progress); err != nil { err = s.runMigrations(ctx, progress)
if err != nil {
errStr := fmt.Sprintf("error performing migration: %s", err) errStr := fmt.Sprintf("error performing migration: %s", err)
// roll back to the backed up version // roll back to the backed up version
@ -87,6 +95,11 @@ func (s *MigrateJob) Execute(ctx context.Context, progress *job.Progress) error
} }
} }
// reinitialise the database
if err := database.ReInitialise(); err != nil {
return fmt.Errorf("error reinitialising database: %s", err)
}
logger.Infof("Database migration complete") logger.Infof("Database migration complete")
return nil return nil
@ -124,6 +137,8 @@ func (s *MigrateJob) runMigrations(ctx context.Context, progress *job.Progress)
defer m.Close() defer m.Close()
logger.Info("Running migrations")
for { for {
currentSchemaVersion := m.CurrentSchemaVersion() currentSchemaVersion := m.CurrentSchemaVersion()
targetSchemaVersion := m.RequiredSchemaVersion() targetSchemaVersion := m.RequiredSchemaVersion()
@ -144,21 +159,15 @@ func (s *MigrateJob) runMigrations(ctx context.Context, progress *job.Progress)
progress.Increment() progress.Increment()
} }
// reinitialise the database // perform post-migrate analyze using the migrator connection
if err := database.ReInitialise(); err != nil {
return fmt.Errorf("error reinitialising database: %s", err)
}
// optimise the database
progress.ExecuteTask("Optimising database", func() { progress.ExecuteTask("Optimising database", func() {
err = database.Optimise(ctx) err = m.PostMigrate(ctx)
progress.Increment()
}) })
if err != nil { if err != nil {
return fmt.Errorf("error optimising database: %s", err) return fmt.Errorf("error optimising database: %s", err)
} }
progress.Increment()
return nil return nil
} }

View file

@ -430,7 +430,19 @@ func (db *Database) Vacuum(ctx context.Context) error {
// Analyze runs an ANALYZE on the database to improve query performance. // Analyze runs an ANALYZE on the database to improve query performance.
func (db *Database) Analyze(ctx context.Context) error { func (db *Database) Analyze(ctx context.Context) error {
_, err := db.writeDB.ExecContext(ctx, "ANALYZE") return analyze(ctx, db.writeDB)
}
// analyze runs an ANALYZE on the database to improve query performance.
func analyze(ctx context.Context, db *sqlx.DB) error {
_, err := db.ExecContext(ctx, "ANALYZE")
return err
}
// flushWAL flushes the Write-Ahead Log (WAL) to the main database file.
// It also truncates the WAL file to 0 bytes.
func flushWAL(ctx context.Context, db *sqlx.DB) error {
_, err := db.ExecContext(ctx, "PRAGMA wal_checkpoint(TRUNCATE)")
return err return err
} }

View file

@ -39,6 +39,12 @@ func NewMigrator(db *Database) (*Migrator, error) {
m.conn.SetConnMaxIdleTime(dbConnTimeout) m.conn.SetConnMaxIdleTime(dbConnTimeout)
m.m, err = m.getMigrate() m.m, err = m.getMigrate()
// if error encountered, close the connection
if err != nil {
m.Close()
}
return m, err return m, err
} }
@ -124,6 +130,27 @@ func (m *Migrator) runCustomMigration(ctx context.Context, fn customMigrationFun
return nil return nil
} }
func (m *Migrator) PostMigrate(ctx context.Context) error {
// optimise the database
var err error
logger.Info("Running database analyze")
// don't use Optimize/vacuum as this adds a significant amount of time
// to the migration
err = analyze(ctx, m.conn)
if err == nil {
logger.Debug("Flushing WAL")
err = flushWAL(ctx, m.conn)
}
if err != nil {
return fmt.Errorf("error optimising database: %s", err)
}
return nil
}
func (db *Database) getDatabaseSchemaVersion() (uint, error) { func (db *Database) getDatabaseSchemaVersion() (uint, error) {
m, err := NewMigrator(db) m, err := NewMigrator(db)
if err != nil { if err != nil {