Written by

Sales Engineer at InterSystems Corporation
Question Marc Mundt · Jun 21, 2016

Looking for a good routine/class for purging Caché backups

A customer is using Caché online backups and needs to automatically purge the cbk files with a scheduled task.

This is a wheel has been reinvented uncountable times already and I know somebody out there has a well written, extremely robust version that has already stood the test of time.

Does anyone have a nice routine/class/task for purging old Caché backup files? 

Comments

Jenna Makin · Jun 21, 2016

Funny you should ask this as I was just looking at how to do this today.  

Most operating systems offer a way to search for files given a certain filter, such as being older than a certain date, and then piping that list to another command, such as delete.

Here is a class method I wrote to do this on a Windows 2012 R2 server running Cache

ClassMethod PurgeFiles(Path As %String, OlderThan As %Integer)
{
    set Date=$zd($h-OlderThan)
    set cmd="forfiles /P "_Path_" /D -"_Date_" /C ""cmd /c del @path"""
    set sc=$zf(-1,cmd)
}

This method accepts a path and an integer indicating the number of days to keep files for.  It then uses constructs a command line which uses the "forfiles" command passing the path and a calculated date.  For each file it finds, it executes the command cmd /c del <path> which deletes the file.

There are probably more elegant ways to do this, cross platform compatible, but this is one solution that I had.

0
Ben Spead  Jun 21, 2016 to Jenna Makin

If you look at the %Library.File:FileSet() query and the %Library.File:Delete() method I think you could probably convert this to an OS-independent solution in about 10 min or less :)  Just sort the FileSet() query results by DateCreated and then stop processing the files once you get to the cutoff date for what you want to keep :) 

0
Dean Stith  Jul 1, 2016 to Jenna Makin

I would refrain from using $ZF(-1,cmd) because if the callout gets hung at the O/S for any reason, the job issuing the $ZF will be hung.

I’d suggest using InterProcess Communication to the O/S or the %Fileset query as shown below.

0
Jenna Makin · Jun 23, 2016

Here's an example of how one might use the FileSet query in the %File class and the Delete class method in %File to purge backup files in a given directory before a given date.

/// Purge backups older than <var>DaysToKeep</var>/// <var>Path</var> points to the directory path containing the backups./// Only *.cbk files will be purgedClassMethod PurgeBackups(Directory As %String, DaysToKeep As %Integer = 14) As %Integer{	// Calculate the oldest date to keep files on or after	set BeforeThisDate = $zdt($h-DaysToKeep_",0",3)	// Gather the list of files in the specified directory	set rs=##class(%ResultSet).%New("%File:FileSet")	do rs.Execute(Directory,"*.cbk","DateModified")	// Step through the files in DateModified order	while rs.Next() {		set DateModified=rs.Get("DateModified")		if BeforeThisDate]DateModified {			// Delete the file			set Name=rs.Get("Name")			do ##class(%File).Delete(Name)		}		// Stop when we get to files with last modified dates on or after our delete date		if DateModified]BeforeThisDate quit	}}
0
Ben Spead  Jun 23, 2016 to Jenna Makin

Bravo! Make sure to tag this as "Code Sample" so people can find it easily in the future

0
Jenna Makin  Jun 23, 2016 to Ben Spead

Not sure where you specify its a code snippet Ben

0
Ben Spead  Jun 24, 2016 to Jenna Makin

I apologize - I thought it was possible to add Tags to Answers, but I guess not.  The Code Sample tag would need to be added by Marc to the original question.

0
Stephen De Gabrielle · Dec 18, 2018

The article Recommendations on installing the InterSystems Caché DBMS for production environment  has some code that does this.

My comment  has a class that only deletes old backups as a scheduled task.

Kind regards,

Stephen

Class App.PurgeOldBackupFiles Extends %SYS.Task.Definition{Property BackupsToKeep As %Integer(MAXVAL = 30, MINVAL = 1) [ InitialExpression = 30, Required ];Property BackupFolder As %String [ Required ];Property BackupFileType As %String [ Required ];Method OnTask() As %Status{//s BackupsToKeep = 2//s Folder = "c:\backupfolder"//s BackupFileType = "FullAllDatabases" // or "FullDBList"SortOrder = "DateModified"If ..BackupsToKeep<1 Quit $$$ERROR($$$GeneralError,"Invalid - Number of Backups to Keep must be greater than or equal to 1")If ..BackupFolder="" Quit $$$ERROR($$$GeneralError,"Invalid - BackupFolder - not supplied")if ..BackupFileType = "" Quit $$$ERROR($$$GeneralError,"Invalid - BackupFileType - not supplied")if (..BackupFileType '= "FullAllDatabases")&&(..BackupFileType '= "FullDBList") Quit $$$ERROR($$$GeneralError,"Invalid - BackupFileType")BackupCount=0     k zPurgeOldBackupFiles(..BackupFileType)     Set rs=##class(%ResultSet).%New("%Library.File:FileSet")     w !,"backuplist",!     s BackupFileWildcard = ..BackupFileType _ "*.cbk"     set status=rs.Execute(..BackupFolder, BackupFileWildcard, SortOrder)     WHILE rs.Next() {          Set FullFileName=rs.Data("Name")          Set FName=##class(%File).GetFilename(FullFileName)          Set FDateTime=##class(%File).GetFileDateModified(FullFileName)          w "File "_FName_" "_FDateTime,!          Set FDate=$PIECE(FDateTime,",")          Set CDate=$PIECE($H,",")          s BackupCount=$I(BackupCount)          s zPurgeOldBackupFiles(..BackupFileType, BackupCount)=FullFileName      }     s zPurgeOldBackupFiles(..BackupFileType, "BackupCount")=BackupCount     do rs.Close()     if BackupCount > ..BackupsToKeep {          for i=1:1:BackupCount-..BackupsToKeep {               s FullFileName = zPurgeOldBackupFiles(..BackupFileType, i)               d ##class(%File).Delete(FullFileName)               w "File Purged "_FullFileName_" Purged",!          }     }     q status}}
0
Graham Brown  Apr 2, 2020 to Stephen De Gabrielle

Hi Stephen, thanks for sharing but do we use this code in an adapter? Or is it a routine?

0
Alexey Maslov  Apr 2, 2020 to Graham Brown

Hi Graham,
the code published above is a Task Manager task. If you need flexible Task to purge .cbk and .log files created by internal online backup tasks of any kind, you may also want to look at cmPurgeBackup.

0
Sylvain Guilbaud · Jan 13

You can schedule the following task which removes any file from a directory, based on its age, using Python:

Class admin.purge Extends%SYS.Task.Definition
{

Property Directory As%String(MAXLEN = 2000) [ InitialExpression = "/usr/irissys/mgr/Backup" ];Property DaysToKeep As%Integer(VALUELIST = ",0,1,2,3,4,5") [ InitialExpression = "1" ];

Method OnTask() As%Status
{
    set sc = $$$OKTry {
        do..purge(..Directory,..DaysToKeep)
    }
    Catch ex {
        Set sc=ex.AsStatus()
    }
    return sc
}

ClassMethod purge(path As%String, daysToKeep As%Integer) As%Status [ Language = python ]
{
import iris
import os
import time
from datetime import datetime, timedelta

event = "[TASK PURGE OLD BACKUP FILES]"class FileDeletionError(Exception):
    """Custom exception for file deletion errors."""
    pass

def delete_old_files(path, days_limit):
    limit_date = datetime.now() - timedelta(days=int(days_limit))

    for file in os.listdir(path):
        file_path = os.path.join(path, file)
        if os.path.isfile(file_path):
            creation_date = datetime.fromtimestamp(os.path.getctime(file_path))
            if creation_date < limit_date:
                try:
                    os.remove(file_path)
                    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Deleted: {str(file_path)}")
                except PermissionError:
                    raise FileDeletionError(f"Permission error: Unable to delete {file}")
                except FileNotFoundError:
                    raise FileDeletionError(f"File not found: {file}")
                except Exception as e:
                    raise FileDeletionError(f"Unexpected error while deleting {file}: {str(e)}")

try:
    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Executing task to delete backup files from directory {path} created more than {str(daysToKeep)} days ago")
    days_limit = daysToKeep
    delete_old_files(path, days_limit)
except FileDeletionError as e:
    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Error during deletion: {str(e)}",0,1)
except Exception as e:
    iris.cls("%SYS.System").WriteToConsoleLog(f"{event} Unexpected error: {str(e)}",0,1)
}

}
01/13/25-18:21:00:549 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Executing task to delete backup files from directory /usr/irissys/mgr/Backup created more than 0 days ago
01/13/25-18:21:00:550 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_009.cbk
01/13/25-18:21:00:550 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_008.log
01/13/25-18:21:00:598 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_008.cbk
01/13/25-18:21:00:598 (35722) 0 [Utility.Event] [TASK PURGE OLD BACKUP FILES] Deleted: /usr/irissys/mgr/Backup/FullAllDatabases_20250113_009.log
0