Scheduler
The schedule package provides a lightweight cron-style task scheduler. Register periodic tasks that run automatically in the background when your app starts.
Overview
The scheduler is available on app.Scheduler. Tasks registered by plugins (via HasSchedule) or directly in your app are started automatically when app.Run() launches the server.
Registering tasks
import (
"context"
"time"
"github.com/CodeSyncr/nimbus/schedule"
)
// In a plugin (HasSchedule)
func (p *Plugin) Schedule(s *schedule.Scheduler) {
s.Every(5*time.Minute, "sync-data", func(ctx context.Context) error {
return p.syncData(ctx)
})
s.Daily("03:00", "nightly-cleanup", func(ctx context.Context) error {
return p.cleanup(ctx)
})
}
// Or directly via the app
app.Scheduler.Every(30*time.Second, "heartbeat", func(ctx context.Context) error {
return pingExternalService(ctx)
})
Convenience methods
| Method | Interval | Example |
|---|---|---|
Every(duration, name, fn) | Custom interval | Every(10*time.Second, ...) |
EveryMinute(name, fn) | 1 minute | Health pings |
EveryFiveMinutes(name, fn) | 5 minutes | Cache cleanup |
Hourly(name, fn) | 1 hour | Metrics flush |
Daily(at, name, fn) | 24 hours at specific time | Daily("03:00", ...) |
Daily tasks with specific times
Use Daily() with an "HH:MM" string in local timezone:
// Run at 3:00 AM every day
s.Daily("03:00", "send-reports", func(ctx context.Context) error {
return reportService.SendDailyDigest(ctx)
})
// Run at midnight
s.Daily("00:00", "rotate-logs", func(ctx context.Context) error {
return logManager.Rotate(ctx)
})
How it works
- Each task runs in its own goroutine with a
time.Ticker. - Tasks include panic recovery — a panicking task won't crash the app.
- Errors are logged via
log.Printf. - The scheduler starts automatically in
app.Run()if any tasks are registered. - On shutdown (
SIGINT/SIGTERM), the scheduler is stopped gracefully via context cancellation.
Standalone usage
You can also use the scheduler outside of the app lifecycle:
s := schedule.New()
s.EveryMinute("ping", pingHandler)
s.Hourly("metrics", metricsHandler)
s.Start(ctx) // non-blocking, runs in background
defer s.Stop() // graceful shutdown
Multi-instance safety (distributed lock)
When running multiple app instances, configure a locker so each scheduled tick runs on only one node:
import (
"github.com/CodeSyncr/nimbus/schedule"
"github.com/CodeSyncr/nimbus/redis"
)
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
app.Scheduler.WithLocker(schedule.NewRedisLocker(rdb))
Plugin integration
Plugins implement HasSchedule to register tasks that start automatically:
type AnalyticsPlugin struct { nimbus.BasePlugin }
func (p *AnalyticsPlugin) Schedule(s *schedule.Scheduler) {
s.Every(5*time.Minute, "analytics-flush", p.flushMetrics)
s.Daily("02:00", "analytics-aggregate", p.aggregateDaily)
}
Real-Life Example: Database Cleanup
func RegisterSchedule(s *scheduler.Scheduler) {
// Clean expired sessions every hour
s.EveryHour(func(ctx context.Context) error {
result := db.Where("expires_at < ?", time.Now()).Delete(&Session{})
logger.Info("cleaned expired sessions", "deleted", result.RowsAffected)
return result.Error
})
// Remove soft-deleted records older than 30 days
s.Daily(func(ctx context.Context) error {
cutoff := time.Now().AddDate(0, 0, -30)
db.Unscoped().Where("deleted_at < ?", cutoff).Delete(&Order{})
db.Unscoped().Where("deleted_at < ?", cutoff).Delete(&User{})
return nil
})
}
Real-Life Example: Metrics & Reporting
func RegisterSchedule(s *scheduler.Scheduler) {
// Aggregate hourly metrics
s.EveryHour(func(ctx context.Context) error {
stats := metrics.ReadRuntimeStats()
return db.Create(&MetricSnapshot{
Goroutines: stats.Goroutines,
HeapAlloc: stats.HeapAlloc,
Timestamp: time.Now(),
}).Error
})
// Daily revenue report
s.Daily(func(ctx context.Context) error {
var total float64
db.Model(&Order{}).
Where("created_at >= ?", time.Now().AddDate(0, 0, -1)).
Select("COALESCE(SUM(total), 0)").Scan(&total)
return queue.Dispatch(&jobs.SendDailyReport{
Date: time.Now().AddDate(0, 0, -1),
Revenue: total,
}).Dispatch(ctx)
})
}
Production Deployment
Run the scheduler as a separate process from your web server:
Systemd
[Unit]
Description=Nimbus Scheduler
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp schedule:run
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Docker
# Run scheduler in a separate container
CMD ["./myapp", "schedule:run"]
Best Practices
- Keep tasks idempotent — Running twice should be safe
- Log task execution — Use
logger.Infofor visibility - Use queues for heavy work — Schedule should dispatch jobs, not run them inline
- Run in a separate process — Don't block your web server
- Use appropriate intervals — Don't schedule heavy work every minute