Lock retries with memcached
July 19, 2010I've seen multiple implementations of locks using memcached that use exponential retry algorithms and have yet to figure out the reasoning behind it. From what I understand, exponential back off is used in TCP packet retries, and I think that's where it first came from but I don't see how it applies to locks implemented with memcached. I'm going to try to explain why I think exponential retry is wrong in this situation.
Analogy
Let's say you need to do laundry, and you walk to the laundry room to acquire a lock on the laundry machine. As soon as you get to the door, another person is also about to walk into the door. You both step and your shoulders collide with each other and the door frame. Neither of you make it through, so you wait and try again, he goes first then you. 38 minutes later, you need to move your laundry from the washer to the dryer, so you walk over but the dryer is taken. So you look at the timer, wait 12 more minutes, and move your laundry over.
These are two different types of contention, where the main difference is that the contention in the latter case doesn't prevent progress of both parties.
Memcached Locks
Typically, when you're using memcached to lock a resource, it's to ensure operations are performed on some resource serially. In an ideal world, processes would enter a queue for rights to perform an operation on a specific resource and each would be notified when it's their turn to perform their operation. But that level of synchronization is non-trivial in a large system, so instead all the processes poll a memcached key for rights to perform their operation.
Analysis
With an exponential retry algorithm, a process will wait longer and longer between tries until the resource is available. Let's see if this makes sense with 2 processes requesting a lock on the same resource: process A and B both need to use resource 1. Process A gets the lock first and intends to perform an operation on resource 1 that has a mean duration 200ms and a std dev of 70ms. 10ms later, process B needs resource 1, so it tries to acquire the lock but fails. At this point in time, we can calculate the probability that this resource is unlocked after t ms given the mean and std dev. We can calculate when we should try next, and bound that by how often we want to tax our system (ops/sec). An exponential retry algorithm will try at 100ms, then at 300ms (+200), 700ms, 1500ms. But we know that if it's not released after 300ms, it's likely that it will be available soon after that, and very unlikely that process A needed 700ms given the 200ms mean and 70ms std dev.
So why can't we use math to calculate how often we retry, similar to looking at the time left on the dryer in the laundry analogy? We can pretty easily track how many processes are waiting for a lock using a memcached key and incr/decr, and can easily calculate the operation duration distribution. And even if we don't keep track of the waiters, we can still use a linear algorithm and it won't be making the incorrect conclusion that the exponential algorithm did.
Implementation
Here is one implementation the described algorithm: http://gist.github.com/480851
The goal of this algorithm is to minimize the amount of time a resource spends unlocked despite a process that could use it, while keeping in mind reasonable pressure on the system.
I did some tests with the algorithm back when I wrote in Nov 2009, but don't have the data or code anymore. I intend on revisiting this to collect data on the distribution of tries, time spent waiting, and the amount of time the resource spends unlocked. I'll update this post when I get time to do that.

