CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/golang-github-com-go-co-op-gocron-v2

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.

Overview
Eval results
Files

maintenance-jobs.mddocs/examples/by-scenario/

Maintenance Task Examples

Database cleanup, log rotation, backup jobs, and system maintenance tasks.

Database Cleanup

Old Record Cleanup

// 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),
)

Soft Delete Purge

// 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"),
)

Database Vacuum

// 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"),
)

Index Maintenance

// 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"),
)

Log Management

Log Rotation

// 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"),
)

Old Log Cleanup

// 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"),
)

Database Log Archival

// 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"),
)

Backup Jobs

Incremental Backup

// 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

// 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"),
)

Backup Verification

// 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"),
)

Backup Cleanup

// 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"),
)

System Maintenance

Temporary File 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"),
)

Disk Space Monitor

// 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"),
)

SSL Certificate Check

// 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"),
)

Complete Maintenance Example

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 {}
}

Related Documentation

  • Examples: Web App Tasks
  • Examples: Data Processing
  • Guide: Job Lifecycle
  • API: Job Options

Install with Tessl CLI

npx tessl i tessl/golang-github-com-go-co-op-gocron-v2@2.19.1

docs

index.md

tile.json