11.2.2 Rewriting our lock
As you may remember from section 6.2, locking involved generating an ID, conditionally setting a key with SETNX, and upon success setting the expiration time of the key. Though conceptually simple, we had to deal with failures and retries, which resulted in the original code shown in the next listing.
There’s nothing too surprising here if you remember how we built up to this lock in section 6.2. Let’s go ahead and offer the same functionality, but move the core locking into Lua.
There aren’t any significant changes in the code, except that we change the commands we use so that if a lock is acquired, it always has a timeout. Let’s also go ahead and rewrite the release lock code to use Lua.
Previously, we watched the lock key, and then verified that the lock still had the same value. If it had the same value, we removed the lock; otherwise we’d say that the lock was lost. Our Lua version of release_lock() is shown next.
Unlike acquiring the lock, releasing the lock became shorter as we no longer needed to perform all of the typical WATCH/MULTI/EXEC steps.
Reducing the code is great, but we haven’t gotten far if we haven’t actually improved the performance of the lock itself. We’ve added some instrumentation to the locking code along with some benchmarking code that executes 1, 2, 5, and 10 parallel processes to acquire and release locks repeatedly. We count the number of attempts to acquire the lock and how many times the lock was acquired over 10 seconds, with both our original and Lua-based acquire and release lock functions. Table 11.2 shows the number of calls that were performed and succeeded.
|Benchmark configuration||Tries in 10 seconds||Acquires in 10 seconds|
|Original lock, 1 client||31,359||31,359|
|Original lock, 2 clients||30,085||22,507|
|Original lock, 5 clients||47,694||19,695|
|Original lock, 10 clients||71,917||14,361|
|Lua lock, 1 client||44,494||44,494|
|Lua lock, 2 clients||50,404||42,199|
|Lua lock, 5 clients||70,807||40,826|
|Lua lock, 10 clients||96,871||33,990|
Looking at the data from our benchmark (pay attention to the right column), one thing to note is that Lua-based locks succeed in acquiring and releasing the lock in cycles significantly more often than our previous lock—by more than 40% with a single client, 87% with 2 clients, and over 100% with 5 or 10 clients attempting to acquire and release the same locks. Comparing the middle and right columns, we can also see how much faster attempts at locking are made with Lua, primarily due to the reduced number of round trips.
But even better than performance improvements, our code to acquire and release the locks is significantly easier to understand and verify as correct.
Another example where we built a synchronization primitive is with semaphores; let’s take a look at building them next.