spring-bootarchitecture

Architectural question - how to avoid multiple events tobe processed at the same time


I have some listener. Spring Boot app with Hibernate. Sometimes listener can receive 2 events (at the same time) that are processed simultaneously - the result is - duplicated objects. Is there any way to ensure that these 2 events are processed one by one? In know that Inbox pattern will be the best solution but maybe there is some other solution worth considering (a solution that requires less time to implement)?

Both handers use the same tables in DB. I could use pessimistic locking too but it could reduce the system's performance

My AMQPListener

  @RabbitListener(
            queues = {RabbitApi.MY_EVENT_A + "." + RabbitApi.EXCHANGE},
            errorHandler = "amqpListenerExceptionHandler"
    )
    public void handleEvent_A(
            @Headers Map<String, Object> headers,
            @Payload @Valid EventA event
    ) {
        eEventHandlerA.handle(event);
    }

 @RabbitListener(
            queues = {RabbitApi.MY_EVENT_B + "." + RabbitApi.EXCHANGE},
            errorHandler = "amqpListenerExceptionHandler"
    )
    public void handleEvent_B(
            @Headers Map<String, Object> headers,
            @Payload @Valid EventB event
    ) {
        eEventHandlerB.handle(event);
    }

EDIT - new data: The problem is that in the second event handlerB doesn't knkow yet that object has been created in db table because of no commit. The result is that the second object is created (and shouldn't be created) Two transactions overlap.


Solution

  • If there is a domain key that can be used to identify the duplicates here, I would suggest utilizing your database to create a unique index on that key.

    Ultimately, synchronization requires an external piece of state to "lock" on so that the different handlers can atomically track the lock, and be aware of each other. Modern databases are fairly adept at this (being that external state), with both pessimistic and optimistic locking patterns, with the added advantage of being the store itself.

    This can also be solved with an explicit locking solution, either in-process (using a concurrent hashmap or similar), or externally, using something like Redis or memcached to track the locks.

    A naive solution here would be to create a synchronize block around each implementation here, locking on an object. This would, of course slow down your application immensely as you would only be able to process a single event at a time. Thus something like a concurrent hashmap, with locks for each "key", assuming you can identify one.