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.
Warmup tasks, one-time setup, migration jobs, and application startup tasks.
// Warm up caches immediately on application start
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Warming up caches...")
// Warm up user cache
users := fetchActiveUsers()
for _, user := range users {
cache.Set(fmt.Sprintf("user:%d", user.ID), user, 1*time.Hour)
}
// Warm up product cache
products := fetchPopularProducts()
for _, product := range products {
cache.Set(fmt.Sprintf("product:%d", product.ID), product, 30*time.Minute)
}
// Warm up configuration cache
config := fetchConfiguration()
cache.Set("app:config", config, 10*time.Minute)
log.Println("Cache warmup complete")
return nil
}),
gocron.WithName("cache-warmup"),
)// Initialize database connections on startup
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Warming up database connection pool...")
// Pre-create connections
for i := 0; i < 10; i++ {
conn := db.GetConnection()
// Simple query to test connection
conn.Ping()
db.ReleaseConnection(conn)
}
log.Println("Database connection pool ready")
return nil
}),
gocron.WithName("db-pool-warmup"),
)// Register with service discovery immediately
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Registering with service discovery...")
serviceInfo := ServiceInfo{
Name: "my-service",
Address: os.Getenv("SERVICE_ADDR"),
Port: os.Getenv("SERVICE_PORT"),
Health: "/health",
}
err := consul.RegisterService(serviceInfo)
if err != nil {
return fmt.Errorf("service registration failed: %w", err)
}
log.Println("Service registered successfully")
return nil
}),
gocron.WithName("service-registration"),
)// Run database migrations once on startup
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Running database migrations...")
migrator := NewMigrator(db)
// Check current version
currentVersion := migrator.GetCurrentVersion()
log.Printf("Current schema version: %d", currentVersion)
// Run pending migrations
err := migrator.MigrateUp()
if err != nil {
return fmt.Errorf("migration failed: %w", err)
}
newVersion := migrator.GetCurrentVersion()
log.Printf("Migrated to version: %d", newVersion)
return nil
}),
gocron.WithName("db-migration"),
gocron.WithEventListeners(
gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
log.Printf("CRITICAL: Database migration failed: %v", err)
// Don't start other jobs if migrations fail
os.Exit(1)
}),
),
)// Migrate data format (run once)
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
// Check if migration already completed
if migrationCompleted("data_format_v2") {
log.Println("Data migration already completed")
return nil
}
log.Println("Starting data format migration...")
// Migrate in batches
batchSize := 1000
offset := 0
totalMigrated := 0
for {
records := db.Query(`
SELECT * FROM records
WHERE format_version = 1
LIMIT ? OFFSET ?
`, batchSize, offset)
if len(records) == 0 {
break
}
for _, record := range records {
migratedData := migrateDataFormat(record)
db.Update("records", record.ID, migratedData)
}
totalMigrated += len(records)
offset += batchSize
log.Printf("Migrated %d/%d records", totalMigrated, offset)
}
// Mark migration as complete
markMigrationComplete("data_format_v2")
log.Printf("Data migration complete: %d records migrated", totalMigrated)
return nil
}),
gocron.WithName("data-migration"),
)// Load configuration from remote source on startup
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Loading remote configuration...")
// Fetch configuration from config server
config, err := configClient.FetchConfiguration("production")
if err != nil {
// Try fallback
log.Printf("Remote config failed, using fallback: %v", err)
config = loadLocalConfiguration()
}
// Apply configuration
app.SetConfiguration(config)
// Cache configuration
cache.Set("app:config", config, 5*time.Minute)
log.Println("Configuration loaded successfully")
return nil
}),
gocron.WithName("config-load"),
)// Initialize feature flags on startup
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Initializing feature flags...")
// Load feature flags
flags, err := featureFlagService.GetAllFlags()
if err != nil {
return fmt.Errorf("failed to load feature flags: %w", err)
}
// Initialize flag cache
for _, flag := range flags {
featureFlags.Set(flag.Name, flag.Enabled)
}
log.Printf("Loaded %d feature flags", len(flags))
return nil
}),
gocron.WithName("feature-flags-init"),
)// Wait for dependencies before starting service
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(time.Now().Add(30*time.Second))),
gocron.NewTask(func() error {
log.Println("Starting delayed service initialization...")
// Wait for dependencies
if !waitForDependency("database", 30*time.Second) {
return errors.New("database not ready")
}
if !waitForDependency("cache", 30*time.Second) {
return errors.New("cache not ready")
}
// Start service
startService()
log.Println("Service started successfully")
return nil
}),
gocron.WithName("delayed-service-start"),
)// Initialize components in stages
func setupStagedInitialization(s gocron.Scheduler) {
now := time.Now()
// Stage 1: Core components (immediate)
s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Stage 1: Initializing core components")
initializeDatabase()
initializeCache()
return nil
}),
gocron.WithName("init-stage-1"),
)
// Stage 2: Application layer (after 10 seconds)
s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(now.Add(10*time.Second))),
gocron.NewTask(func() error {
log.Println("Stage 2: Initializing application layer")
loadConfiguration()
initializeServices()
return nil
}),
gocron.WithName("init-stage-2"),
)
// Stage 3: Background workers (after 20 seconds)
s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(now.Add(20*time.Second))),
gocron.NewTask(func() error {
log.Println("Stage 3: Starting background workers")
startWorkers()
return nil
}),
gocron.WithName("init-stage-3"),
)
// Stage 4: Warmup (after 30 seconds)
s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(now.Add(30*time.Second))),
gocron.NewTask(func() error {
log.Println("Stage 4: Warming up caches")
warmupCaches()
return nil
}),
gocron.WithName("init-stage-4"),
)
}// Perform initial health check before accepting traffic
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Performing initial health check...")
checks := []HealthCheck{
{Name: "database", Check: checkDatabase},
{Name: "cache", Check: checkCache},
{Name: "external-api", Check: checkExternalAPI},
{Name: "disk-space", Check: checkDiskSpace},
}
failed := []string{}
for _, check := range checks {
err := check.Check()
if err != nil {
log.Printf("Health check failed: %s - %v", check.Name, err)
failed = append(failed, check.Name)
} else {
log.Printf("Health check passed: %s", check.Name)
}
}
if len(failed) > 0 {
return fmt.Errorf("health checks failed: %v", failed)
}
log.Println("All health checks passed")
return nil
}),
gocron.WithName("initial-health-check"),
)// Wait for all systems to be ready before marking service as ready
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Waiting for readiness...")
timeout := time.After(60 * time.Second)
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for {
select {
case <-timeout:
return errors.New("readiness timeout exceeded")
case <-ticker.C:
if isSystemReady() {
log.Println("System is ready")
markServiceReady()
return nil
}
log.Println("System not ready yet, waiting...")
}
}
}),
gocron.WithName("wait-readiness"),
)// Load seed data for development/testing
j, _ := s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
// Only run in development
if os.Getenv("ENV") != "development" {
log.Println("Skipping seed data (not in development)")
return nil
}
// Check if already seeded
if isDataSeeded() {
log.Println("Database already seeded")
return nil
}
log.Println("Loading seed data...")
// Create test users
users := createTestUsers(10)
for _, user := range users {
db.Insert("users", user)
}
// Create test products
products := createTestProducts(50)
for _, product := range products {
db.Insert("products", product)
}
// Mark as seeded
markDataSeeded()
log.Println("Seed data loaded successfully")
return nil
}),
gocron.WithName("seed-data"),
)package main
import (
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/go-co-op/gocron/v2"
)
func main() {
s, _ := gocron.NewScheduler()
defer s.Shutdown()
// Step 1: Run migrations (blocking)
s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartImmediately()),
gocron.NewTask(func() error {
log.Println("Running database migrations...")
return runMigrations()
}),
gocron.WithName("db-migration"),
gocron.WithEventListeners(
gocron.AfterJobRunsWithError(func(jobID uuid.UUID, jobName string, err error) {
log.Fatalf("Migration failed: %v", err)
}),
),
)
// Step 2: Load configuration
s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(time.Now().Add(2*time.Second))),
gocron.NewTask(func() error {
log.Println("Loading configuration...")
return loadConfiguration()
}),
gocron.WithName("config-load"),
)
// Step 3: Warm up caches
s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(time.Now().Add(5*time.Second))),
gocron.NewTask(func() error {
log.Println("Warming up caches...")
return warmupCaches()
}),
gocron.WithName("cache-warmup"),
)
// Step 4: Health check
s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(time.Now().Add(10*time.Second))),
gocron.NewTask(func() error {
log.Println("Running health checks...")
return performHealthChecks()
}),
gocron.WithName("health-check"),
)
// Step 5: Mark service ready
s.NewJob(
gocron.OneTimeJob(gocron.OneTimeJobStartDateTime(time.Now().Add(15*time.Second))),
gocron.NewTask(func() error {
log.Println("Service is ready")
markServiceReady()
return nil
}),
gocron.WithName("mark-ready"),
)
// Start scheduler
s.Start()
log.Println("Initialization scheduler started")
// Monitor initialization progress
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
remaining := len(s.Jobs())
if remaining == 0 {
log.Println("All initialization jobs complete")
return
}
log.Printf("%d initialization jobs remaining", remaining)
}
}()
// Wait for interrupt
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
log.Println("Shutting down...")
}Install with Tessl CLI
npx tessl i tessl/golang-github-com-go-co-op-gocron-v2docs
api
examples
guides