23

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?
  • 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 Apr 27 '16 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? – Phil Hawthorn Apr 27 '16 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 Apr 27 '16 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.) – Phil Hawthorn Apr 28 '16 at 12:57
  • 2
    @PhilHawthorn Think the issue is with the license of the user and no way to switch that. – Keith C Apr 28 '16 at 14:10
16

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);
    }
}
  • 2
    In the Queueable example, you chain immediately and leave it to the platform to add its minimum delay between executions? – Keith C Apr 27 '16 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 Apr 27 '16 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 Apr 27 '16 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 Apr 27 '16 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 Apr 27 '16 at 19:29
16

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.

  • 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 Apr 27 '16 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 Apr 27 '16 at 22:45
  • Because I use a fixed job name plus a time stamp, "Like" on the name is sufficient. – kibitzer Apr 28 '16 at 17:30
  • It was other terms I was wondering about that would separate the "stuck" ones from the valid ones. – Keith C Apr 29 '16 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 May 2 '16 at 20:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.