Scheduled Tasks

The Task Scheduler allows you to create cron like entries that will create Tasks on demand.

This requires a separate process to be run that will supervise the configured schedules and create the tasks. We have such a Scheduler built into the ajc binary deployable in any container manager.

The scheduler we provide support being deployed in a highly-available cluster, they will perform leader election with one of the cluster scheduling tasks. There is no need to restart or signal these schedulers as tasks are added, removed or updated.

You can Deploy to Kubernetes using our Helm Charts, or run anywhere else as per below guide.

Schedule Overview

A Scheduled task takes a Cron like Schedule and some Task related properties and will then create new jobs on demand using those properties as templates.

// ScheduledTask represents a cron like schedule and task properties that will
// result in regular new tasks to be created machine schedule
type ScheduledTask struct {
	// Name is a unique name for the scheduled task
	Name string `json:"name"`
	// Schedule is a cron specification for the schedule
	Schedule string `json:"schedule"`
	// Queue is the name of a queue to enqueue the task into
	Queue string `json:"queue"`
	// TaskType is the type of task to create
	TaskType string `json:"task_type"`
	// Payload is the task payload for the enqueued tasks
	Payload []byte `json:"payload"`
	// Deadline is the time after scheduling that the deadline would be
	Deadline time.Duration `json:"deadline,omitempty"`
	// MaxTries is how many times the created task could be tried
	MaxTries int `json:"max_tries"`
	// CreatedAt is when the schedule was created
	CreatedAt time.Time `json:"created_at"`
}

Valid schedules are like those for the common unix utility cron, including the syntaxes like */5 and so forth. Short cuts for @yearly, @monthly, @weekly, @daily and @hourly are supported in addition to @every 10m where 10m is a Go standard duration.

Go API

Adding and loading Scheduled Tasks is a lot like a normal task:

client, _ := aj.NewClient(asyncjobs.NatsContext("AJC"))

// The deadline being an hour from now will result in a Schedule Task with a 1 hour deadline set
task, _ := aj.NewTask("email:monthly", nil, aj.TaskDeadlin(time.Now().Add(time.Hour)))

// Create the schedule
err := client.NewScheduledTask("EMAIL_MONTHLY_UPDATE", "@monthly", "EMAIL", task)

// Load it
st, _ := client.LoadScheduledTaskByName("EMAIL_MONTHLY_UPDATE")

// Remove it
err = client.RemoveScheduledTask("EMAIL_MONTHLY_UPDATE")

CLI management

Below a quick overview of the CLI, the CLI is brand new so some aspects might change.

Adding and Removing Scheduled Tasks

$ ajc task cron add EMAIL_MONTHLY_UPDATE "0 0 1 * *" email:monthly --queue EMAIL --deadline 12h
Scheduled Task EMAIL_MONTHLY_UPDATE created at 17 Feb 22 17:40:37 UTC

             Schedule: 0 0 1 * *
                Queue: EMAIL
            Task Type: email:monthly
              Payload: 0 B
  Scheduling Deadline: 12h0m0s

We add a scheduled task that will run monthly (Could also use monthly instead of cron format), it will create a task with type email:monthly in the EMAIL queue and we set a deadline on the task 12 hours after creation.

To enter the schedule @yearly use just “yearly” as the CLI package will interpret the @.

Given the name, you can remove it again with ajc task cron delete EMAIL_MONTHLY_UPDATE, it supports -f for non interactive.

Viewing schedules

A list of schedules can be loaded:

$ ajc task cron list
╭───────────────────────────────────────────────────────────────────────────────────╮
│                                1 Scheduled Task(s)                                │
├──────────────────────┬───────────┬───────┬───────────────┬────────────────────────┤
│ Name                 │ Schedule  │ Queue │ Task Type     │ Created                │
├──────────────────────┼───────────┼───────┼───────────────┼────────────────────────┤
│ EMAIL_MONTHLY_UPDATE │ 0 0 1 * * │ EMAIL │ email:monthly │ 17 Feb 22 17:40:37 UTC │
╰──────────────────────┴───────────┴───────┴───────────────┴────────────────────────╯
$ ajc task cron list --names
EMAIL_MONTHLY_UPDATE

A single schedule can be viewed:

$ ajc task cron view EMAIL_MONTHLY_UPDATE
Scheduled Task EMAIL_MONTHLY_UPDATE created at 17 Feb 22 17:40:37 UTC

             Schedule: 0 0 1 * *
                Queue: EMAIL
            Task Type: email:monthly
              Payload: 0 B
  Scheduling Deadline: 12h0m0s
$ ajc task cron viww EMAIL_MONTHLY_UPDATE --json
{
  "name": "EMAIL_MONTHLY_UPDATE",
  "schedule": "0 0 1 * *",
  "queue": "EMAIL",
  "task_type": "email:monthly",
  "payload": null,
  "Deadline": 43200000000000,
  "MaxTries": 0,
  "created_at": "2022-02-17T17:40:37.001480991Z"
}

Running the Scheduler

You can Deploy to Kubernetes using our Helm Charts, or run anywhere else as per below guide.

Each scheduler needs an, ideally unique, name. This will be used in some logs and in leader elections.

$ ajc task cron scheduler $(hostname -f) --monitor 8080 --context AJC
INFO[19:21:29] Starting leader election as dev1.example.net
INFO[19:21:29] Registered a new item EMAIL_MONTHLY_UPDATE on queue EMAIL: 0 0 1 * *
INFO[19:21:29] Loaded 1 scheduled task(s)
INFO[19:21:42] Became leader, tasks will be scheduled

As Scheduled jobs are added or removed logs will be logged and updates about scheduling tasks will be shown. Prometheus metrics is on port 8080 in /metrics.

You can run as many schedulers as you want, they will do leader elections, all will log Schedule updates but only one will create Tasks. NOTE there’s a chance that during a leadership change some tasks will not be scheduled, this is something we will resolve later.

When running ajc info will have some details:

$ ajc info
...
Leader Elections:

         Entries: 1 @ 139 B
    Memory Based: false
        Replicas: 1
       Elections:
                  task_scheduler: dev1.example.net
...

Here we see the current leader of the task_scheduler election group is the instance above.