I'm new to memcached. Is this code vulnerable to the expired cache race condition? How would you improve it?
$memcache = new Memcache;
$memcache->connect('127.0.0.1');
$arts = ($memcache===FALSE) ? FALSE : $memcache->get($qparams);
if($arts===FALSE) {
$arts=fetchdb($q, $qparams);
$memcache->add($qparams, $arts, MEMCACHE_COMPRESSED, 60*60*24*3);
}
if($arts<>FALSE) {
// do stuff
} else {
// empty dataset
}
Let's say that query X gets 100 rows. A little after row #50 is modified by another process (lets say that the retail price gets increased).
Is this code vulnerable to the expired cache race condition? How would you improve it?
Yes. If two (or more) simultaneous clients try to fetch the same key from the cache and end up pulling it from the database. You will have spikes on the database and for periods of time the database will be under heavy load. This is called cache stampede. There are a couple of ways to handle this:
For more info check the memcached faq.
Let's say that query X gets 100 rows. A little after row #50 is modified by another process (lets say that the retail price gets increased).
You have three types of data in cache:
What I usually do is to keep the objects as separate keys and then use cache "pointers" in lists. In your case you have N objets somewhere in cache (lets say the keys are 1,2..N
), and then you have your list of objects in an array array(1,2,3,10,42...)
. When you decide to load the list with objects, you load the list key from cache, then load the actual objects from cache (using getMulti
to reduce requests). In this case if any of the object gets updated, you update it in one spot only and it is automatically updated everywhere (not to mention that you save huge amount of space with this technique).
Edit: Decided to add a bit more info regarding the lookahead time expiration.
You set up your object with an expiration data x
and save it into the database with an expiration date of x+5minutes
. This are the steps you take when you load the object from the cache:
time() - x < 0
)+ ":lock"
at the end. You must set this key to expire in the shortest amount possible (for memcached that is 1 second).Hope this clears everything up :)
You have to invalidate any cached object that contains a modified item. Either you have to modify the cache mechanism to store items at a more granular level, or invalidate the entire entry.
It's basically the same as saying you're caching the entire DB in a single cache-entry. You either expire it or you don't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With