Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run scheduled task once within Spring Boot app deployed to Azure

In my Spring Boot app I have a scheduled task that runs every minute. It runs some queries to find any notifications that are due, and then sends them by email

@Service
public class EmailSender {

    @Scheduled(cron = "0 * * * * *")
    public void runTask() {
      // send some emails
    }
}

It works fine when I test it locally, but when I deploy it to Azure, 2 copies of each email are sent. This is because in Azure we run (at least) 2 containers, both of which have the scheduling enabled.

I looked for a per-container environment variable, which the scheduler would check, and only run the job if this variable is set to "true", but couldn't find any reliable way to achieve this.

I'm now considering the following instead

  • Removing the @Scheduled annotation
  • Adding a HTTP endpoint (controller method) that exposes the functionality of the task
  • Scheduling this endpoint to be called every minute using Azure Logic Apps (I also considered using WebJobs, but this is not yet supported for App Service on Linux).

This seems like a lot of additional complexity, and am wondering if there's a simpler solution?

like image 345
Antonio Dragos Avatar asked Sep 06 '25 00:09

Antonio Dragos


1 Answers

Spring, by default, cannot handle scheduler synchronization over multiple instances – it executes the jobs simultaneously on every node instead. So there are two possible solutions:

Using an external library

You can use Spring ShedLock to handle this though. Here there is a guide you can use: Spring ShedLock. You can find all the code need to implement this lock there.

It works by using a parameter in a database, that gives the information to the other nodes about the execution status of that task. In this way ShedLock prevents your scheduled tasks from being executed more than once at the same time. If a task is being executed on one node, it acquires a lock which prevents the execution of the same task from another node.

You can find more info in their Github Page about how to handle it with different databases.

Deploying the active schedulers in a different instance

You can create two different profiles, for example, one is prod and the other is cron. Then you can put the annotation @Profile("cron") on scheduler classes, so that they will be ran only on the second profile.

Then you have to build the application twice:

  1. The first time you have to build it with -Dspring.profiles.active=prod and you deploy it normally.
  2. The second time you build it with -Dspring.profiles.active=prod,cron, and you have to deploy this in an environment that is not autoscaling so that you can be sure there will be only one instance of this.
like image 101
robertobatts Avatar answered Sep 10 '25 03:09

robertobatts