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.
Most frequently used scheduling patterns with gocron v2.
// Clean up temporary files every hour
j, _ := s.NewJob(
gocron.DurationJob(time.Hour),
gocron.NewTask(func() {
cleanupTempFiles()
}),
gocron.WithName("cleanup-temp-files"),
gocron.WithTags("maintenance", "cleanup"),
)// Refresh cache every 5 minutes
j, _ := s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func() {
if err := refreshCache(); err != nil {
log.Printf("Cache refresh failed: %v", err)
}
}),
gocron.WithName("cache-refresh"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)Use singleton mode to skip runs if the previous refresh is still in progress.
// Check service health every 30 seconds
j, _ := s.NewJob(
gocron.DurationJob(30*time.Second),
gocron.NewTask(func() {
if err := checkHealth(); err != nil {
log.Printf("Health check failed: %v", err)
alerting.SendAlert("Service unhealthy", err.Error())
}
}),
gocron.WithName("health-check"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)// Generate report every day at 9 AM
j, _ := s.NewJob(
gocron.DailyJob(
1,
gocron.NewAtTimes(gocron.NewAtTime(9, 0, 0)),
),
gocron.NewTask(func() {
report := generateDailyReport()
sendEmail("admin@example.com", report)
}),
gocron.WithName("daily-report"),
)// Run every 15 minutes, Monday-Friday, 9 AM - 5 PM
j, _ := s.NewJob(
gocron.CronJob("*/15 9-17 * * 1-5", false),
gocron.NewTask(processBusinessTasks),
gocron.WithName("business-hours-processor"),
)// Run every Saturday at 3 AM
j, _ := s.NewJob(
gocron.WeeklyJob(
1,
gocron.NewWeekdays(time.Saturday),
gocron.NewAtTimes(gocron.NewAtTime(3, 0, 0)),
),
gocron.NewTask(func() {
performDatabaseMaintenance()
optimizeIndexes()
cleanupOldRecords()
}),
gocron.WithName("weekend-maintenance"),
)// Process queue items every minute
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func() {
items := fetchQueueItems()
for _, item := range items {
if err := processItem(item); err != nil {
log.Printf("Failed to process item %s: %v", item.ID, err)
}
}
}),
gocron.WithName("queue-processor"),
gocron.WithSingletonMode(gocron.LimitModeWait),
)Use LimitModeWait to ensure all items are processed eventually.
// Process records in batches every 5 minutes
j, _ := s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func(ctx context.Context) {
batchSize := 100
offset := 0
for {
select {
case <-ctx.Done():
log.Println("Batch processing cancelled")
return
default:
records := fetchRecords(offset, batchSize)
if len(records) == 0 {
return
}
processBatch(records)
offset += batchSize
}
}
}),
gocron.WithName("batch-processor"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)// Sync with external database every 10 minutes
j, _ := s.NewJob(
gocron.DurationJob(10*time.Minute),
gocron.NewTask(func() {
changes := fetchExternalChanges()
for _, change := range changes {
applyChange(change)
}
updateSyncTimestamp()
}),
gocron.WithName("db-sync"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)// Poll external API every 2 minutes
j, _ := s.NewJob(
gocron.DurationJob(2*time.Minute),
gocron.NewTask(func() {
data, err := fetchFromAPI()
if err != nil {
log.Printf("API poll failed: %v", err)
return
}
processData(data)
}),
gocron.WithName("api-poller"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)// Retry failed webhooks every 5 minutes
j, _ := s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func() {
failedWebhooks := getFailedWebhooks()
for _, webhook := range failedWebhooks {
if err := retryWebhook(webhook); err != nil {
log.Printf("Webhook retry failed: %v", err)
} else {
markWebhookSuccessful(webhook)
}
}
}),
gocron.WithName("webhook-retry"),
)// Process rate-limited requests
j, _ := s.NewJob(
gocron.DurationJob(time.Second),
gocron.NewTask(func() {
// Process one request per second to respect rate limit
request := getNextPendingRequest()
if request != nil {
sendRequest(request)
}
}),
gocron.WithName("rate-limited-processor"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)// Collect system metrics every 10 seconds
j, _ := s.NewJob(
gocron.DurationJob(10*time.Second),
gocron.NewTask(func() {
metrics := collectSystemMetrics()
sendToMonitoring(metrics)
}),
gocron.WithName("metrics-collector"),
)// Aggregate logs every minute
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func() {
logs := aggregateLogs()
if len(logs) > 0 {
sendToLogService(logs)
}
}),
gocron.WithName("log-aggregator"),
gocron.WithSingletonMode(gocron.LimitModeWait),
)// Run warmup immediately on startup
j, _ := s.NewJob(
gocron.DurationJob(24*time.Hour),
gocron.NewTask(func() {
warmupCache()
preloadData()
}),
gocron.WithName("warmup"),
gocron.WithStartAt(gocron.WithStartImmediately()),
)// Send reminder in 1 hour
reminderTime := time.Now().Add(time.Hour)
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(reminderTime)),
gocron.NewTask(func() {
sendReminder("Don't forget to submit your report!")
}),
gocron.WithName("reminder"),
)// High-priority scheduler (no concurrency limit)
highPriority, _ := gocron.NewScheduler()
defer highPriority.Shutdown()
highPriority.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(criticalTask),
gocron.WithName("critical"),
)
// Low-priority scheduler (limited to 2 concurrent jobs)
lowPriority, _ := gocron.NewScheduler(
gocron.WithLimitConcurrentJobs(2, gocron.LimitModeReschedule),
)
defer lowPriority.Shutdown()
lowPriority.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(backgroundTask),
gocron.WithName("background"),
)
highPriority.Start()
lowPriority.Start()j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func() error {
maxRetries := 3
for attempt := 1; attempt <= maxRetries; attempt++ {
if err := performOperation(); err != nil {
if attempt == maxRetries {
log.Printf("Operation failed after %d attempts", maxRetries)
return err
}
log.Printf("Attempt %d failed, retrying...", attempt)
time.Sleep(time.Second * time.Duration(attempt))
continue
}
return nil
}
return nil
}),
gocron.WithName("operation-with-retry"),
)j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(riskyOperation),
gocron.WithName("risky-operation"),
gocron.WithEventListeners(
gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
log.Printf("Job %s failed: %v", jobName, err)
sendAlert(jobName, err)
}),
gocron.AfterJobRunsWithPanic(func(jobID uuid.UUID, jobName string, recoverData any) {
log.Printf("Job %s panicked: %v", jobName, recoverData)
sendCriticalAlert(jobName, recoverData)
}),
),
)j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func(ctx context.Context) {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
log.Println("Job cancelled, cleaning up...")
cleanup()
return
case <-ticker.C:
doWork()
}
}
}),
gocron.WithName("long-running-job"),
)ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(func(ctx context.Context) {
select {
case <-ctx.Done():
log.Println("Job timed out")
return
default:
doWork()
}
}),
gocron.WithName("timed-job"),
gocron.WithContext(ctx),
)func setupJobs(s gocron.Scheduler, config Config) {
// Always add core jobs
s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(coreTask),
gocron.WithName("core-task"),
)
// Conditionally add feature jobs
if config.EnableReporting {
s.NewJob(
gocron.DailyJob(1, gocron.NewAtTimes(gocron.NewAtTime(9, 0, 0))),
gocron.NewTask(generateReport),
gocron.WithName("daily-report"),
)
}
if config.EnableCleanup {
s.NewJob(
gocron.DurationJob(time.Hour),
gocron.NewTask(cleanup),
gocron.WithName("cleanup"),
)
}
}// Add temporary jobs
s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(tempTask1),
gocron.WithTags("temporary", "experiment"),
)
s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(tempTask2),
gocron.WithTags("temporary", "experiment"),
)
// Later: remove all temporary jobs
s.RemoveByTags("temporary")// Good: named job
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(myFunc),
gocron.WithName("my-cleanup-job"),
)
// Bad: unnamed job (hard to identify in logs)
j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(myFunc),
)j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(slowOperation),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)task := gocron.NewTask(func(ctx context.Context) {
select {
case <-ctx.Done():
return
default:
doWork()
}
})s, _ := gocron.NewScheduler()
defer s.Shutdown()j, _ := s.NewJob(
gocron.DurationJob(time.Minute),
gocron.NewTask(myFunc),
gocron.WithTags("cleanup", "database", "production"),
)Install with Tessl CLI
npx tessl i tessl/golang-github-com-go-co-op-gocron-v2@2.19.1docs
api
examples
guides