fix(preflight): improve BLOB count detection and block restore when max_locks_per_transaction is critically low
- Add higher lock boost tiers for extreme BLOB counts (100K+, 200K+) - Calculate actual lock requirement: (totalBLOBs / max_connections) * 1.5 - Block restore with CRITICAL error when BLOB count exceeds 2x safe limit - Improve preflight summary with PASSED/FAILED status display - Add clearer fix instructions for max_locks_per_transaction
This commit is contained in:
@@ -4,8 +4,8 @@ This directory contains pre-compiled binaries for the DB Backup Tool across mult
|
|||||||
|
|
||||||
## Build Information
|
## Build Information
|
||||||
- **Version**: 3.42.50
|
- **Version**: 3.42.50
|
||||||
- **Build Time**: 2026-01-16_17:31:38_UTC
|
- **Build Time**: 2026-01-16_18:37:32_UTC
|
||||||
- **Git Commit**: 698b8a7
|
- **Git Commit**: 9200024
|
||||||
|
|
||||||
## Recent Updates (v1.1.0)
|
## Recent Updates (v1.1.0)
|
||||||
- ✅ Fixed TUI progress display with line-by-line output
|
- ✅ Fixed TUI progress display with line-by-line output
|
||||||
|
|||||||
@@ -212,7 +212,8 @@ func (e *Engine) checkPostgreSQL(ctx context.Context, result *PreflightResult) {
|
|||||||
result.Warnings = append(result.Warnings,
|
result.Warnings = append(result.Warnings,
|
||||||
fmt.Sprintf("max_locks_per_transaction=%d is low (recommend 256+). "+
|
fmt.Sprintf("max_locks_per_transaction=%d is low (recommend 256+). "+
|
||||||
"This setting requires PostgreSQL RESTART to change. "+
|
"This setting requires PostgreSQL RESTART to change. "+
|
||||||
"BLOB-heavy databases may fail with 'out of shared memory' error.",
|
"BLOB-heavy databases may fail with 'out of shared memory' error. "+
|
||||||
|
"Fix: Edit postgresql.conf, set max_locks_per_transaction=2048, then restart PostgreSQL.",
|
||||||
result.PostgreSQL.MaxLocksPerTransaction))
|
result.PostgreSQL.MaxLocksPerTransaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,14 +325,57 @@ func (e *Engine) calculateRecommendations(result *PreflightResult) {
|
|||||||
if result.Archive.TotalBlobCount > 50000 {
|
if result.Archive.TotalBlobCount > 50000 {
|
||||||
lockBoost = 16384
|
lockBoost = 16384
|
||||||
}
|
}
|
||||||
|
if result.Archive.TotalBlobCount > 100000 {
|
||||||
|
lockBoost = 32768
|
||||||
|
}
|
||||||
|
if result.Archive.TotalBlobCount > 200000 {
|
||||||
|
lockBoost = 65536
|
||||||
|
}
|
||||||
|
|
||||||
// Cap at reasonable maximum
|
// For extreme cases, calculate actual requirement
|
||||||
if lockBoost > 16384 {
|
// Rule of thumb: ~1 lock per BLOB, divided by max_connections (default 100)
|
||||||
lockBoost = 16384
|
// Add 50% safety margin
|
||||||
|
maxConns := result.PostgreSQL.MaxConnections
|
||||||
|
if maxConns == 0 {
|
||||||
|
maxConns = 100 // default
|
||||||
|
}
|
||||||
|
calculatedLocks := (result.Archive.TotalBlobCount / maxConns) * 3 / 2 // 1.5x safety margin
|
||||||
|
if calculatedLocks > lockBoost {
|
||||||
|
lockBoost = calculatedLocks
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Archive.RecommendedLockBoost = lockBoost
|
result.Archive.RecommendedLockBoost = lockBoost
|
||||||
|
|
||||||
|
// CRITICAL: Check if current max_locks_per_transaction is dangerously low for this BLOB count
|
||||||
|
currentLocks := result.PostgreSQL.MaxLocksPerTransaction
|
||||||
|
if currentLocks > 0 && result.Archive.TotalBlobCount > 0 {
|
||||||
|
// Estimate max BLOBs we can handle: locks * max_connections
|
||||||
|
maxSafeBLOBs := currentLocks * maxConns
|
||||||
|
|
||||||
|
if result.Archive.TotalBlobCount > maxSafeBLOBs {
|
||||||
|
severity := "WARNING"
|
||||||
|
if result.Archive.TotalBlobCount > maxSafeBLOBs*2 {
|
||||||
|
severity = "CRITICAL"
|
||||||
|
result.CanProceed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
e.log.Error(fmt.Sprintf("%s: max_locks_per_transaction too low for BLOB count", severity),
|
||||||
|
"current_max_locks", currentLocks,
|
||||||
|
"total_blobs", result.Archive.TotalBlobCount,
|
||||||
|
"max_safe_blobs", maxSafeBLOBs,
|
||||||
|
"recommended_max_locks", lockBoost)
|
||||||
|
|
||||||
|
result.Errors = append(result.Errors,
|
||||||
|
fmt.Sprintf("%s: Archive contains %s BLOBs but max_locks_per_transaction=%d can only safely handle ~%s. "+
|
||||||
|
"Increase max_locks_per_transaction to %d in postgresql.conf and RESTART PostgreSQL.",
|
||||||
|
severity,
|
||||||
|
humanize.Comma(int64(result.Archive.TotalBlobCount)),
|
||||||
|
currentLocks,
|
||||||
|
humanize.Comma(int64(maxSafeBLOBs)),
|
||||||
|
lockBoost))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Log recommendation
|
// Log recommendation
|
||||||
e.log.Info("Calculated recommended lock boost",
|
e.log.Info("Calculated recommended lock boost",
|
||||||
"total_blobs", result.Archive.TotalBlobCount,
|
"total_blobs", result.Archive.TotalBlobCount,
|
||||||
@@ -386,6 +430,14 @@ func (e *Engine) printPreflightSummary(result *PreflightResult) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Errors (blocking issues)
|
||||||
|
if len(result.Errors) > 0 {
|
||||||
|
fmt.Println("\n ✗ ERRORS (must fix before proceeding):")
|
||||||
|
for _, e := range result.Errors {
|
||||||
|
fmt.Printf(" • %s\n", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Warnings
|
// Warnings
|
||||||
if len(result.Warnings) > 0 {
|
if len(result.Warnings) > 0 {
|
||||||
fmt.Println("\n ⚠ Warnings:")
|
fmt.Println("\n ⚠ Warnings:")
|
||||||
@@ -394,6 +446,23 @@ func (e *Engine) printPreflightSummary(result *PreflightResult) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final status
|
||||||
|
fmt.Println()
|
||||||
|
if !result.CanProceed {
|
||||||
|
fmt.Println(" ┌─────────────────────────────────────────────────────────┐")
|
||||||
|
fmt.Println(" │ ✗ PREFLIGHT FAILED - Cannot proceed with restore │")
|
||||||
|
fmt.Println(" │ Fix the errors above and try again. │")
|
||||||
|
fmt.Println(" └─────────────────────────────────────────────────────────┘")
|
||||||
|
} else if len(result.Warnings) > 0 {
|
||||||
|
fmt.Println(" ┌─────────────────────────────────────────────────────────┐")
|
||||||
|
fmt.Println(" │ ⚠ PREFLIGHT PASSED WITH WARNINGS - Proceed with care │")
|
||||||
|
fmt.Println(" └─────────────────────────────────────────────────────────┘")
|
||||||
|
} else {
|
||||||
|
fmt.Println(" ┌─────────────────────────────────────────────────────────┐")
|
||||||
|
fmt.Println(" │ ✓ PREFLIGHT PASSED - Ready to restore │")
|
||||||
|
fmt.Println(" └─────────────────────────────────────────────────────────┘")
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println(strings.Repeat("─", 60))
|
fmt.Println(strings.Repeat("─", 60))
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user