Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP Atomic Memcache

Tags:

php

memcached

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?

like image 202
Kovo Avatar asked Jan 16 '23 16:01

Kovo


2 Answers

Consider what problem you are trying to solve here. Do you:

  1. just want to avoid the lost-update problem. (E.g. with a counter that increments by one.)
  2. or you do need to ensure that while one client has retrieved an item's value, no other client can use that value? (This is where values represent a limited resource.)

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.

like image 131
Francis Avila Avatar answered Jan 25 '23 14:01

Francis Avila


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.

like image 33
drew010 Avatar answered Jan 25 '23 16:01

drew010