Some checks failed
CI/CD / Integration Tests (push) Has been cancelled
CI/CD / Native Engine Tests (push) Has been cancelled
CI/CD / Lint (push) Has been cancelled
CI/CD / Build Binary (push) Has been cancelled
CI/CD / Test Release Build (push) Has been cancelled
CI/CD / Release Binaries (push) Has been cancelled
CI/CD / Test (push) Has been cancelled
- Update version string to 5.1.14 - Update CHANGELOG with v5.1.10-v5.1.14 features - Update README with new enterprise features - Remove development files from repository - Add sensitive files to .gitignore
429 lines
14 KiB
Go
429 lines
14 KiB
Go
package cmd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var parallelRestoreCmd = &cobra.Command{
|
|
Use: "parallel-restore",
|
|
Short: "Configure and test parallel restore settings",
|
|
Long: `Configure parallel restore settings for faster database restoration.
|
|
|
|
Parallel restore uses multiple threads to restore databases concurrently:
|
|
- Parallel jobs within single database (--jobs flag)
|
|
- Parallel database restoration for cluster backups
|
|
- CPU-aware thread allocation
|
|
- Memory-aware resource limits
|
|
|
|
This significantly reduces restoration time for:
|
|
- Large databases with many tables
|
|
- Cluster backups with multiple databases
|
|
- Systems with multiple CPU cores
|
|
|
|
Configuration:
|
|
- Set parallel jobs count (default: auto-detect CPU cores)
|
|
- Configure memory limits for large restores
|
|
- Tune for specific hardware profiles
|
|
|
|
Examples:
|
|
# Show current parallel restore configuration
|
|
dbbackup parallel-restore status
|
|
|
|
# Test parallel restore performance
|
|
dbbackup parallel-restore benchmark --file backup.dump
|
|
|
|
# Show recommended settings for current system
|
|
dbbackup parallel-restore recommend
|
|
|
|
# Simulate parallel restore (dry-run)
|
|
dbbackup parallel-restore simulate --file backup.dump --jobs 8`,
|
|
}
|
|
|
|
var parallelRestoreStatusCmd = &cobra.Command{
|
|
Use: "status",
|
|
Short: "Show parallel restore configuration",
|
|
Long: `Display current parallel restore configuration and system capabilities.`,
|
|
RunE: runParallelRestoreStatus,
|
|
}
|
|
|
|
var parallelRestoreBenchmarkCmd = &cobra.Command{
|
|
Use: "benchmark",
|
|
Short: "Benchmark parallel restore performance",
|
|
Long: `Benchmark parallel restore with different thread counts to find optimal settings.`,
|
|
RunE: runParallelRestoreBenchmark,
|
|
}
|
|
|
|
var parallelRestoreRecommendCmd = &cobra.Command{
|
|
Use: "recommend",
|
|
Short: "Get recommended parallel restore settings",
|
|
Long: `Analyze system resources and recommend optimal parallel restore settings.`,
|
|
RunE: runParallelRestoreRecommend,
|
|
}
|
|
|
|
var parallelRestoreSimulateCmd = &cobra.Command{
|
|
Use: "simulate",
|
|
Short: "Simulate parallel restore execution plan",
|
|
Long: `Simulate parallel restore without actually restoring data to show execution plan.`,
|
|
RunE: runParallelRestoreSimulate,
|
|
}
|
|
|
|
var (
|
|
parallelRestoreFile string
|
|
parallelRestoreJobs int
|
|
parallelRestoreFormat string
|
|
)
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(parallelRestoreCmd)
|
|
|
|
parallelRestoreCmd.AddCommand(parallelRestoreStatusCmd)
|
|
parallelRestoreCmd.AddCommand(parallelRestoreBenchmarkCmd)
|
|
parallelRestoreCmd.AddCommand(parallelRestoreRecommendCmd)
|
|
parallelRestoreCmd.AddCommand(parallelRestoreSimulateCmd)
|
|
|
|
parallelRestoreStatusCmd.Flags().StringVar(¶llelRestoreFormat, "format", "text", "Output format (text, json)")
|
|
parallelRestoreBenchmarkCmd.Flags().StringVar(¶llelRestoreFile, "file", "", "Backup file to benchmark (required)")
|
|
parallelRestoreBenchmarkCmd.MarkFlagRequired("file")
|
|
parallelRestoreSimulateCmd.Flags().StringVar(¶llelRestoreFile, "file", "", "Backup file to simulate (required)")
|
|
parallelRestoreSimulateCmd.Flags().IntVar(¶llelRestoreJobs, "jobs", 0, "Number of parallel jobs (0=auto)")
|
|
parallelRestoreSimulateCmd.MarkFlagRequired("file")
|
|
}
|
|
|
|
func runParallelRestoreStatus(cmd *cobra.Command, args []string) error {
|
|
numCPU := runtime.NumCPU()
|
|
recommendedJobs := numCPU
|
|
if numCPU > 8 {
|
|
recommendedJobs = numCPU - 2 // Leave headroom
|
|
}
|
|
|
|
status := ParallelRestoreStatus{
|
|
SystemCPUs: numCPU,
|
|
RecommendedJobs: recommendedJobs,
|
|
MaxJobs: numCPU * 2,
|
|
CurrentJobs: cfg.Jobs,
|
|
MemoryGB: getAvailableMemoryGB(),
|
|
ParallelSupported: true,
|
|
}
|
|
|
|
if parallelRestoreFormat == "json" {
|
|
data, _ := json.MarshalIndent(status, "", " ")
|
|
fmt.Println(string(data))
|
|
return nil
|
|
}
|
|
|
|
fmt.Println("[PARALLEL RESTORE] System Capabilities")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Printf("CPU Cores: %d\n", status.SystemCPUs)
|
|
fmt.Printf("Available Memory: %.1f GB\n", status.MemoryGB)
|
|
fmt.Println()
|
|
|
|
fmt.Println("[CONFIGURATION]")
|
|
fmt.Println("==========================================")
|
|
fmt.Printf("Current Jobs: %d\n", status.CurrentJobs)
|
|
fmt.Printf("Recommended Jobs: %d\n", status.RecommendedJobs)
|
|
fmt.Printf("Maximum Jobs: %d\n", status.MaxJobs)
|
|
fmt.Println()
|
|
|
|
fmt.Println("[PARALLEL RESTORE MODES]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Println("1. Single Database Parallel Restore:")
|
|
fmt.Println(" Uses pg_restore -j flag or parallel mysql restore")
|
|
fmt.Println(" Restores tables concurrently within one database")
|
|
fmt.Println(" Example: dbbackup restore single db.dump --jobs 8 --confirm")
|
|
fmt.Println()
|
|
fmt.Println("2. Cluster Parallel Restore:")
|
|
fmt.Println(" Restores multiple databases concurrently")
|
|
fmt.Println(" Each database can use parallel jobs")
|
|
fmt.Println(" Example: dbbackup restore cluster backup.tar --jobs 4 --confirm")
|
|
fmt.Println()
|
|
|
|
fmt.Println("[PERFORMANCE TIPS]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Println("• Start with recommended jobs count")
|
|
fmt.Println("• More jobs ≠ always faster (context switching overhead)")
|
|
fmt.Printf("• For this system: --jobs %d is optimal\n", status.RecommendedJobs)
|
|
fmt.Println("• Monitor system load during restore")
|
|
fmt.Println("• Use --profile aggressive for maximum speed")
|
|
fmt.Println("• SSD storage benefits more from parallelization")
|
|
fmt.Println()
|
|
|
|
return nil
|
|
}
|
|
|
|
func runParallelRestoreBenchmark(cmd *cobra.Command, args []string) error {
|
|
if _, err := os.Stat(parallelRestoreFile); err != nil {
|
|
return fmt.Errorf("backup file not found: %s", parallelRestoreFile)
|
|
}
|
|
|
|
fmt.Println("[PARALLEL RESTORE] Benchmark Mode")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Printf("Backup File: %s\n", parallelRestoreFile)
|
|
fmt.Println()
|
|
|
|
// Detect backup format
|
|
ext := filepath.Ext(parallelRestoreFile)
|
|
format := "unknown"
|
|
if ext == ".dump" || ext == ".pgdump" {
|
|
format = "PostgreSQL custom format"
|
|
} else if ext == ".sql" || ext == ".gz" && filepath.Ext(parallelRestoreFile[:len(parallelRestoreFile)-3]) == ".sql" {
|
|
format = "SQL format"
|
|
} else if ext == ".tar" || ext == ".tgz" {
|
|
format = "Cluster backup"
|
|
}
|
|
|
|
fmt.Printf("Detected Format: %s\n", format)
|
|
fmt.Println()
|
|
|
|
fmt.Println("[BENCHMARK STRATEGY]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Println("Benchmarking would test restore with different job counts:")
|
|
fmt.Println()
|
|
|
|
numCPU := runtime.NumCPU()
|
|
testConfigs := []int{1, 2, 4}
|
|
if numCPU >= 8 {
|
|
testConfigs = append(testConfigs, 8)
|
|
}
|
|
if numCPU >= 16 {
|
|
testConfigs = append(testConfigs, 16)
|
|
}
|
|
|
|
for i, jobs := range testConfigs {
|
|
estimatedTime := estimateRestoreTime(parallelRestoreFile, jobs)
|
|
fmt.Printf("%d. Jobs=%d → Estimated: %s\n", i+1, jobs, estimatedTime)
|
|
}
|
|
|
|
fmt.Println()
|
|
fmt.Println("[NOTE]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println("Actual benchmarking requires:")
|
|
fmt.Println(" - Test database or dry-run mode")
|
|
fmt.Println(" - Multiple restore attempts with different job counts")
|
|
fmt.Println(" - Measurement of wall clock time")
|
|
fmt.Println()
|
|
fmt.Println("For now, use 'dbbackup restore single --dry-run' to test without")
|
|
fmt.Println("actually restoring data.")
|
|
fmt.Println()
|
|
|
|
return nil
|
|
}
|
|
|
|
func runParallelRestoreRecommend(cmd *cobra.Command, args []string) error {
|
|
numCPU := runtime.NumCPU()
|
|
memoryGB := getAvailableMemoryGB()
|
|
|
|
fmt.Println("[PARALLEL RESTORE] Recommendations")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
|
|
fmt.Println("[SYSTEM ANALYSIS]")
|
|
fmt.Println("==========================================")
|
|
fmt.Printf("CPU Cores: %d\n", numCPU)
|
|
fmt.Printf("Available Memory: %.1f GB\n", memoryGB)
|
|
fmt.Println()
|
|
|
|
// Calculate recommendations
|
|
var recommendedJobs int
|
|
var profile string
|
|
|
|
if memoryGB < 2 {
|
|
recommendedJobs = 1
|
|
profile = "conservative"
|
|
} else if memoryGB < 8 {
|
|
recommendedJobs = min(numCPU/2, 4)
|
|
profile = "conservative"
|
|
} else if memoryGB < 16 {
|
|
recommendedJobs = min(numCPU-1, 8)
|
|
profile = "balanced"
|
|
} else {
|
|
recommendedJobs = numCPU
|
|
if numCPU > 8 {
|
|
recommendedJobs = numCPU - 2
|
|
}
|
|
profile = "aggressive"
|
|
}
|
|
|
|
fmt.Println("[RECOMMENDATIONS]")
|
|
fmt.Println("==========================================")
|
|
fmt.Printf("Recommended Profile: %s\n", profile)
|
|
fmt.Printf("Recommended Jobs: %d\n", recommendedJobs)
|
|
fmt.Println()
|
|
|
|
fmt.Println("[COMMAND EXAMPLES]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Println("Single database restore (recommended):")
|
|
fmt.Printf(" dbbackup restore single db.dump --jobs %d --profile %s --confirm\n", recommendedJobs, profile)
|
|
fmt.Println()
|
|
fmt.Println("Cluster restore (recommended):")
|
|
fmt.Printf(" dbbackup restore cluster backup.tar --jobs %d --profile %s --confirm\n", recommendedJobs, profile)
|
|
fmt.Println()
|
|
|
|
if memoryGB < 4 {
|
|
fmt.Println("[⚠ LOW MEMORY WARNING]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println("Your system has limited memory. Consider:")
|
|
fmt.Println(" - Using --low-memory flag")
|
|
fmt.Println(" - Restoring databases one at a time")
|
|
fmt.Println(" - Reducing --jobs count")
|
|
fmt.Println(" - Closing other applications")
|
|
fmt.Println()
|
|
}
|
|
|
|
if numCPU >= 16 {
|
|
fmt.Println("[💡 HIGH-PERFORMANCE TIPS]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println("Your system has many cores. Optimize with:")
|
|
fmt.Println(" - Use --profile aggressive")
|
|
fmt.Printf(" - Try up to --jobs %d\n", numCPU)
|
|
fmt.Println(" - Monitor with 'dbbackup restore ... --verbose'")
|
|
fmt.Println(" - Use SSD storage for temp files")
|
|
fmt.Println()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func runParallelRestoreSimulate(cmd *cobra.Command, args []string) error {
|
|
if _, err := os.Stat(parallelRestoreFile); err != nil {
|
|
return fmt.Errorf("backup file not found: %s", parallelRestoreFile)
|
|
}
|
|
|
|
jobs := parallelRestoreJobs
|
|
if jobs == 0 {
|
|
jobs = runtime.NumCPU()
|
|
if jobs > 8 {
|
|
jobs = jobs - 2
|
|
}
|
|
}
|
|
|
|
fmt.Println("[PARALLEL RESTORE] Simulation")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Printf("Backup File: %s\n", parallelRestoreFile)
|
|
fmt.Printf("Parallel Jobs: %d\n", jobs)
|
|
fmt.Println()
|
|
|
|
// Detect backup type
|
|
ext := filepath.Ext(parallelRestoreFile)
|
|
isCluster := ext == ".tar" || ext == ".tgz"
|
|
|
|
if isCluster {
|
|
fmt.Println("[CLUSTER RESTORE PLAN]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Println("Phase 1: Extract archive")
|
|
fmt.Println(" • Decompress backup archive")
|
|
fmt.Println(" • Extract globals.sql, schemas, and database dumps")
|
|
fmt.Println()
|
|
fmt.Println("Phase 2: Restore globals (sequential)")
|
|
fmt.Println(" • Restore roles and permissions")
|
|
fmt.Println(" • Restore tablespaces")
|
|
fmt.Println()
|
|
fmt.Println("Phase 3: Parallel database restore")
|
|
fmt.Printf(" • Restore databases with %d parallel jobs\n", jobs)
|
|
fmt.Println(" • Each database can use internal parallelization")
|
|
fmt.Println()
|
|
fmt.Println("Estimated databases: 3-10 (actual count varies)")
|
|
fmt.Println("Estimated speedup: 3-5x vs sequential")
|
|
} else {
|
|
fmt.Println("[SINGLE DATABASE RESTORE PLAN]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Println("Phase 1: Pre-restore checks")
|
|
fmt.Println(" • Verify backup file integrity")
|
|
fmt.Println(" • Check target database connection")
|
|
fmt.Println(" • Validate sufficient disk space")
|
|
fmt.Println()
|
|
fmt.Println("Phase 2: Schema preparation")
|
|
fmt.Println(" • Create database (if needed)")
|
|
fmt.Println(" • Drop existing objects (if --clean)")
|
|
fmt.Println()
|
|
fmt.Println("Phase 3: Parallel data restore")
|
|
fmt.Printf(" • Restore tables with %d parallel jobs\n", jobs)
|
|
fmt.Println(" • Each job processes different tables")
|
|
fmt.Println(" • Automatic load balancing")
|
|
fmt.Println()
|
|
fmt.Println("Phase 4: Post-restore")
|
|
fmt.Println(" • Rebuild indexes")
|
|
fmt.Println(" • Restore constraints")
|
|
fmt.Println(" • Update statistics")
|
|
fmt.Println()
|
|
fmt.Printf("Estimated speedup: %dx vs sequential restore\n", estimateSpeedup(jobs))
|
|
}
|
|
|
|
fmt.Println()
|
|
fmt.Println("[EXECUTION COMMAND]")
|
|
fmt.Println("==========================================")
|
|
fmt.Println()
|
|
fmt.Println("To perform this restore:")
|
|
if isCluster {
|
|
fmt.Printf(" dbbackup restore cluster %s --jobs %d --confirm\n", parallelRestoreFile, jobs)
|
|
} else {
|
|
fmt.Printf(" dbbackup restore single %s --jobs %d --confirm\n", parallelRestoreFile, jobs)
|
|
}
|
|
fmt.Println()
|
|
|
|
return nil
|
|
}
|
|
|
|
type ParallelRestoreStatus struct {
|
|
SystemCPUs int `json:"system_cpus"`
|
|
RecommendedJobs int `json:"recommended_jobs"`
|
|
MaxJobs int `json:"max_jobs"`
|
|
CurrentJobs int `json:"current_jobs"`
|
|
MemoryGB float64 `json:"memory_gb"`
|
|
ParallelSupported bool `json:"parallel_supported"`
|
|
}
|
|
|
|
func getAvailableMemoryGB() float64 {
|
|
// Simple estimation - in production would query actual system memory
|
|
// For now, return a reasonable default
|
|
return 8.0
|
|
}
|
|
|
|
func estimateRestoreTime(file string, jobs int) string {
|
|
// Simplified estimation based on file size and jobs
|
|
info, err := os.Stat(file)
|
|
if err != nil {
|
|
return "unknown"
|
|
}
|
|
|
|
sizeGB := float64(info.Size()) / (1024 * 1024 * 1024)
|
|
baseTime := sizeGB * 120 // ~2 minutes per GB baseline
|
|
parallelTime := baseTime / float64(jobs) * 0.7 // 70% efficiency
|
|
|
|
if parallelTime < 60 {
|
|
return fmt.Sprintf("%.0fs", parallelTime)
|
|
}
|
|
return fmt.Sprintf("%.1fm", parallelTime/60)
|
|
}
|
|
|
|
func estimateSpeedup(jobs int) int {
|
|
// Amdahl's law: assume 80% parallelizable
|
|
if jobs <= 1 {
|
|
return 1
|
|
}
|
|
// Simple linear speedup with diminishing returns
|
|
speedup := 1.0 + float64(jobs-1)*0.7
|
|
return int(speedup)
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|