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

interval-scheduling.mddocs/examples/by-task/

Interval Scheduling Examples

Schedule jobs at fixed intervals using DurationJob and DurationRandomJob.

Fixed Interval Scheduling

Every N Seconds/Minutes/Hours

// Every 30 seconds
j, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(func() {
        fmt.Println("30 seconds elapsed")
    }),
)

// Every 5 minutes
j, _ = s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(func() {
        fmt.Println("5 minutes elapsed")
    }),
)

// Every hour
j, _ = s.NewJob(
    gocron.DurationJob(time.Hour),
    gocron.NewTask(func() {
        fmt.Println("1 hour elapsed")
    }),
)

Minimal Working Example

package main

import (
    "fmt"
    "time"

    "github.com/go-co-op/gocron/v2"
)

func main() {
    // Create scheduler
    s, err := gocron.NewScheduler()
    if err != nil {
        panic(err)
    }
    defer s.Shutdown()

    // Add job
    j, err := s.NewJob(
        gocron.DurationJob(5*time.Second),
        gocron.NewTask(func() {
            fmt.Println("running every 5 seconds")
        }),
    )
    if err != nil {
        panic(err)
    }

    fmt.Printf("Job %s added\n", j.ID())

    // Start scheduler
    s.Start()

    // Block forever
    select {}
}

Interval from Completion

Wait N duration AFTER job completes, not from start time:

j, _ := s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(func() {
        // Takes variable time to complete
        doWork()
    }),
    gocron.WithIntervalFromCompletion(),
)
// Ensures 5 minutes of rest between executions

Comparison: Start vs Completion Interval

// Default: interval from start
// If job takes 2 minutes, next run is 3 minutes after completion (5 - 2)
j1, _ := s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(func() {
        time.Sleep(2 * time.Minute)
    }),
)

// With interval from completion
// If job takes 2 minutes, next run is 5 minutes after completion
j2, _ := s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(func() {
        time.Sleep(2 * time.Minute)
    }),
    gocron.WithIntervalFromCompletion(),
)

Long-Running Background Job

// Process queue with consistent wait time between batches
j, _ := s.NewJob(
    gocron.DurationJob(10*time.Second),
    gocron.NewTask(func() {
        batch := fetchQueueBatch()
        processBatch(batch) // Variable duration
    }),
    gocron.WithIntervalFromCompletion(),
    gocron.WithName("queue-processor"),
)
// Always waits 10 seconds after batch completes

Random Interval (Jitter)

Prevent thundering herd by randomizing intervals:

// Run every 4-6 minutes (prevents all instances running simultaneously)
j, _ := s.NewJob(
    gocron.DurationRandomJob(4*time.Minute, 6*time.Minute),
    gocron.NewTask(func() {
        checkHealth()
    }),
)

Health Check with Jitter

// Multiple instances won't all hit the health endpoint at once
j, _ := s.NewJob(
    gocron.DurationRandomJob(
        50*time.Second,
        70*time.Second,
    ),
    gocron.NewTask(func() {
        status := checkServiceHealth()
        if !status.OK {
            sendAlert(status.Error)
        }
    }),
    gocron.WithName("health-check"),
)

Cache Refresh with Jitter

// Randomize cache refresh to avoid stampede
j, _ := s.NewJob(
    gocron.DurationRandomJob(
        4*time.Minute,
        6*time.Minute,
    ),
    gocron.NewTask(func() {
        if cache.IsStale() {
            cache.Refresh()
        }
    }),
    gocron.WithName("cache-refresh"),
)

API Rate Limiting with Jitter

// Poll external API with randomized intervals to avoid rate limits
j, _ := s.NewJob(
    gocron.DurationRandomJob(
        45*time.Second,
        75*time.Second, // Average 60s, but randomized
    ),
    gocron.NewTask(func() {
        data, err := externalAPI.FetchUpdates()
        if err != nil {
            logger.Error("API fetch failed", "error", err)
            return
        }
        processUpdates(data)
    }),
    gocron.WithName("api-poller"),
)

Combining Intervals with Start/Stop Times

Limited Duration Interval

// Run every 30 seconds, but only for 5 minutes
start := time.Now()
end := start.Add(5 * time.Minute)

j, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(func() {
        fmt.Println("Limited run")
    }),
    gocron.WithStartAt(gocron.WithStartDateTime(start)),
    gocron.WithStopAt(gocron.WithStopDateTime(end)),
)

Delayed Start Interval

// Wait 1 hour before starting, then run every 5 minutes
startTime := time.Now().Add(time.Hour)

j, _ := s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(func() {
        fmt.Println("Delayed interval job")
    }),
    gocron.WithStartAt(gocron.WithStartDateTime(startTime)),
)

Warmup Period

// Start immediately, then switch to regular interval
j, _ := s.NewJob(
    gocron.DurationJob(time.Hour),
    gocron.NewTask(func() {
        syncData()
    }),
    gocron.WithStartAt(gocron.WithStartImmediately()),
    gocron.WithName("data-sync"),
)
// Runs once now, then hourly

Common Interval Patterns

High-Frequency Monitoring

// Very frequent checks (every 5 seconds)
j, _ := s.NewJob(
    gocron.DurationJob(5*time.Second),
    gocron.NewTask(func() {
        metrics := collectMetrics()
        if metrics.ErrorRate > 0.05 {
            triggerAlert()
        }
    }),
    gocron.WithName("metrics-check"),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Moderate Frequency Background Tasks

// Regular background processing (every 5 minutes)
j, _ := s.NewJob(
    gocron.DurationJob(5*time.Minute),
    gocron.NewTask(func() {
        processBackgroundQueue()
    }),
    gocron.WithName("background-processor"),
)

Low-Frequency Maintenance

// Periodic cleanup (every 6 hours)
j, _ := s.NewJob(
    gocron.DurationJob(6*time.Hour),
    gocron.NewTask(func() {
        cleanupTempFiles()
        archiveOldLogs()
    }),
    gocron.WithName("maintenance"),
)

Preventing Overlapping Runs

Skip Mode

// If job is running when next run is scheduled, skip it
j, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(func() {
        time.Sleep(time.Minute) // Takes longer than interval
    }),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Queue Mode

// If job is running, queue the next run
j, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(func() {
        time.Sleep(time.Minute)
    }),
    gocron.WithSingletonMode(gocron.LimitModeWait),
)

With Interval from Completion

// Best for ensuring consistent rest periods between runs
j, _ := s.NewJob(
    gocron.DurationJob(30*time.Second),
    gocron.NewTask(func() {
        // Variable duration work
        processItems()
    }),
    gocron.WithIntervalFromCompletion(),
    gocron.WithSingletonMode(gocron.LimitModeReschedule),
)

Complete Interval Example

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/go-co-op/gocron/v2"
)

func main() {
    s, err := gocron.NewScheduler(
        gocron.WithLocation(time.UTC),
        gocron.WithLogger(gocron.NewLogger(gocron.LogLevelInfo)),
    )
    if err != nil {
        panic(err)
    }
    defer s.Shutdown()

    // Fast interval: every 10 seconds
    s.NewJob(
        gocron.DurationJob(10*time.Second),
        gocron.NewTask(func() {
            fmt.Println("Fast check")
        }),
        gocron.WithName("fast-check"),
        gocron.WithSingletonMode(gocron.LimitModeReschedule),
    )

    // Medium interval: every 5 minutes
    s.NewJob(
        gocron.DurationJob(5*time.Minute),
        gocron.NewTask(func(ctx context.Context) {
            fmt.Println("Medium task")
            time.Sleep(2 * time.Minute)
        }),
        gocron.WithName("medium-task"),
        gocron.WithIntervalFromCompletion(),
    )

    // Slow interval with jitter: every 15-20 minutes
    s.NewJob(
        gocron.DurationRandomJob(15*time.Minute, 20*time.Minute),
        gocron.NewTask(func() {
            fmt.Println("Slow task with jitter")
        }),
        gocron.WithName("slow-task"),
    )

    // Hourly maintenance
    s.NewJob(
        gocron.DurationJob(time.Hour),
        gocron.NewTask(func() {
            fmt.Println("Hourly maintenance")
        }),
        gocron.WithName("hourly-maintenance"),
        gocron.WithStartAt(gocron.WithStartImmediately()),
    )

    s.Start()
    fmt.Println("Scheduler started with interval jobs")

    // Print schedule
    for _, j := range s.Jobs() {
        nextRun, _ := j.NextRun()
        fmt.Printf("%s: next run at %v\n", j.Name(), nextRun)
    }

    // Wait for interrupt
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
    <-sigCh

    fmt.Println("Shutting down...")
}

Related Documentation

  • API: Job DefinitionsDurationJob, DurationRandomJob
  • API: Job OptionsWithIntervalFromCompletion, WithSingletonMode
  • Guide: Concurrency Control
  • Examples: Cron Scheduling

Install with Tessl CLI

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

docs

index.md

tile.json