Im wondering what is the best way to make my Memcache operations atmoic in my web app.
Take the following scenario into consideration:
Client 1 connects and retrieves data from key 1
Client 2 connects a few microsecond after Client 1, requests the same data from key 1
Client 1 saves new data to key 1
Client 2 saves new (different data) to key 1, not taking into account that Client 1 modified the value already
In this case, there is no atomicity in the process.
My (potential) solution is to set, acquire and release locks on keys from within my app.
So the above process would work like this after my implementation:
Client 1 connects, checks for an active lock on key 1, finds none, and gets the data
Client 2 connects a few microsecond after Client 1, requests the same data from key 1, but finds a lock
Client 2 enters a retry loop until Client 1 releases the lock
Client 1 saves new data to key 1, releases the lock
Client 2 gets the fresh data, sets a lock on key 1, and continues
Thoughts? Will this method work, and will there be any performance hits I should be wary of?
Consider what problem you are trying to solve here. Do you:
Most of the time people just want (1). If this is all you want, you can use check-and-set semantics with Memcached::cas()
, or if you have a simple integer value you can use the atomic Memcached::increment()
and Memcached::decrement()
operations.
If, however, you need to use the key to represent a finite resource (2), consider using a different set of semantics:
$keyname = 'key_with_known_name_representing_finite_resource';
// try to "acquire" the key with add().
// If the key exists already (resource taken), operation will fail
// otherwise, we use it then release it with delete()
// specify a timeout to avoid a deadlock.
// timeout should be <= php's max_execution_time
if ($mcache->add($keyname, '', 60)) {
// resource acquired
// ...do stuff....
// now release
$mcache->delete($keyname);
} else {
// try again?
}
If for some reason you do not have access to cas()
, you can implement a lock using two keys and add()
/delete
$key = 'lockable_key_name';
$lockkey = $key.'##LOCK';
if ($mcache->add($lockkey, '', 60)) { // acquire
$storedvalue = $mcache->get($key);
// do something with $storedvalue
$mcache->set($key, $newvalue);
// release
$mcache->delete($lockkey);
}
This approach leads to much more resource contention than you would get with a check-and-set approach.
There is a built in feature that can take care of this for you already. What you are looking for is CAS (check and set).
"cas" is a check and set operation which means "store this data but only if no one else has updated since I last fetched it." 1
When you attempt to store data that had been previously updated by another process, the call to set
will fail so you can then decide if you need to re-fetch the data, store it anyway, or bail out.
See Memcached::cas() for more.
Hope that helps.
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