Understanding TimerTriggers in Azure Functions

Azure Functions are Microsoft’s way of offering serverless compute.

In essence, Azure Functions are just source-code functions, running on PaaS servers, which are triggered by some external mechanism. You just deploy functions and do not care about the infrastructure underneath it.

Multiple programming languages are supported e.g. C#, Javascript, Java to write your function in.

Multiple kinds of triggers are available. Most of them are related to some event in another Azure resource. For example, adding a blob in Azure Blob Storage (a BlobTrigger) or receiving a message in an Azure Event Hub can trigger the function (a EventHubTrigger).

A function can also expose an external HTTP endpoint. Then a Rest call on that endpoint triggers the function (HttpTrigger).

All these triggers are scalable. The more triggers are fired on the Azure Function, the more functions are executed. If you choose for a consumption plan this can even result in a scale-out on the number of servers (which you do not have configured).

Azure Functions also offers a TimerTrigger. Functions are just triggered by a … timer.

This seems simple but the Timer trigger behaves a little bit differently when executed.

Let’s try understanding the Timer Trigger.

Why using a timer-triggered function?

The biggest advantage is controlling speed. Because the trigger is not reactive on an external event, you control the speed in which tasks are executed.

In a recent project, we had some dependencies with external services that could throttle our calls to them. With a timer trigger, we were able to speed up and slow down the calls towards that service.

Deploying a function

For this experiment, I created an Azure Function App (the ‘logical container’ of multiple functions) running on an Application Service Plan. Compared to a consumption plan, this makes the behavior more clear. I also added Application insights. to it.

So this is the function we experiment with:

public static class Function
{
    [FunctionName("Function")]
    public static void Run([TimerTrigger("*/5 * * * * *")] TimerInfo myTimer, ILogger log)
    {
        log.LogInformation($"Timer trigger executed at: {DateTime.Now}");

        var text = string.Empty;

        for (int i = 0; i < 4000; i++)
        {
            text = text + "-" + DateTime.Now;
        }

        if (text.Length < 10)
        {
            log.LogInformation($"dummy text");
        }

        log.LogInformation($"Function processed message with length: {text.Length}");
    }
}

The load of this function is just some dummy stuff. It’s just burning CPU time in the For loop, running 4000 times so we can simulate actual work. This will help us interpreting the charts.

The timer is set to running every five seconds.

This results in this behavior, as seen in Application Insights:

These Live Metrics are showing several interesting charts:

  1. The function is triggered every five seconds. One minute shows twelve executions.
  2. Each function runs for approx. two seconds. This is due to the 8000 cycles.
  3. Due to the cycles, the function takes up to 100% op CPU during execution.
  4. The function runs on one server. The server itself has a total load of 32 percent of CPU usage.

This is as expected. Let’s play with the timer interval setting.

NCRONTAB expressions

The timer has a interval setting based on the NCronTab notation. This is a dialect on the popular CRON notation as seen in Linux. I adds a SECOND notation:

{second} {minute} {hour} {day} {month} {day-of-week}

I urge you to look into the notation. A few examples are explaned here.

Executing a trigger once every hour on the fifth minute (and zero seconds), looks like this:

0 5 * * * *

Executing a trigger every minute on the fifth second of each minute looks like this:

5 * * * * *

So the logic behind these CRON notations is only executed once an hour or a minute.

But in our example, we run it multiple times a minute. For example, to run it every ten seconds a minute, we use a notation like:

*/10 * * * * *

We ‘divide’ the minute in parts of ten seconds.

This is equal to this notation:

0,10,20,30,40,50 * * * * *

This looks like this:

The function is now executed every ten seconds with an average execution time of just less than two seconds.

Now, just to illustrate, we can also do:

15/5 * * * * *

We now trigger the function every five seconds. But, we skip the first fifteen seconds of each minute:

Notice the gap in the execution sequence.

Play with the timer CRON setting to find the right execution time for your purpose.

What if the execution time exceeds the interval?

Let’s look at another scenario where the execution time exceeds the interval time

For this, I set the timer to trigger every five seconds and the For loop now runs 10.000 times:

Once deployed, we see this pattern:

This is interesting!

The execution time is now approx. 15 seconds.

The interval drops to four-five triggers per minute instead of the expected twelve times as configured (every five seconds configured).

Because the duration of executing the function exceeds the timer interval, the function ignored timer triggers during the execution of the previous timer-triggered function.

Scaling up: more functions on the same server

A Function App can run multiple services, including multiple timer-triggered functions.

I demonstrate this with two functions on the same Function App:

This second function is just a copy of the previous function with the same settings.

These functions are set to run every five seconds with a load of a few seconds. This should result in around 24 calls a minute:

What we see is quite a nice distribution of execution over time:

  1. It looks like the two functions are executed in pairs.
  2. For some reason, the execution time differs in two different time intervals…
  3. The CPU usage is still ok.
  4. The CPU Total doubles. This is on par with the predicted load (we added a second function).

But what happens if the execution time exceeds the timer interval timespan?

I change the compute loop length of the first function into 10000 which is ~15 seconds:

Now, the behavior has become unpredictable!

There are large holes in the execution intervals and almost no function has a duration of just a few seconds,

So, the performance of the second function drops dramatically due to the slow performance of the first function (exceeding the CRON setting).

I tried to fix this by changing the CRON setting of the first function to twenty seconds:

This improves the overall performance of the second function. We see the three executions of the slow function and the execution of the second function. It is in fact complete with 12 + 3 = 15 executions.

Due to the Singleton behavior of the timer, the timer-triggered functions are reacting to each other.

Running a second timer-triggered function seems OK but you have to keep an eye on the execution time.

Scaling out: more servers

Because we are running on an Application Service Plan, we can also scale-out.

According to Microsoft:

If a function app scales out to multiple instances, only a single instance of a timer-triggered function is run across all instances.

This means that the timer-triggered function will only run on one instance even when multiple servers are instantiated.

I demonstrate this with a single function in the Function App, scaled out to four servers. The function is set to run every three seconds and has a load for about 1 second:

We see four servers created and running:

  1. The function is indeed executed every three seconds.
  2. The execution time for each function is around one second.
  3. The CPU load shows the overall CPU usage for all instances. We started with one instance. It drops once the other three instances are created.
  4. There is only one function working (at 37 percent CPU usage). The other three services are doing nothing.

So, timer-triggered functions do not scale well 🙂

You can try to scale up (more functions on a single server or a bigger sized single server) but adding more servers does not do anything.

If you want to scale out, just add multiple Application Service Plans, each with its isolated trigger.

Conclusion

We are now ready to use the Azure Function timer trigger with these simple rules:

  • Functions exceeding the timer interval while executing logic have a serious impact on performance. Keep an eye on the total execution duration.
  • Scaling-up work (more function in the same Function App) for you if you keep the previous rule in mind. Moving over to a larger sized server can improve the process too if this results in shorter execution times.
  • Scaling out to multiple servers has zero effect on the overall performance. The other servers are not touched by the timer-triggered function.

The timer-triggered function acts as a singleton. With the right understanding, it is a helpful addition to your Azure toolkit.

Een gedachte over “Understanding TimerTriggers in Azure Functions

Geef een reactie

Vul je gegevens in of klik op een icoon om in te loggen.

WordPress.com logo

Je reageert onder je WordPress.com account. Log uit /  Bijwerken )

Google photo

Je reageert onder je Google account. Log uit /  Bijwerken )

Twitter-afbeelding

Je reageert onder je Twitter account. Log uit /  Bijwerken )

Facebook foto

Je reageert onder je Facebook account. Log uit /  Bijwerken )

Verbinden met %s