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
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.
Tries in 10 seconds
Acquires in 10 seconds
Original lock, 1 client
Original lock, 2 clients
Original lock, 5 clients
Original lock, 10 clients
Lua lock, 1 client
Lua lock, 2 clients
Lua lock, 5 clients
Lua lock, 10 clients
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.