Dealing with Salesforce rowlocks

Salesforce is a multi-tenant environment, which means that resources are limited and we will need to design our products by taking care of the governor limits. However, sometimes we forget about other kind of restrictions that we have to keep in mind not only in Salesforce, but along other systems that work with databases.

Salesforce rowlocks intro

What is a rowlock in Salesforce?

From the Salesforce help:

When a record is being updated or created, we place a lock on that record to prevent another operation from updating the record at the same time and causing inconsistencies on the data.

Normally records will be locked for a really short period of time, but facing this issue can be relatively easy depending on the complexity of your products. If you ever find an error like this, the console output will be similar to this:

UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record

When rowlocks become a problem in Salesforce?

A rowlock will occur when there are two or more processes accessing the same records, so you won’t be facing this issue when all your logic is sequential. Even if you have some asynchronous processes, the probability of this happening should be low. However it’s pretty common to forget about these kind of issues and start adding more and more processes that can be executed asynchronously.

Pain points could be:

  • Queueable, Batches or any kind of jobs that are scheduled on a regular basis.
  • Lot of different processes focused only in a small group of objects.
  • Long processes with huge SOQLs that locks a large amount of records.

Basically, any kind of asynchronous interactions can end up into a rowlock issue and, as you may know, exceptions will rollback the whole logic transaction. So rowlocks can become a real problem when customizations in Salesforce scale a lot and/or if lot of processes focus on the same kind of records.

On this post, you can find more official information about rowlocks. Even, Salesforce prepared a cheatsheet to have this information more handy.

How to face rowlocks in Salesforce?

Of course, the best answer will be to avoid them instead of fight them. Keeping a good development path with good design practices can definitely help here. A good starter point was to follow enterprise patterns like Unit of Work or similar patterns that comes from the fflib.

If you cannot avoid to add asynchronous processes like jobs running intense workloads on the same object, at least, group all the logic on one main process and prevent many different processes that can be performing DMLs at the same time. This is the same advice when working with Triggers, just avoid creating more than one on the same object.

If your system has scaled without a good design and now you have a monster you can’t control, you can still try a few things:

Locking Statements in Salesforce

You can explicitly state that you are going to be using a set of records and they must be locked for the rest of the processes:

Account [] accts = [SELECT Id FROM Account LIMIT 2 FOR UPDATE];

FOR UPDATE will lock the records returned by the SOQL (at reading time) during the Apex transaction. Beware that when other processes attempt to access those protected records, they will wait up to 10 seconds before the system throws an exception. Taking into account that limit of time, my recommendation is to use locking statements with caution. Make sure that the transaction is fast enough so other processes can still work with the data without getting errors.

Retry Logic Workaround

There is not much additional resources that we can use and not much official information apart from what we already saw. But searching a bit deeper, I could found this article from Salesforce that suggest a possible workaround as a last resort:

… Because Salesforce holds these locks very briefly, customers who are experiencing a small number of locking errors might be able to handle the problem by adding retry logic to their integration code …

And what’s a retry logic in this context? Well something like this:

Account[] accounts;

do{
   try{
      accounts = [SELECT ... FOR UPDATE];
   } catch(QueryException e){
   ...
   }
} while (accounts == null)

This logic will retry to lock the same records even after the 10 seconds has passed. Of course, I’d not recommend to use a infinite loop (that will end up finding another Salesforce limitation), but limit it to a fixed number of attempts.

If your rowlocks problem is really serious, maybe after 3 attempts what you really want to do is to show a user-friendly error where you inform the user that the process could not take place. At the end, we just need to know when to stop as resources are not limitless.

Also you can use BatchApexErrorEvent to build your own retry logic on batches. If that doesn’t fit your requirements, please take a look at these other examples: example 1, example 2.

Conclusion

If you have a healthy code base with good standards, rowlocks is probably the least of your problems. Despite there are some workarounds you can use to mitigate this issue, focus on improving your design of your products and avoid as much as possible a high usage of the same resources.

Leave a Comment