Reputation: 9646
I am doing performance test on my grails application using jMeter. In a scenario when concurrent users hit the site, sometime I see the following error in the logs:
Excepton: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) [com.mypack.app.MyClass#1113]
How can I fix and avoid errors like these in the future?
Below is the full stack:
->> 245 | doCall in com.mypack.app.MyController$_save_closure3_closure8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 225 | save in com.mypack.app.MyClontroller
| 195 | doFilter in grails.plugin.cache.web.filter.PageFragmentCachingFilter
| 63 | doFilter in grails.plugin.cache.web.filter.AbstractFilter
| 1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 615 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run . . . in java.lang.Thread
Upvotes: 0
Views: 1517
Reputation: 75681
Grails defaults to optimistic locking, where it's assumed that there will be few or no concurrent updates, or that when they occur you have a strategy in place to resolve issues. This is similar to many source control systems, where two users can edit one file but if the edits overlap there is a conflict that must be manually resolved.
In general it's impractical to resolve concurrent edit issues. If you change one property in a MyClass
instance and I change a different one, we could theoretically merge those two changes into one new record, but what if your change would have affected the change that I made if I had seen it first?
To keep things simple, if you allow concurrent edits and don't have a 'merge' strategy, you should use explicit locking. This does affect performance, but if you only lock for specific domain classes or for specific workflows, the impact should be small, especially if the time that a record is kept locked is short.
But locking in web apps is tricky. You need to lock when you start the 'edit' workflow and load the domain class instance to populate that GSP, and keep the record locked while the user makes changes in the browser, and release the lock when the 'update' action finishes (either successfully or not). This isn't supported in Grails because each request gets its own Hibernate Session
and it's closed (along with its Connection
, which would release your lock) at the end of each request. And if you were to implement this you'd have to make sure that if the update action isn't called (or is called after a max allowable time) that the lock is freed to allow others to edit.
So you're back to a manual merge strategy (or Webflow or something similar). You can remove automatic version checking in the domain class by adding version false
in the mapping
block. Then in your update action, convert the block that checks for stale versions to custom code that looks at versions but also at changed fields, and uses whatever logic makes sense for your domain class(es) to resolve/merge concurrent edits.
Upvotes: 3