Redis as a Lock! Are You Sure?
Tags: redis, architecture, tutorial, database
Published: 2022-05-02 03:21:51

Redis is a storage that keeps data in memory, and we know anything in memory is unreliable. Furthermore, [Redis' data persistence](https://towardsdev.com/data-persistence-in-redis-2780c11d1623) is not as reliable as claimed. Therefore, it is very dangerous to use Redis as a lock to achieve some sort of synchronization.

This article, however, is not intended to focus only on Redis' unreliability, but rather to further analyze whether those locks are being used correctly.

In fact, we often refer to two very different concepts as locks.

- Exclusive lock: Used to control the access rights of a critical section, where only one person can be in the critical section at a time. A similar concept is the semaphore, which allows the maximum number of people in the critical section.
- Barrier: Used to reduce the frequency of a specific action.

Nevertheless, these two concepts have completely different purposes and are implemented in totally different ways. However, usually, we do not carefully define the requirements and choose the correct approach, resulting in an abnormality.

## Exclusive Lock

The purpose of an exclusive lock, also known as a mutex lock, is to control the simultaneous users of a critical section to only one.

Since it is a critical section, there are two actions, locking and releasing, entering while locking and leaving while releasing. This is often used to avoid racing conditions in databases, e.g. MySQL cannot avoid lost updates, so an additional exclusive lock is used as a synchronization mechanism for two simultaneous updates.

Based on the above description we can write a pseudo code.

```python
while tryLock(forLongTime):
    sleep(veryShortTime)
    
doSomeThing()

releaseLock()
```

There are several worth noting points above.

1. The `tryLock` method implies two actions: acquire the lock and lock it. If you can acquire the lock, you have to lock it immediately, otherwise the lock will be taken away by others, and the following critical section will be breached.
2. The waiting time should be set very short, the objective is to be able to continue things as soon as possible, rather than spend a bunch of time waiting.
3. However, the locking time should be very long to avoid the lock disappearing before things are done, resulting in invalidation of the critical section.
4. After doing what needs to be done, the lock must be released immediately so that others can access the critical section immediately.

Again, the purpose of the exclusive lock is to control access to critical sections. If two actions occur at the same time, then both actions can be completed correctly, just one after another.

## Barrier

The purpose of the barrier is to reduce the frequency of a certain action. For example, if you call a API twice in a row, and the second time the API responds with "Please try again later", this is a typical barrier.

Since the purpose is to reduce the frequency, the timing of the blocking is important, depending on the feature requirements. For instance, if the product specification requires at least 5 seconds between APIs, then the locking time is 5 seconds. This is usually shorter than the locking time of an exclusive lock.

We also try to write pseudo code.

```python
if tryLock(featureSpecTime):
    return False

return doSomeThing()
```

Compared with the exclusive lock, there is no unlock and wait, but the same `tryLock` is used, but the lock time is from the product requirements.

The barrier is to reduce the frequency, so if two actions happen at the same time, then one will succeed and the other will fail.

## How about Redis?

Alright, we know all of this, so what does this matter to Redis? Yes, absolutely.

Redis is the one that is most often used to implement either exclusive locks or barriers, and `tryLock`, which is used on both sides, is a one-line command in Redis: `SET someKey 1 EX someTime NX`. Depending on whether the result is `OK` or `nil`, you can tell if the lock has been obtained. In terms of implementation, this is very simple.

However, as mentioned at the beginning of the article, Redis data is not reliable, and using Redis as an exclusive lock may cause critical sections to become invalid.

Redis can play a good role as a barrier, because a failure to block is at most an action that is done a few times more and does not affect the stability of the system, but as an exclusive lock, we must carefully consider the pros and cons.

For me, if I want to avoid MySQL lost updates, I always recommend using MySQL's native `FOR UPDATE`, which is not difficult to implement and does not need to wait, and will not be invalid. Of course, to avoid MySQL lost updates, there are several approaches in addition to locking, which can be found in [my previous article](https://medium.com/interviewnoodle/how-to-avoid-the-race-condition-and-the-negative-value-3f397b2b08e4).


## Conclusion

This article explains that there are actually two different meanings of what we commonly call locks, and although they both have a similar appearance, both the purpose and the details of their implementation are entirely different.

To summarize.

- Exclusive lock: Two simultaneous actions will both succeed, but one after the other.
- Barrier: Two simultaneous actions, only one of which will succeed.

When it comes to locks, make sure you know exactly which context you want to deal with and get it right. In my case, even if I had Redis in my system and I would use it, I would design my system as if Redis data did not exist, so that you would know if your system would handle it correctly when something unfortunate happened.