How to handle code synchronization when running app on multiple docker instances (Spring JPA @Lock, Java synchronized, scheduled jobs, ...)?

Viewed 560

Context

We have a Spring boot application (an API used by an angular frontend).

It is running on a docker container. It is using a single instance of a PostgreSQL database.

Our application had some load problems so we asked us to scale it. We told us to run our API on several docker containers for that.

We have several questions / problems dealing with code synchronization over multiple docker instances executing our code.

Problem 1

We have some @Scheduled jobs integrated and deployed with our API code.

We don't want these scheduled jobs to be executed by all container instances, but only one.

I think we can simply handle this by disabling jobs on other containers through environment variables with the "-" value to disable the Spring scheduled cron.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html#CRON_DISABLED

Does it sounds right?

Problem 2

The other problem is that we use Spring's @Lock annotation on some repository methods.

public interface IncrementRepository extends JpaRepository<IncrementEntity, UUID> {

    @Lock(LockModeType.PESSIMISTIC_FORCE_INCREMENT)
    Optional<IncrementEntity> findByAnnee(String pAnneeAA);

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    IncrementEntity save(IncrementEntity pIncrementEntity);

}

This is critical for us to have a lock on that as we get / compute an increment used to act as a unique identifier of some of our data.

If I correctly understood this locking mechanism :

  • if a process execute this code, the Spring JPA @Transaction will acquire a lock on the IncrementEntity (lock the database table).

  • when another process tries do do the same thing before the first lock has been released by the first transaction, it should have a PessimisticLockException and the second transaction will rollback

  • this is managed by Spring at application level, NOT directly at database level (right??)

So what will happen if we're running our code on several containers ?

  • app running in container 1 sets a lock
  • app running in container 2 execute the same code and tries to set the same lock while the first one has not been released yet
  • each Spring application running in different containers will probably acquire the lock without problems as they don't share the same information?

Please tell me if I correctly understood how it works, and if we will effectively have a problem running such code on several docker containers.

I guess that solution would be to set a lock directly on the database table, as we have only one instance of it?

Is there a way to easily set / release the lock at database level using Spring JPA code ?

Or perhaps I misunderstood and setting a lock using Spring's @Lock annotation sets a real database lock ?

In that case, perhaps we don't have any problem at all, as the lock is correctly set on the database itself, shared by all containers instances??

Problem 3

To avoid having too much exceptions and reject some requests trying to acquire a lock at the same time, we also added a synchronized block around the above code.

    String numIncrement;
    synchronized (this.mutex) {
        try {
            numIncrement = this.incrementService.getIncrement(var);
        } catch (Exception e) {
            // rethrow custom technical exception
        }
    }

This way concurrent requests should be delayed and queued, which is better for our users experience.

I guess that we will also have problems here as docker instances doesn't share the same JVM, so synchronization can work only in the scope of the container itself... right?

Conclusion

For all these problems, please tell me if you have some solutions to workaround / adapt our code so it can be compatible with app scaling.

1 Answers

Following a set of tests I can confirm these points about my original question

Problem 1

We can disable a Spring CRON with the - value

@Scheduled(cron = "-")

Problem 2

The Spring's JPa @Lock annotation sets a lock on the database itself. It is not managed by Spring software.

So when duplicating containers, if the Spring app in the first container sets a lock, the database is locked and when the second app in another container tries to get data it has the PessimisticLockException.

Problem 3

Synchronized code using the synchronized JAVA keyword is obviously managed by JVM, so there is no code mutual exclusion between containers.

Related