Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails StaleObjectStateException in lock() method

I'm receiving a StaleObjectStateException while I'm trying to lock a domain object inside of a transactional service (grails 2.3.8):

@Transactional
class AnalyticsService {
    boolean newStreamView(Long streamId) {

    Stream stream = Stream.lock(streamId) // The exception is launched here

Of course this only occurs if there are many concurrent calls to this service. As I can see, hibernate is trying to lock with ID and version parameters:

select id from stream where id =442 and version =305 for update

and this fails. If I disable the optimistic locking (version: false) inside of that domain class, everything works fine (hibernate just use the id for locking the row).

As posted in Marc Palmer's Blog seems that:

The only foolproof way to avoid StaleObjectException(s) while keeping optimistic locking ON, is to do all GORM work in transactions, and always load objects with Domain.lock(id). When using dynamic finders or criteria you will need to specify the “lock” option to have results pre-locked

He says that we should keep optimistic locking ON.

Is there any safe approach of avoiding StaleObjectStateException with lock and optimistic locking ON ?

If I disable optimistic locking (version: false) what other problems can I expect. I'm concerned about this because this domain class is updated from other services ?

Thanks in advance.

like image 880
eko Avatar asked Oct 20 '22 03:10

eko


1 Answers

We fixed 100% of our StaleStateException/OptimisticLocking issues by:

  • We don't explicitly lock anything. We adjusted the code flow and objects to minimize the chance of lock contention using the following
  • Removing all @Transactional annotations from all controllers. Move code that modifies domain objects from controllers into services. Never modify a domain object in a controller, ever. Only delegate to services (which are transactional by default) to modify domain objects. Perhaps you did this in this case, but make sure you do it 100% of the time.
  • Don't disable optimistic locking, it's there for a reason. Without it, you run the risk of overwriting an update from a different transaction. In general, you really need to know what you're doing if you're solving this problem with explicit locks or other interventions in the transaction processing.
  • Remember that if your domain object is part of a belongsTo/hasMany relationship, the version number of all of the related objects gets bumped up when any update occurs at all. So if two different processes are updating different parts of the object graph, the first to commit will invalidate the second and cause this. See if what Burt Beckwith is saying here is pertinent: https://www.youtube.com/watch?v=-nofscHeEuU. Even though he's talking about performance here, the solution he proposes also minimizes this cascading of version number updates.
  • Again, you don't appear to be doing this here, but we minimize passing potentially dirty whole domain objects around when we just need to do specific updates. So rather than orderService.update(order) when all we're doing is setting a status we might say orderService.setStatus('foo',orderId) and have that method do the get and update. This tightens the window of opportunity for a StaleState to occur because it's a short time from the fetch to the save. Basically, make sure you don't hang onto a dirty domain object longer than you absolutely have to.
like image 75
Jabrwoky Avatar answered Oct 23 '22 09:10

Jabrwoky