25

From a naive read of these questions and answers:

and given that the "Maximum number of Apex classes scheduled concurrently" limit is now 100, setting up 12 scheduled jobs 5 minutes apart seems like the simple (and durable - see the discussion in Scheduled batch jobs and durability) way of achieving this.

Are there any downsides to this approach given the platform as it is in 2016? Is there a compelling alternative?

(This is not an academic question - looks like we are going to have to do this to work-around a problem so looking for advice. The problem is that some getContentasPdf pages fail to render images stored in Attachments when executed in asynchronous code kicked off from a site. As this processing is asynchronous anyway, looking to execute it in the non-site context where it is known to work via this scheduled code and avoid future context-related surprises.)

PS

Just scanned the "Going Asynchronous" chapter of Dan Appleman's excellent Advanced Apex Programming – 3rd edition and read:

... native chaining support built into queueable Apex, there is no longer a need to use scheduled Apex in this manner ...

but I'm not entirely clear on which of the patterns he describes would fit this case...

PPS

So 2 approaches are proposed in the answers but I still have these questions about them:

  1. "suicide scheduling" but with the advice to also schedule say every hour: what is the best way to ensure that multiple "suicide schedules" don't get created?
  2. my trivial test of enqueueing at the end of a Queueable ran so fast that a significant part of the asynchronous Apex method executions governor limit might be consumed; is anyone using this approach successfully today?
11
  • 2
    I would think the main downside is that it can degrade system performance. It uses up a lot of system resources in a multi-tenant environment. You might even face repercussions from Salesforce itself if your usage is too heavy.
    – Adrian Larson
    Commented Apr 27, 2016 at 18:09
  • 2
    I've found 15 minute gaps workable, but always seem to run into trouble with 5 mins. Might some detail on the use case provide some inspiration from someone/somewhere? Commented Apr 27, 2016 at 18:15
  • 1
    @PhilHawthorn Dan Appleman mentions that the platform now enforces a minimum time between executions: wonder if that is what you have seen.
    – Keith C
    Commented Apr 27, 2016 at 18:26
  • 1
    Possibly so, i've come unstuck before trying to beat the platform - with limited time to get to the root cause, i revert to 15 mins which is generally acceptable for my use cases (updating balances etc.) Commented Apr 28, 2016 at 12:57
  • 2
    @PhilHawthorn Think the issue is with the license of the user and no way to switch that.
    – Keith C
    Commented Apr 28, 2016 at 14:10

3 Answers 3

18

The main downside to scheduling 12 identical events is that if one gets lagged behind for some reason, the next one will run in less than five minutes. This may or may not be desirable. Particularly during maintenance, you may have all 12 jobs trying to execute immediately upon the system coming back from maintenance. Also, if you're running an organization that already has some 90'ish scheduled jobs already (including daily, weekly, and monthly jobs), using 12 slots is undesirable.

I personally prefer using "suicide scheduling" for a granularity of less than an hour, because it uses fewer resources and is still reliable. With suicide scheduling, your job purposefully does something, aborts itself, and schedules itself X minutes in the future with an hourly schedule fallback. This way, if it fails to reschedule because of blown governor limits or an unhandled exception, it'll run in an hour, otherwise it'll run in X minutes, and all you need is a single job.

Here's what it generally looks like:

public class SuicideScheduler implements Schedulable {
    public void execute(SchedulableContext context) {
        System.abortJob(context.getTriggerId());
        System.schedule('SuicideScheduler', '0 '+DateTime.now().addMinutes(5).minute()+' */1 ? * *', this);
        // Do your payload now
    }
}

However, you could also avoid this problem entirely by just scheduling yourself hourly and using a Queuable/Batchable class that the scheduler kicks off:

public class QueuedScheduler implements Schedulable, Queueable {
    public void execute(SchedulableContext context) {
        System.enqueueJob(this);
    }
    public void execute(QueueableContext context) {
        // Do your work here
        // 
        // If you need to chain, then you can...
        System.enqueueJob(this);
    }
}
6
  • 2
    In the Queueable example, you chain immediately and leave it to the platform to add its minimum delay between executions?
    – Keith C
    Commented Apr 27, 2016 at 18:31
  • 1
    Yes. Queueable is supposed to use a exponential delay up to a minute between executions. This is a more resource friendly than having a ton of dead jobs laying around.
    – sfdcfox
    Commented Apr 27, 2016 at 18:35
  • Also for the Queueable case, I assume the point of scheduling hourly is to restart in case the chain has been broken by some kind of failure. But without some kind of guarding, what stops multiple chains being created?
    – Keith C
    Commented Apr 27, 2016 at 19:00
  • 1
    @KeithC I didn't include any guards specifically, but yes, you'd want to either abort the current chain or avoid creating a new chain. For example, you might do this in your scheduled execute context: if([SELECT COUNT() FROM AsyncApexJob WHERE ApexClass.Name = 'QueuedScheduler' AND JobType = 'Queueable' AND Status IN ('Holding','Queued','Preparing','Processing')] == 0) { System.enqueueJob(this);} Of course, governor limits keeps things from going to crazy, but well guarded code shouldn't have any problems.
    – sfdcfox
    Commented Apr 27, 2016 at 19:12
  • In a quick test of the Queueable chaining where each execute added a small attachment, the attachments were a couple of seconds apart and in a dev org the chain dies with "Maximum stack depth has been reached." presumably because of the 5 limit described at the end of the Queueable Apex. Seems rather aggressive and perhaps hard to get working and test in a non-production org? Do you have this sort chaining working in production today?
    – Keith C
    Commented Apr 27, 2016 at 19:29
17

The "suicide scheduling" approach is a correct one, with one caveat. Change the name of the scheduler each time (I add a timestamp), then periodically run a query using a "Like" term to delete any of the scheduled jobs that got "stuck".

On rare occasions a scheduled job will get stuck in such a way that even after aborting it, you won't be able to start a new one with the same name - so the suicide becomes permanent. Varying the names resolves this issue.

5
  • Absolutely. I'd forgotten about this. It tends to happen especially when you refresh a sandbox, you'll end up with ghost jobs that require support to clear some background table.
    – sfdcfox
    Commented Apr 27, 2016 at 18:55
  • What would the typical query to delete the "stuck" ones look like i.e. what other terms (in addition to the 'like" for the name) do you suggest?
    – Keith C
    Commented Apr 27, 2016 at 22:45
  • Because I use a fixed job name plus a time stamp, "Like" on the name is sufficient.
    – kibitzer
    Commented Apr 28, 2016 at 17:30
  • It was other terms I was wondering about that would separate the "stuck" ones from the valid ones.
    – Keith C
    Commented Apr 29, 2016 at 7:58
  • It's been a while, but if I recall correctly it was a case of an AsyncApexJob not being deleted when the associated CronTrigger object was deleted (or vice versa).
    – kibitzer
    Commented May 2, 2016 at 20:55
1

in spring '23, Queueable now has a 'retryInMinutes' override

https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_enqueueJob_delayQueueable.htm&release=242&type=5

so for certain use cases this is very clean ie: Queueable does the first callout, and you can easily retry 'n' times after 'n' minutes.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .