feat(engine): physical backup revolution - XtraBackup capabilities in pure Go

Why wrap external tools when you can BE the tool?

New physical backup engines:
• MySQL Clone Plugin - native 8.0.17+ physical backup
• Filesystem Snapshots - LVM/ZFS/Btrfs orchestration
• Binlog Streaming - continuous backup with seconds RPO
• Parallel Cloud Upload - stream directly to S3, skip local disk

Smart engine selection automatically picks the optimal strategy based on:
- MySQL version and edition
- Available filesystem features
- Database size
- Cloud connectivity

Zero external dependencies. Single binary. Enterprise capabilities.

Commercial backup vendors: we need to talk.
This commit is contained in:
2025-12-13 21:21:17 +01:00
parent f69bfe7071
commit dbb0f6f942
27 changed files with 7559 additions and 268 deletions

110
cmd/engine.go Normal file
View File

@@ -0,0 +1,110 @@
package cmd
import (
"context"
"fmt"
"strings"
"dbbackup/internal/engine"
"github.com/spf13/cobra"
)
var engineCmd = &cobra.Command{
Use: "engine",
Short: "Backup engine management commands",
Long: `Commands for managing and selecting backup engines.
Available engines:
- mysqldump: Traditional mysqldump backup (all MySQL versions)
- clone: MySQL Clone Plugin (MySQL 8.0.17+)
- snapshot: Filesystem snapshot (LVM/ZFS/Btrfs)
- streaming: Direct cloud streaming backup`,
}
var engineListCmd = &cobra.Command{
Use: "list",
Short: "List available backup engines",
Long: "List all registered backup engines and their availability status",
RunE: runEngineList,
}
var engineInfoCmd = &cobra.Command{
Use: "info [engine-name]",
Short: "Show detailed information about an engine",
Long: "Display detailed information about a specific backup engine",
Args: cobra.ExactArgs(1),
RunE: runEngineInfo,
}
func init() {
rootCmd.AddCommand(engineCmd)
engineCmd.AddCommand(engineListCmd)
engineCmd.AddCommand(engineInfoCmd)
}
func runEngineList(cmd *cobra.Command, args []string) error {
ctx := context.Background()
registry := engine.DefaultRegistry
fmt.Println("Available Backup Engines:")
fmt.Println(strings.Repeat("-", 70))
for _, info := range registry.List() {
eng, err := registry.Get(info.Name)
if err != nil {
continue
}
avail, err := eng.CheckAvailability(ctx)
if err != nil {
fmt.Printf("\n%s (%s)\n", info.Name, info.Description)
fmt.Printf(" Status: Error checking availability\n")
continue
}
status := "✓ Available"
if !avail.Available {
status = "✗ Not available"
}
fmt.Printf("\n%s (%s)\n", info.Name, info.Description)
fmt.Printf(" Status: %s\n", status)
if !avail.Available && avail.Reason != "" {
fmt.Printf(" Reason: %s\n", avail.Reason)
}
fmt.Printf(" Restore: %v\n", eng.SupportsRestore())
fmt.Printf(" Incremental: %v\n", eng.SupportsIncremental())
fmt.Printf(" Streaming: %v\n", eng.SupportsStreaming())
}
return nil
}
func runEngineInfo(cmd *cobra.Command, args []string) error {
ctx := context.Background()
registry := engine.DefaultRegistry
eng, err := registry.Get(args[0])
if err != nil {
return fmt.Errorf("engine not found: %s", args[0])
}
avail, err := eng.CheckAvailability(ctx)
if err != nil {
return fmt.Errorf("failed to check availability: %w", err)
}
fmt.Printf("Engine: %s\n", eng.Name())
fmt.Printf("Description: %s\n", eng.Description())
fmt.Println(strings.Repeat("-", 50))
fmt.Printf("Available: %v\n", avail.Available)
if avail.Reason != "" {
fmt.Printf("Reason: %s\n", avail.Reason)
}
fmt.Printf("Restore: %v\n", eng.SupportsRestore())
fmt.Printf("Incremental: %v\n", eng.SupportsIncremental())
fmt.Printf("Streaming: %v\n", eng.SupportsStreaming())
return nil
}

View File

@@ -66,15 +66,15 @@ var reportControlsCmd = &cobra.Command{
}
var (
reportType string
reportDays int
reportStartDate string
reportEndDate string
reportFormat string
reportOutput string
reportCatalog string
reportTitle string
includeEvidence bool
reportType string
reportDays int
reportStartDate string
reportEndDate string
reportFormat string
reportOutput string
reportCatalog string
reportTitle string
includeEvidence bool
)
func init() {

View File

@@ -60,12 +60,12 @@ var rtoCheckCmd = &cobra.Command{
}
var (
rtoDatabase string
rtoTargetRTO string
rtoTargetRPO string
rtoCatalog string
rtoFormat string
rtoOutput string
rtoDatabase string
rtoTargetRTO string
rtoTargetRPO string
rtoCatalog string
rtoFormat string
rtoOutput string
)
func init() {
@@ -188,7 +188,7 @@ func runRTOStatus(cmd *cobra.Command, args []string) error {
formatDuration(config.TargetRTO),
formatDuration(config.TargetRPO))
fmt.Println("╠═══════════════════════════════════════════════════════════╣")
// Compliance status
rpoRate := 0.0
rtoRate := 0.0
@@ -203,11 +203,11 @@ func runRTOStatus(cmd *cobra.Command, args []string) error {
fmt.Printf("║ RPO Compliant: %-5d (%.0f%%) ║\n", summary.RPOCompliant, rpoRate)
fmt.Printf("║ RTO Compliant: %-5d (%.0f%%) ║\n", summary.RTOCompliant, rtoRate)
fmt.Printf("║ Fully Compliant: %-3d (%.0f%%) ║\n", summary.FullyCompliant, fullRate)
if summary.CriticalIssues > 0 {
fmt.Printf("║ ⚠️ Critical Issues: %-3d ║\n", summary.CriticalIssues)
}
fmt.Println("╠═══════════════════════════════════════════════════════════╣")
fmt.Printf("║ Average RPO: %-15s Worst: %-15s ║\n",
formatDuration(summary.AverageRPO),
@@ -215,14 +215,14 @@ func runRTOStatus(cmd *cobra.Command, args []string) error {
fmt.Printf("║ Average RTO: %-15s Worst: %-15s ║\n",
formatDuration(summary.AverageRTO),
formatDuration(summary.WorstRTO))
if summary.WorstRPODatabase != "" {
fmt.Printf("║ Worst RPO Database: %-38s║\n", summary.WorstRPODatabase)
}
if summary.WorstRTODatabase != "" {
fmt.Printf("║ Worst RTO Database: %-38s║\n", summary.WorstRTODatabase)
}
fmt.Println("╚═══════════════════════════════════════════════════════════╝")
fmt.Println()
@@ -238,10 +238,10 @@ func runRTOStatus(cmd *cobra.Command, args []string) error {
if !a.RPOCompliant || !a.RTOCompliant {
status = "❌"
}
rpoStr := formatDuration(a.CurrentRPO)
rtoStr := formatDuration(a.CurrentRTO)
if !a.RPOCompliant {
rpoStr = "⚠️ " + rpoStr
}
@@ -249,7 +249,7 @@ func runRTOStatus(cmd *cobra.Command, args []string) error {
rtoStr = "⚠️ " + rtoStr
}
fmt.Printf("%-25s %-12s %-12s %s\n",
fmt.Printf("%-25s %-12s %-12s %s\n",
truncateRTO(a.Database, 24),
rpoStr,
rtoStr,
@@ -383,7 +383,7 @@ func outputAnalysisText(analyses []*rto.Analysis) error {
fmt.Println()
fmt.Println(" Recovery Objectives:")
fmt.Println(strings.Repeat("-", 50))
fmt.Printf(" RPO (Current): %-15s Target: %s\n",
fmt.Printf(" RPO (Current): %-15s Target: %s\n",
formatDuration(a.CurrentRPO), formatDuration(a.TargetRPO))
fmt.Printf(" RPO Status: %s\n", rpoStatus)
fmt.Printf(" RTO (Estimated): %-14s Target: %s\n",