What is optimistic locking?


Optimistic locking is one of the concurrency control mechanisms. It can help avoid situations when one user overrides changes made by another one. When 2 users want to save the same object at the same time, we will reject one of them. The rejected user should review the new state of the object and decide if he still wants to do the update.

Optimistic locking

How to implement optimistic locking?

The implementation mainly comes down to introducing additional information about the used version of the object. Each time someone saves it, the system should:

  • Verify that the received version on which the changes were applied is the latest one stored in the database (if not, stop saving).
  • Generate an identifier that determines the next version and save it with the object.

In practice, in the case of relational databases, it comes down to making an update with an additional condition with a version identifier and checking the number of modified records as a result of query execution (if 0, it means we have a conflict):

UPDATE my_table SET my_content = ? WHERE id = ? AND obj_version = ?

To identify the version of the record you can use for example number, date, string (or generated UUID), but it is important that the generated values are unique within a given object.

When (not) to use it?

  • Use it when an object may be modified by multiple users at the same time, and the state of the object is important during processing.
  • Don’t use it when an object may be modified very often by many users (high rejection rate)
  • You don’t have to use it when you do not care about checking the previous state of the object (when the state of the object does not affect the possibility of updating)

How to use it?

Optimistic locking can be used for the saving process itself (backend only) or for the entire editing process (backend and frontend).


Example 1:

Two users have started processing the order placed in the online store. At the same time, they executed two different operations. The first one decided to cancel the order, and the second one to approve it. It could look like this:

Flow

If we do not use the locking mechanism here, there may be a problem - a canceled order, which has already started the process of returning the money to the customer, will be approved and will eventually end up with the customer receiving a refund and purchased product.

In this case, it is sufficient for us to apply blocking only in the area of saving the data itself. If we detect that something modified state between the order reading and writing during the save operation (read order version ID does not match during write), we will throw an optimistic lock exception.

In this case, we can even automatically retry the rejected process - the 2nd approach to processing the request will no longer pass the validation stage - validation will return an error that the order has been canceled so it can no longer be approved.

But lock during save can often be insufficient. In some cases, you can also return the object’s version to the user interface. It will be necessary when the backend receives only changed fields instead of the full state.


Example 2:

Two employees want to modify the product - a blue, L-size T-shirt. One of them wants to change the color to green, the 2nd wants to change the size to M. If there are 2 separate actions in the system for changing color and size, the object’s version should also be loaded into the user interface, to know exactly which version of object users wants to modify.

Flow2

The above sample flow without the object’s version may lead to create a non-existent product - a green T-shirt of size M - something we don’t have in the store and probably nobody will notice the problem until the first order appears. In the case of using the optimistic locking with a provided version of the object to the frontend, it can protect the system from such a situation. At the time of saving, the system can verify that the customer wanted to change the size of the product, but it is now in a different version (because of the changed color in the meantime) and inform the user about this fact.


Auto-retry or not?

There is no simple answer here. The safest way is to return an error message to the user - so that he is aware of what data will be saved. Error messages, however, may not be attractive to the user. Depending on the business problem, you can try repeated writing. With optimistic locking, we know that only 1 version will always be saved at any given time and that trying again will involve re-validating the incoming data with what is already saved. So it may be good enough to be able to view the history of changes and restore any of the versions. However, returning an error to the employee may have fewer consequences than allowing the customer to order a product variant that is not for sale. When making a choice, it is important to keep the consequences in mind.