Pessimistic Locking for a distributed System (Part-2)

Sankalp Singh
Urban Company – Engineering
3 min readJul 9, 2018

This blog is a continuation of my previous blog and answers some of the important questions left open ended. I shall strongly advice to go through my previous blog `Pessimistic Locking for a distributed System (Part-1)` to have a clear context.

Lets answer each and every question I wrote about in my previous blog

  1. How do we eliminate starvation?

Now, while working with exclusive locks no one can guarantee that there will be no starvation, unless he/she enables some sort of preemption or timeouts. And thats what we are exactly going to do. We can enable timeouts for our transaction. Any code block that executes beyond that time limit will loose on exclusivity for that piece of code, as the transaction that was guaranteeing it has already committed.

2. How do we ensure some processes access the code block while others do not when one or more thread already have acquired a lock on it?

This is a bit of an art. Remember we were attaching a unique identifier to every pice of code before taking a lock and using that identifier as the primary key in our insert operation. Lets take this up with an examples :-

a) there is a code piece say C and we want to allow only one process to use it at any given time.

In this case our choice of key for that code piece shall be a constant string, say “access_code_c”. Every transaction attempting to insert this unique key shall wait if another transaction attempting to insert this unique key has already acquired the lock.

b) there is a code piece say C and we have say N users respectively that could access it. We want that all N users can access that code piece simultaneously but no user individually can access that code piece more than once in parallel at any given time.

In this case our choice of key for that code piece shall be concatenation of a constant string and a variable that uniquely identifies each user (you can use an id that identifies the user in your system), say “access_code_c_{$user_id}”

c) there are two code pieces say C and D, and we have to ensure while code piece C is being accessed by a process, both code pieces C and D get blocked for any other process to access it.

In this case our choice of identifier for both the code pieces should be the same, say "access_code_cd"

3. How do we handle timeouts?

Almost every language provides a way to trigger a function with a delay. This function could be leveraged to commit the transaction prematurely if it is taking more than the stipulated time.

4. Why we used `update on duplicate` query in a transaction resulting in commit and not a simple `insert` query in a transaction resulting in a rollback?

This requires nuanced understanding of MySQL locking mechanism. A row lock on an `insert`query for more than two transactions shall resolve to a `shared lock` on transactions that came later while there is already a transaction in progress. This will result in a failure because of deadlock once the first transaction completes (each transaction will need to upgrade to an exclusive lock which could not happen as each shall wait on the others shared lock). On the other hand a lock on row behaves quite differently for `insert….update_on_duplicate` query. Here we always get an `exclusive row lock` and therefore could leverage this to avoid deadlocks. You can refer to this document for a deeper understanding.

I hope this short read explains how we can easily achieve locking of a resource or code piece across a distributed system. If you have any questions or situations where you faced similar constraints in your production systems, please share in the comments below.

P.S. If there is enough interest, will also share the implementation details on Github.

--

--