diff --git a/bin/README.md b/bin/README.md index 2a275fc..e1d0a18 100644 --- a/bin/README.md +++ b/bin/README.md @@ -4,8 +4,8 @@ This directory contains pre-compiled binaries for the DB Backup Tool across mult ## Build Information - **Version**: 3.42.34 -- **Build Time**: 2026-01-15_14:16:33_UTC -- **Git Commit**: eeacbfa +- **Build Time**: 2026-01-15_14:33:12_UTC +- **Git Commit**: 09a9177 ## Recent Updates (v1.1.0) - ✅ Fixed TUI progress display with line-by-line output diff --git a/internal/restore/engine.go b/internal/restore/engine.go index 64ca27a..7fb7111 100755 --- a/internal/restore/engine.go +++ b/internal/restore/engine.go @@ -1937,6 +1937,8 @@ type OriginalSettings struct { } // boostPostgreSQLSettings boosts multiple PostgreSQL settings for large restores +// NOTE: max_locks_per_transaction requires a PostgreSQL RESTART to take effect! +// maintenance_work_mem can be changed with pg_reload_conf(). func (e *Engine) boostPostgreSQLSettings(ctx context.Context, lockBoostValue int) (*OriginalSettings, error) { connStr := e.buildConnString() db, err := sql.Open("pgx", connStr) @@ -1956,30 +1958,113 @@ func (e *Engine) boostPostgreSQLSettings(ctx context.Context, lockBoostValue int // Get current maintenance_work_mem db.QueryRowContext(ctx, "SHOW maintenance_work_mem").Scan(&original.MaintenanceWorkMem) - // Boost max_locks_per_transaction (if not already high enough) + // CRITICAL: max_locks_per_transaction requires a PostgreSQL RESTART! + // pg_reload_conf() is NOT sufficient for this parameter. + needsRestart := false if original.MaxLocks < lockBoostValue { _, err = db.ExecContext(ctx, fmt.Sprintf("ALTER SYSTEM SET max_locks_per_transaction = %d", lockBoostValue)) if err != nil { - e.log.Warn("Could not boost max_locks_per_transaction", "error", err) + e.log.Warn("Could not set max_locks_per_transaction", "error", err) + } else { + needsRestart = true + e.log.Warn("max_locks_per_transaction requires PostgreSQL restart to take effect", + "current", original.MaxLocks, + "target", lockBoostValue) } } // Boost maintenance_work_mem to 2GB for faster index creation + // (this one CAN be applied via pg_reload_conf) _, err = db.ExecContext(ctx, "ALTER SYSTEM SET maintenance_work_mem = '2GB'") if err != nil { e.log.Warn("Could not boost maintenance_work_mem", "error", err) } - // Reload config to apply changes (no restart needed for these settings) + // Reload config to apply maintenance_work_mem _, err = db.ExecContext(ctx, "SELECT pg_reload_conf()") if err != nil { return original, fmt.Errorf("failed to reload config: %w", err) } + // If max_locks_per_transaction needs a restart, try to do it + if needsRestart { + if restarted := e.tryRestartPostgreSQL(ctx); restarted { + e.log.Info("PostgreSQL restarted successfully - max_locks_per_transaction now active") + // Wait for PostgreSQL to be ready + time.Sleep(3 * time.Second) + } else { + // Cannot restart - warn user loudly + e.log.Error("=" + strings.Repeat("=", 70)) + e.log.Error("WARNING: max_locks_per_transaction change requires PostgreSQL restart!") + e.log.Error("Current value: " + strconv.Itoa(original.MaxLocks) + ", needed: " + strconv.Itoa(lockBoostValue)) + e.log.Error("Restore may fail with 'out of shared memory' error on BLOB-heavy databases.") + e.log.Error("") + e.log.Error("To fix manually:") + e.log.Error(" 1. sudo systemctl restart postgresql") + e.log.Error(" 2. Or: sudo -u postgres pg_ctl restart -D $PGDATA") + e.log.Error(" 3. Then re-run the restore") + e.log.Error("=" + strings.Repeat("=", 70)) + // Continue anyway - might work for small restores + } + } + return original, nil } +// tryRestartPostgreSQL attempts to restart PostgreSQL using various methods +// Returns true if restart was successful +func (e *Engine) tryRestartPostgreSQL(ctx context.Context) bool { + e.progress.Update("Attempting PostgreSQL restart for lock settings...") + + // Method 1: systemctl (most common on modern Linux) + cmd := exec.CommandContext(ctx, "sudo", "systemctl", "restart", "postgresql") + if err := cmd.Run(); err == nil { + return true + } + + // Method 2: systemctl with version suffix (e.g., postgresql-15) + for _, ver := range []string{"17", "16", "15", "14", "13", "12"} { + cmd = exec.CommandContext(ctx, "sudo", "systemctl", "restart", "postgresql-"+ver) + if err := cmd.Run(); err == nil { + return true + } + } + + // Method 3: service command (older systems) + cmd = exec.CommandContext(ctx, "sudo", "service", "postgresql", "restart") + if err := cmd.Run(); err == nil { + return true + } + + // Method 4: pg_ctl as postgres user + cmd = exec.CommandContext(ctx, "sudo", "-u", "postgres", "pg_ctl", "restart", "-D", "/var/lib/postgresql/data", "-m", "fast") + if err := cmd.Run(); err == nil { + return true + } + + // Method 5: Try common PGDATA paths + pgdataPaths := []string{ + "/var/lib/pgsql/data", + "/var/lib/pgsql/17/data", + "/var/lib/pgsql/16/data", + "/var/lib/pgsql/15/data", + "/var/lib/postgresql/17/main", + "/var/lib/postgresql/16/main", + "/var/lib/postgresql/15/main", + } + for _, pgdata := range pgdataPaths { + cmd = exec.CommandContext(ctx, "sudo", "-u", "postgres", "pg_ctl", "restart", "-D", pgdata, "-m", "fast") + if err := cmd.Run(); err == nil { + return true + } + } + + return false +} + // resetPostgreSQLSettings restores original PostgreSQL settings +// NOTE: max_locks_per_transaction changes are written but require restart to take effect. +// We don't restart here since we're done with the restore. func (e *Engine) resetPostgreSQLSettings(ctx context.Context, original *OriginalSettings) error { connStr := e.buildConnString() db, err := sql.Open("pgx", connStr) @@ -1988,25 +2073,28 @@ func (e *Engine) resetPostgreSQLSettings(ctx context.Context, original *Original } defer db.Close() - // Reset max_locks_per_transaction + // Reset max_locks_per_transaction (will take effect on next restart) if original.MaxLocks == 64 { // Default db.ExecContext(ctx, "ALTER SYSTEM RESET max_locks_per_transaction") } else if original.MaxLocks > 0 { db.ExecContext(ctx, fmt.Sprintf("ALTER SYSTEM SET max_locks_per_transaction = %d", original.MaxLocks)) } - // Reset maintenance_work_mem + // Reset maintenance_work_mem (takes effect immediately with reload) if original.MaintenanceWorkMem == "64MB" { // Default db.ExecContext(ctx, "ALTER SYSTEM RESET maintenance_work_mem") } else if original.MaintenanceWorkMem != "" { db.ExecContext(ctx, fmt.Sprintf("ALTER SYSTEM SET maintenance_work_mem = '%s'", original.MaintenanceWorkMem)) } - // Reload config + // Reload config (only maintenance_work_mem will take effect immediately) _, err = db.ExecContext(ctx, "SELECT pg_reload_conf()") if err != nil { return fmt.Errorf("failed to reload config: %w", err) } + e.log.Info("PostgreSQL settings reset queued", + "note", "max_locks_per_transaction will revert on next PostgreSQL restart") + return nil } diff --git a/internal/restore/preflight.go b/internal/restore/preflight.go index 4bea943..f9a007f 100644 --- a/internal/restore/preflight.go +++ b/internal/restore/preflight.go @@ -201,10 +201,19 @@ func (e *Engine) checkPostgreSQL(ctx context.Context, result *PreflightResult) { result.PostgreSQL.IsSuperuser = isSuperuser } - // Add info/warnings + // CRITICAL: max_locks_per_transaction requires PostgreSQL RESTART to change! + // Warn users loudly about this - it's the #1 cause of "out of shared memory" errors if result.PostgreSQL.MaxLocksPerTransaction < 256 { - e.log.Info("PostgreSQL max_locks_per_transaction is low - will auto-boost", - "current", result.PostgreSQL.MaxLocksPerTransaction) + e.log.Warn("PostgreSQL max_locks_per_transaction is LOW", + "current", result.PostgreSQL.MaxLocksPerTransaction, + "recommended", "256+", + "note", "REQUIRES PostgreSQL restart to change!") + + result.Warnings = append(result.Warnings, + fmt.Sprintf("max_locks_per_transaction=%d is low (recommend 256+). "+ + "This setting requires PostgreSQL RESTART to change. "+ + "BLOB-heavy databases may fail with 'out of shared memory' error.", + result.PostgreSQL.MaxLocksPerTransaction)) } // Parse shared_buffers and warn if very low