Redis Gears cron/ scheduler

Greetings,
I am loving Redis Gears the last few weeks.
I wanted to share few gears I just made and ask for your opinion about them.

The goal: To have gears trigger periodically in a cluster, but without external scripts involved.

First gear runs each second and puts the current timestamp in a key on each shard (in UNBLOCKING mode)

import time
def generator():
    while True:
        time.sleep(1)
        execute('set','time{'+hashtag()+'}', time.time())
        yield 1

gb = GB('PythonReader')
gb.count()
gb.run(generator)

Second script subscribes to those key changes and will be used to run cron jobs on local shard keys

GearsBuilder(‘KeysReader’).register(prefix=‘time{’+hashtag()+"}", noScan=True, eventTypes=None,
keyTypes=None, mode=“async_local”, readValue=True)

These two work fine when tested for few minutes. But I am not sure if this is a proper way of doing this and if these gears wont conflict with internal logic like timeouts. Also I don’t know what will happen on crashes and restarts. Or if they will slowly run out of memory.

The reason of not wanting external scripts to periodically run batch commands - I don’t want to introduce another point of failure.

Hey @infu,

We actually have this future planed to be supported build-in inside RedisGears in future versions, so stay tuned. Until then I have a few comments and a suggestion for another approach.

The issue I see with what you did is that you basically have a forever running execution (the first execution that writes to the key never finished). This implies that you consume a thread from the thread pool forever and you are limited by the number of threads in the thread pool (https://oss.redislabs.com/redisgears/configuration.html#executionthreads). In addition, your approach will not survive restart because the execution will not restart after the server restart.

Here is my suggestion, You create a registration that registers on key expiration. Inside the OnRegister callback, you set the key with whatever expiration you want, when calculation finished you re-add the key with whatever expiration you want (to basically reschedule the job):

JOB_INTERVAL = 5

def SetExpireKey():
	execute('set', 'jobkey{%s}' % hashtag(), 'val', 'EX', str(JOB_INTERVAL))

GB().\
foreach(lambda x: log('doing some job')).\
foreach(lambda x: SetExpireKey()).\
register(prefix='jobkey*', eventTypes=['expired'], readValue=False, mode='async_local', onRegistered=SetExpireKey)

Why is it better?

  1. You will consume a thread from the thread pool only when the job is running and not forever
  2. You will survive restarts, the OnRegister callback will be called on each shard on the RDB/AOF load phase so this will basically reschedule your job.

Please notice that there is one disadvantage here, Redis has 2 types of mechanisms for keys expiration, active expire, and passive expire (to read more about it look here https://redis.io/commands/expire). Basically what it means is that you might get the expire event in some delay and this delay is affected by the total number of expired keys and the amount of effort Redis is configured to do active expire. I do believe though that for cron jobs this solution is good enough.

Hope everything is clear, let me know what you think.

2 Likes

Thanks for the quick response.
Very nice solution to use, while waiting for the new feature, exactly what I needed.
Thanks a lot.

1 Like