Here a simple example on how using cas() command from PECL Memcached to update data that must be at all time up to date
In Memcached, series of commands are not atomic :
A series of commands is not atomic. If you issue a ‘get’ against an item, operate on the data, then wish to ‘set’ it back into memcached, you are not guaranteed to be the only process working on that value. In parallel, you could end up overwriting a value set by something else.
But PECL Memcached come with a useful function : cas()
The principle is :
- Make a get() to get a key value and a cas token (checksum of the value that the get return)
- Make some update on the value
- Ask memcached to update the value with cas() method with the cas token as parameter, to let memcached see if the actual value of the key you want to update is the same as he give before or if another script has changed it
Here the code example, we update an index array, adding values in it when not present
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# Initializing Memcached connection $memcache = new Memcached(); $memcache->addServer('localhost', 11211); $indexKey = 'Our index key'; $newValue = 'Some new value to add to index'; $cas = null; # Starting index updating do { # Getting data, with parameter $cas to get as we want a cas token $indexData = $memcache->get($indexKey, null, $cas); # Index does not exists, initializing it if($indexData === false) { $indexData = array($newValue); $memcache->add($indexKey, $data, 1800); } # Index exists, updating it else { # Adding new value in index $indexData[] = $newValue; $memCache->cas($cas, $indexKey, $indexData, 1800); } } # If result is not stored, or data was updated by another thread while(($memCache->getResultCode() == Memcached::RES_NOTSTORED) || ($memCache->getResultCode() == Memcached::RES_DATA_EXISTS)); |
With that little script, we are sure that :
- If no index are present at start of our script but the index is created in another thread, we retrieve it before adding our value because we use the add() command (Memcached::RES_NOTSTORED)
- If the index is modified by another script while we are working on it, we get the up to date version before trying to add our new data, thanks to the cas() method (Memcached::RES_DATA_EXISTS)
Remember to be careful with cas(), under heavy concurrent updates, the do … while() can easily take a lot of execution time, you must add a maximum try count break condition.
And remember to not check command success in a while loop with something like
1 |
$memCache->getResultCode() != Memcached::RES_SUCCESS |
Memcached::RES_SERVER_ERROR or anything like this and your script will loop forever