quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.54k stars 2.61k forks source link

Add jitter to scheduler #22824

Open joggeli34 opened 2 years ago

joggeli34 commented 2 years ago

Description

It would be nice if the @Scheduled annotation also support setting a jitter.

We have multiple instances running which are doing periodical tasks / checks. Adding a jitter would better distribute the load of the task randomly.

Implementation ideas

It could be added to the @Scheduled annotation similar as it is done in the @Retry annotation.

quarkus-bot[bot] commented 2 years ago

/cc @mkouba

mkouba commented 2 years ago

I must admit that I find the idea interesting. OTOH in most cases the goal of a scheduler method is to execute some logic at a specific time or at a specific interval ;-). Could you be a little more specific about the expected behavior? For example, what exactly should happen if you define @Scheduled(every = "10s", jitter = "1000")? Should it be executed somewhere between interval - 1000ms and interval + 1000ms?

CC @machi1990

joggeli34 commented 2 years ago

For @Scheduled(every = "10s", jitter = "1000") there could be multiple solutions:

  1. At create-time, an interval is fixed with interval +- jitter
  2. For each new interval, the interval is adjusted to interval +- jitter
  3. The interval is used for the base frequency, and each execution is adjusted by a jitter.

I think, 2. would be the simplest to describe. But actually, I don't mind exactly which one is implemented, as the interval time is not that important when someone decides to use a jitter.

mkouba commented 2 years ago
  1. At create-time, an interval is fixed with interval +- jitter

Hm, this one would be probably the easiest to implement correctly, OTOH the option 2. would be more understandable... (and option 3 is functionally equivalent to option 2)

@Scheduled(every = "10s", jitter = "1000")

Alternatively, we could support the jitter directly in the @Scheduled#every(), e.g. via the plus–minus sign: @Scheduled(every = "10s ±1").

machi1990 commented 2 years ago

Alternatively, we could support the jitter directly in the @Scheduled#every(), e.g. via the plus–minus sign: @Scheduled(every = "10s ±1").

Having a jitter argument makes more sense. It is self explanatory.

  1. At create-time, an interval is fixed with interval +- jitter

This means the task will be fixed afterwards during runtime, isn't the whole point of having a jitter to introduce a bit of randomness?

mkouba commented 1 year ago

Note that this could be difficult (if at all possible) to implement in quarkus-quartz.

Felk commented 7 months ago

I encountered this feature request today because we have multiple pods processing a queue of work units, and we want to minimize the chances of them trying to start the same unit of work.
Such a jitter feature is thankfully easily emulated with e.g. this code at the begin of the scheduled method:

private static final Random random = new Random();
// ...
try {
    TimeUnit.MILLISECONDS.sleep(random.nextLong(jitter.toMillis()));
} catch (InterruptedException e) {
    return;
}
mkouba commented 7 months ago

I encountered this feature request today because we have multiple pods processing a queue of work units, and we want to minimize the chances of them trying to start the same unit of work. Such a jitter feature is thankfully easily emulated with e.g. this code at the begin of the scheduled method:

private static final Random random = new Random();
// ...
try {
    TimeUnit.MILLISECONDS.sleep(random.nextLong(jitter.toMillis()));
} catch (InterruptedException e) {
    return;
}

Keep in mind that this blocks the executor thread which is not always desirable...

mkouba commented 7 months ago

I encountered this feature request today because we have multiple pods processing a queue of work units, and we want to minimize the chances of them trying to start the same unit of work.

Maybe a better solution would be to use config properties to slightly adjust the execution time for each pod. For example, something like @Scheduled(cron = "${my.cron.expr:0 15 10 * * ?}") where the my.cron.expr is the config property and 0 15 10 * * ? is the default value - used for pod1. And then for example set ENV variables to adjust the cron expression like MY_CRON_EXPR="0 20 10 * * ?" for pod2 and MY_CRON_EXPR="0 25 10 * * ?" for pod3.