From b6bd7a8076dd65217db53ecfd216b246692015db Mon Sep 17 00:00:00 2001 From: Yevgeniy Miretskiy Date: Thu, 10 Feb 2022 10:01:22 -0500 Subject: [PATCH] jobs: Add `every_hour` and `every_day` extentions to the cron expression. Cron expressions used by scheduled jobs support extensions such as `@hourly` or `@daily`. These cron expressions execute cron schedule on top of every hour or at midnight respectively. Spreading out cron schedules execution times might be beneficial. Even though, ultimately, the responsiblity of not overloading the system falls on the system itself (e.g. jobs system slowing down execution, or perhaps an admission control type system), having an easy way to spread out the schedule execution times might be beneficial. This PR adds support to two extension to the crontab mnemonics: `@every_hour` which executes the schedule every hour on a randmly chosen minute, and `@every_day` which executes the schedule every day at a randomly chosen time. Release Notes (enterprise change): Extend scheduled jobs to support cront tab expression extension to specify job execution at random minute every hour (`@every_hour`), or at a random time every day (`@every_day`). --- pkg/jobs/scheduled_job.go | 20 ++++++++++++++++++-- pkg/jobs/scheduled_job_test.go | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pkg/jobs/scheduled_job.go b/pkg/jobs/scheduled_job.go index cf6a520ffc3a..1bceea0c77fd 100644 --- a/pkg/jobs/scheduled_job.go +++ b/pkg/jobs/scheduled_job.go @@ -13,6 +13,7 @@ package jobs import ( "context" "fmt" + "math/rand" "reflect" "strings" "time" @@ -188,13 +189,28 @@ func (j *ScheduledJob) HasRecurringSchedule() bool { return len(j.rec.ScheduleExpr) > 0 } +// parseCronExpr parses cron expression, with added support for +// cockroach specific mnemonics. +func parseCronExpr(expr string) (cron.Schedule, error) { + switch strings.ToLower(expr) { + case "@every_hour": + // Schedule runs once an hour at a randomly chosen minute. + return cron.ParseStandard(fmt.Sprintf("%d * * * *", rand.Intn(60))) + case "@every_day": + // Schedule runs once a day at a randomly chosen time. + return cron.ParseStandard(fmt.Sprintf("%d %d * * *", rand.Intn(60), rand.Intn(24))) + default: + return cron.ParseStandard(expr) + } +} + // Frequency returns how often this schedule executes. func (j *ScheduledJob) Frequency() (time.Duration, error) { if !j.HasRecurringSchedule() { return 0, errors.Newf( "schedule %d is not periodic", j.rec.ScheduleID) } - expr, err := cron.ParseStandard(j.rec.ScheduleExpr) + expr, err := parseCronExpr(j.rec.ScheduleExpr) if err != nil { return 0, errors.Wrapf(err, "parsing schedule expression: %q; it must be a valid cron expression", @@ -212,7 +228,7 @@ func (j *ScheduledJob) ScheduleNextRun() error { return errors.Newf( "cannot set next run for schedule %d (empty schedule)", j.rec.ScheduleID) } - expr, err := cron.ParseStandard(j.rec.ScheduleExpr) + expr, err := parseCronExpr(j.rec.ScheduleExpr) if err != nil { return errors.Wrapf(err, "parsing schedule expression: %q", j.rec.ScheduleExpr) } diff --git a/pkg/jobs/scheduled_job_test.go b/pkg/jobs/scheduled_job_test.go index 4b9dab4181d4..17ef0e2f9bc0 100644 --- a/pkg/jobs/scheduled_job_test.go +++ b/pkg/jobs/scheduled_job_test.go @@ -18,6 +18,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/util/leaktest" "github.com/cockroachdb/cockroach/pkg/util/log" "github.com/cockroachdb/cockroach/pkg/util/timeutil" + "github.com/robfig/cron/v3" "github.com/stretchr/testify/require" ) @@ -117,3 +118,22 @@ func TestPauseUnpauseJob(t *testing.T) { require.False(t, loaded.IsPaused()) require.False(t, loaded.NextRun().Equal(time.Time{})) } + +func TestCrdbCronExtensions(t *testing.T) { + defer leaktest.AfterTest(t)() + defer log.Scope(t).Close(t) + + frequency := func(s cron.Schedule) time.Duration { + notALeapYear := time.Date(2022, 1, 2, 3, 4, 5, 6, time.UTC) + nextRun := s.Next(notALeapYear) + return s.Next(nextRun).Sub(nextRun) + } + + schedule, err := parseCronExpr("@every_hour") + require.NoError(t, err) + require.Equal(t, time.Hour, frequency(schedule)) + + schedule, err = parseCronExpr("@every_day") + require.NoError(t, err) + require.Equal(t, 24*time.Hour, frequency(schedule)) +}