Passed
Pull Request — master (#2447)
by Tolga
05:40 queued 02:21
created

pkg/cmd/repair.go   A

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 88
dl 0
loc 151
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
C cmd.repairDatastore 0 73 8
A cmd.NewRepairCommand 0 11 1
A cmd.maskURL 0 5 2
A cmd.NewRepairDatastoreCommand 0 29 1
1
package cmd
2
3
import (
4
	"context"
5
	"fmt"
6
	"log/slog"
7
	"time"
8
9
	"github.com/spf13/cobra"
10
11
	"github.com/Permify/permify/pkg/database/postgres"
12
)
13
14
const (
15
	repairDatabaseEngine = "database-engine"
16
	repairDatabaseURI    = "database-uri"
17
	repairBatchSize      = "batch-size"
18
	repairDryRun         = "dry-run"
19
	repairVerbose        = "verbose"
20
	repairRetries        = "retries"
21
)
22
23
// NewRepairCommand - Creates new repair command
24
func NewRepairCommand() *cobra.Command {
25
	cmd := &cobra.Command{
26
		Use:   "repair",
27
		Short: "repair PostgreSQL datastore after migration",
28
		Long:  "Repair PostgreSQL datastore to fix XID wraparound issues and transaction ID synchronization problems that can occur after database migration.",
29
		Args:  cobra.NoArgs,
30
	}
31
32
	cmd.AddCommand(NewRepairDatastoreCommand())
33
34
	return cmd
35
}
36
37
// NewRepairDatastoreCommand - Creates new repair datastore command
38
func NewRepairDatastoreCommand() *cobra.Command {
39
	cmd := &cobra.Command{
40
		Use:   "datastore",
41
		Short: "repair PostgreSQL XID counter to prevent wraparound issues",
42
		Long: `Repair PostgreSQL XID counter using a safe approach.
43
44
This command prevents XID wraparound issues by:
45
- Analyzing maximum referenced XIDs in transactions table
46
- Advancing PostgreSQL's XID counter to stay ahead of referenced XIDs
47
- Using safe batch processing to avoid performance impact
48
49
This approach does NOT modify existing data, only advances the XID counter.
50
Use --dry-run to see what would be changed without making actual modifications.`,
51
		RunE: repairDatastore(),
52
		Args: cobra.NoArgs,
53
	}
54
55
	// Add flags
56
	cmd.PersistentFlags().String(repairDatabaseEngine, "postgres", "database engine (only postgres supported)")
57
	cmd.PersistentFlags().String(repairDatabaseURI, "", "database URI (required)")
58
	cmd.PersistentFlags().Int(repairBatchSize, 1000, "batch size for XID advancement")
59
	cmd.PersistentFlags().Bool(repairDryRun, false, "perform a dry run without making changes")
60
	cmd.PersistentFlags().Bool(repairVerbose, true, "enable verbose logging")
61
	cmd.PersistentFlags().Int(repairRetries, 3, "maximum number of retries")
62
63
	// Mark required flags
64
	cmd.MarkPersistentFlagRequired(repairDatabaseURI)
65
66
	return cmd
67
}
68
69
// repairDatastore - permify repair datastore command
70
func repairDatastore() func(cmd *cobra.Command, args []string) error {
71
	return func(cmd *cobra.Command, args []string) error {
72
		// Get database URI and engine
73
		databaseURI, _ := cmd.Flags().GetString(repairDatabaseURI)
74
		databaseEngine, _ := cmd.Flags().GetString(repairDatabaseEngine)
75
76
		// Validate database engine
77
		if databaseEngine != "postgres" {
78
			return fmt.Errorf("only postgres database engine is supported for repair")
79
		}
80
81
		// Create PostgreSQL instance
82
		pg, err := postgres.New(databaseURI)
83
		if err != nil {
84
			return fmt.Errorf("failed to create PostgreSQL instance: %w", err)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
85
		}
86
		defer pg.Close()
87
88
		// Parse flags directly from cobra
89
		batchSize, _ := cmd.Flags().GetInt(repairBatchSize)
90
		retries, _ := cmd.Flags().GetInt(repairRetries)
91
		dryRun, _ := cmd.Flags().GetBool(repairDryRun)
92
		verbose, _ := cmd.Flags().GetBool(repairVerbose)
93
94
		// Create repair configuration
95
		config := &postgres.RepairConfig{
96
			BatchSize:  batchSize,
97
			MaxRetries: retries,
98
			RetryDelay: 100,
99
			DryRun:     dryRun,
100
			Verbose:    verbose,
101
		}
102
103
		// Perform repair
104
		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
105
		defer cancel()
106
107
		slog.InfoContext(ctx, "Starting PostgreSQL XID counter repair",
108
			slog.String("database_uri", maskURL(databaseURI)),
109
			slog.Int("batch_size", config.BatchSize),
110
			slog.Bool("dry_run", config.DryRun),
111
			slog.Int("max_retries", config.MaxRetries))
112
113
		start := time.Now()
114
		result, err := pg.Repair(ctx, config)
115
		duration := time.Since(start)
116
117
		if err != nil {
118
			return fmt.Errorf("repair failed: %w", err)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
119
		}
120
121
		// Print results
122
		slog.InfoContext(ctx, "Repair completed successfully",
123
			slog.Duration("duration", duration),
124
			slog.Int("created_tx_id_fixed", result.CreatedTxIdFixed),
125
			slog.Int("errors", len(result.Errors)))
126
127
		if len(result.Errors) > 0 {
128
			slog.WarnContext(ctx, "Errors encountered during repair")
129
			for i, err := range result.Errors {
130
				slog.WarnContext(ctx, "Repair error",
131
					slog.Int("error_index", i+1),
132
					slog.String("error", err.Error()))
133
			}
134
		}
135
136
		if result.CreatedTxIdFixed > 0 {
137
			slog.InfoContext(ctx, "XID counter repair completed successfully! Advanced XID counter to prevent wraparound issues.")
138
		} else {
139
			slog.InfoContext(ctx, "No XID counter repair needed. PostgreSQL XID counter is already properly positioned.")
140
		}
141
142
		return nil
143
	}
144
}
145
146
// maskURL masks sensitive information in database URL
147
func maskURL(url string) string {
148
	if len(url) < 10 {
149
		return "***"
150
	}
151
	return url[:10] + "***"
152
}
153