@@ -51,6 +51,14 @@ func BackUpDb() {
5151 log .Fatalf ("Error listing collections: %v" , err )
5252 }
5353
54+ // Check if a same-day or prior-day backup already exists. If so, skip the copy.
55+ // This prevents overwriting the current backup when re-running a job after a partial run.
56+ existingBackups := getBackupDbNames (client , ctx )
57+ if hasRecentBackup (existingBackups ) {
58+ log .Println ("A same-day or prior-day backup already exists. Skipping backup copy." )
59+ return
60+ }
61+
5462 log .Println ("Backing up database..." )
5563 // Iterate over each collection, copying records from the source to target DB
5664 for _ , collName := range collectionNames {
@@ -86,18 +94,73 @@ func BackUpDb() {
8694 }
8795 log .Println ("Successfully backed up database" )
8896
89- // Drop the oldest backup. Get a list of backup names so we can find the oldest backup.
97+ // Drop the oldest backup only if we have at least 3 backups AND the oldest is at least 21 days
98+ // old. This ensures we maintain ~3 weeks of weekly history and prevents accidentally dropping
99+ // all backups during partial runs or tightly-clustered re-runs.
90100 backupNames := getBackupDbNames (client , ctx )
91- oldestBackup := findOldestBackup (backupNames )
92- // Get a handle for the database
93- dbToDrop := client .Database (oldestBackup )
94-
95- // Drop the database
101+ const minBackupCount = 3
102+ const minBackupAgeDays = 21
103+ if len (backupNames ) < minBackupCount {
104+ log .Printf ("Only %d backup(s) exist (need %d). Skipping oldest backup drop." , len (backupNames ), minBackupCount )
105+ return
106+ }
107+ oldestBackupDate , err := parseBackupDate (findOldestBackup (backupNames ))
108+ if err != nil {
109+ log .Fatalf ("Failed to parse oldest backup date: %v" , err )
110+ }
111+ oldestAgedays := int (time .Since (oldestBackupDate ).Hours () / 24 )
112+ if oldestAgedays < minBackupAgeDays {
113+ log .Printf ("Oldest backup is only %d day(s) old (need %d). Skipping oldest backup drop." , oldestAgedays , minBackupAgeDays )
114+ return
115+ }
116+ oldestBackupName := findOldestBackup (backupNames )
117+ dbToDrop := client .Database (oldestBackupName )
96118 err = dbToDrop .Drop (ctx )
97119 if err != nil {
98- log .Fatalf ("Failed to drop database %v: %v" , oldestBackup , err )
120+ log .Fatalf ("Failed to drop database %v: %v" , oldestBackupName , err )
121+ }
122+ log .Printf ("Oldest backup database '%s' dropped successfully\n " , oldestBackupName )
123+ }
124+
125+ // hasRecentBackup returns true if any backup is from today or yesterday.
126+ func hasRecentBackup (backupNames []string ) bool {
127+ now := time .Now ().UTC ()
128+ today := time .Date (now .Year (), now .Month (), now .Day (), 0 , 0 , 0 , 0 , time .UTC )
129+ yesterday := today .AddDate (0 , 0 , - 1 )
130+ for _ , name := range backupNames {
131+ date , err := parseBackupDate (name )
132+ if err != nil {
133+ continue
134+ }
135+ if ! date .Before (yesterday ) {
136+ return true
137+ }
99138 }
100- log .Printf ("Oldest backup database '%s' dropped successfully\n " , oldestBackup )
139+ return false
140+ }
141+
142+ // parseBackupDate extracts the date from a backup database name (e.g. "backup_code_metrics_March_26").
143+ func parseBackupDate (name string ) (time.Time , error ) {
144+ parts := strings .Split (name , "_" )
145+ if len (parts ) < 4 {
146+ return time.Time {}, fmt .Errorf ("invalid backup name: %s" , name )
147+ }
148+ monthStr := parts [len (parts )- 2 ]
149+ dayStr := parts [len (parts )- 1 ]
150+ day , err := strconv .Atoi (dayStr )
151+ if err != nil {
152+ return time.Time {}, fmt .Errorf ("invalid day in backup name %s: %v" , name , err )
153+ }
154+ month , err := parseMonth (monthStr )
155+ if err != nil {
156+ return time.Time {}, err
157+ }
158+ now := time .Now ().UTC ()
159+ year := now .Year ()
160+ if month > now .Month () {
161+ year --
162+ }
163+ return time .Date (year , month , day , 0 , 0 , 0 , 0 , time .UTC ), nil
101164}
102165
103166// The cluster contains a mix of databases - some are backups, and some are other databases.
@@ -120,50 +183,13 @@ func getBackupDbNames(client *mongo.Client, ctx context.Context) []string {
120183
121184// Parse the dates from the backup database names to find the oldest backup database.
122185func findOldestBackup (backupNames []string ) string {
123- // Get the current date to determine the year for each backup
124- now := time .Now ()
125- currentMonth := now .Month ()
126- currentYear := now .Year ()
127-
128- // Variables to track the oldest date and its corresponding string
129186 var oldestDate time.Time
130187 var oldestBackupName string
131-
132- // Iterate over the strings to extract and compare dates
133188 for _ , entry := range backupNames {
134- // Split the string and find the month and day (assume the format is fixed)
135- parts := strings .Split (entry , "_" )
136- if len (parts ) < 4 {
137- continue // Skip invalid strings
138- }
139- monthStr := parts [len (parts )- 2 ] // Second-to-last part is the month
140- dayStr := parts [len (parts )- 1 ] // Last part is the day
141-
142- // Convert the day string to an integer
143- day , err := strconv .Atoi (dayStr )
189+ date , err := parseBackupDate (entry )
144190 if err != nil {
145- fmt .Println ("Error converting day:" , err )
146191 continue
147192 }
148-
149- // Parse the month using the time.Month enum
150- month , err := parseMonth (monthStr )
151- if err != nil {
152- fmt .Println ("Error parsing month:" , err )
153- continue
154- }
155-
156- // Determine the year for this backup
157- // If the backup month is after the current month, it must be from the previous year
158- year := currentYear
159- if month > currentMonth {
160- year = currentYear - 1
161- }
162-
163- // Create a time.Time object for the given month and day
164- date := time .Date (year , month , day , 0 , 0 , 0 , 0 , time .UTC )
165-
166- // Compare dates to find the earliest one
167193 if oldestBackupName == "" || date .Before (oldestDate ) {
168194 oldestDate = date
169195 oldestBackupName = entry
0 commit comments