Blog

Replacing Heroku Scheduler with Custom Clock dyno

Short intro to the custom clock process on Heroku for greater control and scheduling visibility.

Heroku Chron Scheduler
Development5 min read
Auhor image
MarinBozinovicMarin Bozinovic29-11-2023
29-11-2023
HerokuCRON SchedulerSoftware Development
Heroku
CRON Scheduler
Software Development

Heroku Scheduler... Good, but limited

Heroku Scheduler, a free Heroku add-on is a great and easy starting point for introducing CRON and scheduling to your app. Setting it up is a two-step process in which you select your script and the interval for it to run. After reading official Heroku documentation for the add-on, however, you are quickly faced with two major red flags.

Limitations

First, less impactful and easily avoidable, are predefined intervals that you can pick for running your script. Currently, Heroku allows you to run your job every 10 minutes, hourly with a 10-minute scale or daily with a 30-minute scale. If you need anything but that, you’ll need to handle it manually in your script.

The second issue that Heroku Scheduler has is reliability. Heroku doesn’t guarantee that Scheduler will run the job it is supposed to, or that it will run the job only once. That is a huge issue when scheduled jobs are critical for the application you are working on.

In this blog, we’ll go through a simple process of setting your own CRON and ensuring that scheduled work runs correctly.

Requirements and prerequisites

Creating reliable scheduling on the Heroku requires a few things:

  • clock for sending periodic messages
  • worker for receiving messages and doing all the work
  • messaging queue for connecting them

We’ll show basic clock and worker dynos written in NodeJS. Seeing how messaging queue isn't the topic of this blog, we'll just assume that we have a singleton class that exposes two functions: one for creating a receiver for a specific channel, and the other for sending messages over a specific channel.

heroku.svg

Starting with the CRON and clock

Clock dyno is a specific type of Heroku dyno whose sole purpose is sending periodic messages. We’ll add a clock.js script with the following content.

var CronJob = require('cron').CronJobM;
var MQ = require('../mq.js');


var queue = MQ.initQueue();

var job = new CronJob({
    cronTime: '0 */10 * * * *',
    onTick: () => {
        queue.sendMessage('PIXION', 'Hello World!')
    },
});

job.start();

The code above is a simple clock example that writes the message Hello World on channel Pixion. With CRON all set up, we can move on to the worker dyno and create a handler for receiving CRON messages.

Setting up the the Worker

We’ll add worker.js script and a simple handler for CRON messages.

var MQ = require('../mq.js');

var queue = MQ.initQueue();

queue.addReceiver('PIXION', (message) => {
    console.info('Clock said:', message);
});

With this, we have everything we need for a custom scheduler on Heroku. All that is left to do, is set Heroku dynos.

Letting Heroku do its magic

To set Heroku, we will add next lines to the Procfile.

clock: node clock.js
worker: node worker.js

And the last thing left to do is make sure that there is only one instance of clock dyno. We can do that with CLI command heroku ps:scale clock=1, or through Heroku's web interface by going to the app's resource tab and selecting the correct scaling option. You can have as many worker dynos as you wish, just make sure that only one dyno consumes the message sent by the clock.

Fixed but not fixed

To conclude this blog, I want to make sure that you understand that this solution is a basic one and that it doesn't assure execution of your background jobs. Its sole purpose is to transfer scheduling responsibility from Heroku to you and to give you more flexibility. It still leaves doors open for one big issue, and that is downtime. We can (somewhat) solve this issue with persisting jobs, but I am getting ahead of myself. That is a topic for next time.