A Golang job scheduling library that lets you run Go functions at pre-determined intervals using cron expressions, fixed durations, daily, weekly, monthly, or one-time schedules with support for distributed deployments.
Database cleanup, log rotation, backup jobs, and system maintenance tasks.
// Delete old records daily at 2 AM
j, _ := s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(2, 0, 0))),
gocron.NewTask(func() error {
cutoffDate := time.Now().AddDate(0, 0, -90) // 90 days ago
result := db.Exec(`
DELETE FROM events
WHERE created_at < ?
AND archived = false
`, cutoffDate)
count := result.RowsAffected
log.Printf("Deleted %d old events", count)
metrics.RecordCleanup("events", count)
return nil
}),
gocron.WithName("cleanup-old-events"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)// Permanently delete soft-deleted records after 30 days
j, _ := s.NewJob(
gocron.WeeklyJob(
1,
gocron.NewWeekdays(time.Sunday),
gocron.NewAtTimes(gocron.NewAtTime(3, 0, 0)),
),
gocron.NewTask(func() error {
cutoffDate := time.Now().AddDate(0, 0, -30)
tables := []string{"users", "posts", "comments"}
totalDeleted := 0
for _, table := range tables {
result := db.Exec(fmt.Sprintf(`
DELETE FROM %s
WHERE deleted_at IS NOT NULL
AND deleted_at < ?
`, table), cutoffDate)
count := result.RowsAffected
totalDeleted += count
log.Printf("Purged %d soft-deleted records from %s", count, table)
}
log.Printf("Total purged: %d records", totalDeleted)
return nil
}),
gocron.WithName("soft-delete-purge"),
)// Vacuum database tables weekly
j, _ := s.NewJob(
gocron.WeeklyJob(
1,
gocron.NewWeekdays(time.Sunday),
gocron.NewAtTimes(gocron.NewAtTime(4, 0, 0)),
),
gocron.NewTask(func() error {
log.Println("Starting database vacuum")
tables := []string{"users", "events", "logs", "sessions"}
for _, table := range tables {
log.Printf("Vacuuming table: %s", table)
db.Exec(fmt.Sprintf("VACUUM ANALYZE %s", table))
}
log.Println("Database vacuum complete")
return nil
}),
gocron.WithName("db-vacuum"),
)// Rebuild fragmented indexes monthly
j, _ := s.NewJob(
gocron.MonthlyJob(
1,
gocron.NewDaysOfTheMonth(1),
gocron.NewAtTimes(gocron.NewAtTime(3, 0, 0)),
),
gocron.NewTask(func() error {
log.Println("Starting index maintenance")
// Get fragmented indexes
fragmentedIndexes := db.Query(`
SELECT tablename, indexname
FROM pg_indexes
WHERE schemaname = 'public'
`)
for _, idx := range fragmentedIndexes {
log.Printf("Reindexing %s.%s", idx.TableName, idx.IndexName)
db.Exec(fmt.Sprintf("REINDEX INDEX %s", idx.IndexName))
}
log.Println("Index maintenance complete")
return nil
}),
gocron.WithName("index-maintenance"),
)// Rotate application logs daily at 1 AM
j, _ := s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(1, 0, 0))),
gocron.NewTask(func() error {
logFiles := []string{
"/var/log/app/application.log",
"/var/log/app/error.log",
"/var/log/app/access.log",
}
timestamp := time.Now().Format("2006-01-02")
for _, logFile := range logFiles {
// Rotate log file
archiveName := fmt.Sprintf("%s.%s", logFile, timestamp)
err := os.Rename(logFile, archiveName)
if err != nil {
log.Printf("Failed to rotate %s: %v", logFile, err)
continue
}
// Compress archived log
err = compressFile(archiveName)
if err != nil {
log.Printf("Failed to compress %s: %v", archiveName, err)
}
// Create new log file
f, _ := os.Create(logFile)
f.Close()
log.Printf("Rotated log: %s", logFile)
}
return nil
}),
gocron.WithName("log-rotation"),
)// Delete old compressed logs weekly
j, _ := s.NewJob(
gocron.WeeklyJob(
1,
gocron.NewWeekdays(time.Monday),
gocron.NewAtTimes(gocron.NewAtTime(2, 0, 0)),
),
gocron.NewTask(func() error {
logDir := "/var/log/app"
cutoffDate := time.Now().AddDate(0, 0, -30) // 30 days
files, err := filepath.Glob(filepath.Join(logDir, "*.log.*.gz"))
if err != nil {
return err
}
deleted := 0
for _, file := range files {
info, err := os.Stat(file)
if err != nil {
continue
}
if info.ModTime().Before(cutoffDate) {
os.Remove(file)
deleted++
}
}
log.Printf("Deleted %d old log files", deleted)
return nil
}),
gocron.WithName("old-log-cleanup"),
)// Archive database logs to S3 monthly
j, _ := s.NewJob(
gocron.MonthlyJob(
1,
gocron.NewDaysOfTheMonth(1),
gocron.NewAtTimes(gocron.NewAtTime(1, 0, 0)),
),
gocron.NewTask(func() error {
lastMonth := time.Now().AddDate(0, -1, 0)
startDate := time.Date(lastMonth.Year(), lastMonth.Month(), 1, 0, 0, 0, 0, time.Local)
endDate := startDate.AddDate(0, 1, 0)
log.Printf("Archiving logs from %v to %v", startDate, endDate)
// Export logs to file
filename := fmt.Sprintf("logs-%s.json.gz", startDate.Format("2006-01"))
err := exportLogs(filename, startDate, endDate)
if err != nil {
return err
}
// Upload to S3
err = uploadToS3(filename, "logs/"+filename)
if err != nil {
return err
}
// Delete archived logs from database
db.Exec("DELETE FROM logs WHERE created_at >= ? AND created_at < ?", startDate, endDate)
os.Remove(filename)
log.Printf("Archived logs to S3: %s", filename)
return nil
}),
gocron.WithName("log-archival"),
)// Incremental backup every 6 hours
j, _ := s.NewJob(
gocron.DurationJob(6*time.Hour),
gocron.NewTask(func() error {
timestamp := time.Now().Format("2006-01-02-15")
backupFile := fmt.Sprintf("backup-incremental-%s.tar.gz", timestamp)
log.Println("Starting incremental backup")
// Backup changed files only
err := createIncrementalBackup(backupFile)
if err != nil {
return fmt.Errorf("backup failed: %w", err)
}
// Upload to backup storage
err = uploadToBackupStorage(backupFile)
if err != nil {
return fmt.Errorf("upload failed: %w", err)
}
os.Remove(backupFile)
log.Printf("Incremental backup complete: %s", backupFile)
return nil
}),
gocron.WithName("incremental-backup"),
gocron.WithTags("backup"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)// Full backup daily at 2 AM
j, _ := s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(2, 0, 0))),
gocron.NewTask(func() error {
date := time.Now().Format("2006-01-02")
backupFile := fmt.Sprintf("backup-full-%s.tar.gz", date)
log.Println("Starting full backup")
// Create full backup
err := createFullBackup(backupFile)
if err != nil {
return fmt.Errorf("backup failed: %w", err)
}
// Upload to multiple locations
err = uploadToBackupStorage(backupFile)
if err != nil {
return fmt.Errorf("primary upload failed: %w", err)
}
err = uploadToOffsite(backupFile)
if err != nil {
log.Printf("Warning: offsite upload failed: %v", err)
}
os.Remove(backupFile)
log.Printf("Full backup complete: %s", backupFile)
return nil
}),
gocron.WithName("daily-backup"),
gocron.WithTags("backup"),
)// Verify backups weekly on Sunday at 3 AM
j, _ := s.NewJob(
gocron.WeeklyJob(
1,
gocron.NewWeekdays(time.Sunday),
gocron.NewAtTimes(gocron.NewAtTime(3, 0, 0)),
),
gocron.NewTask(func() error {
log.Println("Starting backup verification")
backups := listBackupFiles()
verified := 0
failed := 0
for _, backup := range backups {
err := verifyBackup(backup)
if err != nil {
log.Printf("Backup verification failed: %s - %v", backup, err)
alertOps("backup-verification-failed", backup)
failed++
} else {
verified++
}
}
log.Printf("Backup verification: %d verified, %d failed", verified, failed)
if failed > 0 {
return fmt.Errorf("%d backup(s) failed verification", failed)
}
return nil
}),
gocron.WithName("backup-verification"),
gocron.WithTags("backup"),
)// Clean up old backups - keep last 30 daily, 12 monthly
j, _ := s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(4, 0, 0))),
gocron.NewTask(func() error {
cutoffDaily := time.Now().AddDate(0, 0, -30)
cutoffMonthly := time.Now().AddDate(0, -12, 0)
// List all backups
backups := listBackupFiles()
deleted := 0
for _, backup := range backups {
info := getBackupInfo(backup)
// Keep monthly backups for 12 months
if info.IsMonthly && info.Date.Before(cutoffMonthly) {
deleteBackup(backup)
deleted++
}
// Keep daily backups for 30 days
if !info.IsMonthly && info.Date.Before(cutoffDaily) {
deleteBackup(backup)
deleted++
}
}
log.Printf("Deleted %d old backups", deleted)
return nil
}),
gocron.WithName("backup-cleanup"),
)// Clean temporary files daily at 3 AM
j, _ := s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(3, 0, 0))),
gocron.NewTask(func() error {
tempDirs := []string{
"/tmp/app",
"/var/tmp/uploads",
"./tmp",
}
cutoff := time.Now().Add(-24 * time.Hour)
totalDeleted := 0
for _, dir := range tempDirs {
files, err := os.ReadDir(dir)
if err != nil {
log.Printf("Failed to read %s: %v", dir, err)
continue
}
for _, file := range files {
info, _ := file.Info()
if info.ModTime().Before(cutoff) {
path := filepath.Join(dir, file.Name())
os.RemoveAll(path)
totalDeleted++
}
}
}
log.Printf("Cleaned up %d temporary files", totalDeleted)
return nil
}),
gocron.WithName("temp-cleanup"),
)// Monitor disk space every 10 minutes
j, _ := s.NewJob(
gocron.DurationJob(10*time.Minute),
gocron.NewTask(func() error {
disks := []string{"/", "/var", "/home"}
for _, disk := range disks {
usage := getDiskUsage(disk)
// Alert if usage > 85%
if usage.PercentUsed > 85 {
alertOps(
"high-disk-usage",
fmt.Sprintf("%s is %.1f%% full", disk, usage.PercentUsed),
)
}
// Critical alert if > 95%
if usage.PercentUsed > 95 {
alertOps(
"critical-disk-usage",
fmt.Sprintf("%s is %.1f%% full - CRITICAL", disk, usage.PercentUsed),
)
}
metrics.RecordDiskUsage(disk, usage.PercentUsed)
}
return nil
}),
gocron.WithName("disk-monitor"),
)// Check SSL certificate expiration daily
j, _ := s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(9, 0, 0))),
gocron.NewTask(func() error {
domains := []string{
"example.com",
"api.example.com",
"www.example.com",
}
for _, domain := range domains {
cert, err := getCertificateInfo(domain)
if err != nil {
log.Printf("Failed to check certificate for %s: %v", domain, err)
continue
}
daysUntilExpiry := int(time.Until(cert.NotAfter).Hours() / 24)
// Alert if expiring soon
if daysUntilExpiry < 30 {
alertOps(
"ssl-expiring-soon",
fmt.Sprintf("%s certificate expires in %d days", domain, daysUntilExpiry),
)
}
log.Printf("%s: %d days until expiry", domain, daysUntilExpiry)
}
return nil
}),
gocron.WithName("ssl-check"),
)package main
import (
"log"
"time"
"github.com/go-co-op/gocron/v2"
)
func main() {
s, _ := gocron.NewScheduler()
defer s.Shutdown()
// Database cleanup daily at 2 AM
s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(2, 0, 0))),
gocron.NewTask(func() error {
return cleanupOldRecords()
}),
gocron.WithName("db-cleanup"),
)
// Log rotation daily at 1 AM
s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(1, 0, 0))),
gocron.NewTask(func() error {
return rotateLogs()
}),
gocron.WithName("log-rotation"),
)
// Full backup daily at 2 AM
s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(2, 0, 0))),
gocron.NewTask(func() error {
return createFullBackup()
}),
gocron.WithName("daily-backup"),
gocron.WithTags("backup"),
)
// Incremental backup every 6 hours
s.NewJob(
gocron.DurationJob(6*time.Hour),
gocron.NewTask(func() error {
return createIncrementalBackup()
}),
gocron.WithName("incremental-backup"),
gocron.WithTags("backup"),
)
// Disk space check every 10 minutes
s.NewJob(
gocron.DurationJob(10*time.Minute),
gocron.NewTask(func() error {
return checkDiskSpace()
}),
gocron.WithName("disk-monitor"),
)
s.Start()
log.Println("Maintenance scheduler started")
select {}
}Install with Tessl CLI
npx tessl i tessl/golang-github-com-go-co-op-gocron-v2docs
api
examples
guides