📜 ⬆️ ⬇️

Using composite keys to manipulate memcached data

Often, when working with memcached , a situation arises when it is necessary to delete data in various places. For example, when adding a new comment, you need to update not only the cache of the comments on this page, but also the comment feeds on the main page, the list of user comments, the user comments count, the total count of comments for the site, the article comments count, etc. You can remember all the keys of this data and call delete () with these keys many times.

<?php
$cache->delete( 'comments_art123' );
$cache->delete( 'comments_tape' );
$cache->delete( 'comments_user123' );
$cache->delete( 'comments_counters_art123' );
$cache->delete( 'comments_counters_user123' );
......
?>


* This source code was highlighted with Source Code Highlighter .


As you know, memcached keeps data flat, that is, one key always corresponds to one value. Nested keys do not exist. It is also not possible to delete a group of keys, say by mask. It would be nice if you could, for example, do this: $ cache-> delete ('comments *'); But it is impossible.
')
But if you can not, but really want, you can;)

Task : Implement the ability to group delete data from the cache
Solution : We will store the sign of remoteness of a group in a separate cache location, and the fact of the availability of data will be determined not by the fact of the availability of the data itself, but by the fact of the presence of the attribute. Let's call this place keskheader . For simplicity, we will store the usual hash there. For an example with comments, the hash will look like this:

array(
'comments' => array(
'art123' => array(),
'tape' => array(),
'user123' => array(),
'counters' => array( 'art123' =>array(), 'user123' => array(),),
)
);


* This source code was highlighted with Source Code Highlighter .


Thus, in order to mark a certain group as deleted, we just need to remove this branch from the cache. For example, if we want to update the counters, remove the 'counters' key, and then our cache will look like this:
array(
'comments' => array(
'art123' => array(),
'tape' => array(),
'user123' => array(),
)
);

* This source code was highlighted with Source Code Highlighter .


This will be a sign that the data should be pulled out of the database, and put in the cache, adding, of course, the relevant information in the cache.

If necessary, other information can be stored in the cache manager, for example, the size of the data, or even links to other related elements, which will expand the possibilities and realize data storage not only in the tree, but also in the form of a network.

So, the code of the class that implements the above:

<?php

/**
* memcached, .
*
* @author rvk
*/
class MemcacheCacher implements DataCacher {

protected $daemon = null ;
protected $key = '' ;
protected $header = array();
const HEADER_KEY_NAME = 'cacheheader' ;

public function __construct($key) {
$ this ->key = explode( '|' , $key);

$ this ->daemon = new Memcache();
$ this ->daemon->connect( 'localhost' , 11211) or die ( "Could not connect" );

// .
$ this ->header = $ this ->daemon-> get (MemcacheCacher::HEADER_KEY_NAME);
if (!is_array($ this ->header)) {
$ this ->daemon->add(MemcacheCacher::HEADER_KEY_NAME, array(), false , 10000);
$ this ->header = $ this ->daemon-> get (MemcacheCacher::HEADER_KEY_NAME);
}

}

/**
*
*
*
* @param mixed $data
*/

public function set ($data) {
$ this ->createHeaderElement();
$ this ->daemon-> set (implode( '|' ,$ this ->key), $data, false , 10000) or die ( "Failed to save data at the server" );
}

/**
*
*
*
* @return mixed
*/

public function get () {
if ($ this ->exists()) {
return $ this ->daemon-> get (implode( '|' ,$ this ->key));
}

return null ;
}

/**
*
*
*
* @return bool
*/

public function exists() {
$element = $ this ->getHeaderElement();
return is_array($element);
}

/**
* . , .
* ,
*/

public function del() {
if ($ this ->exists()) {
$element = $ this ->setHeaderElement( null );
$ this ->daemon-> set (MemcacheCacher::HEADER_KEY_NAME, $ this ->header, false , 10000);
}
}

/**
*
*/
public function flush() {
$ this ->daemon->flush();
}

/**
*
*
*
* @return mixed
*/

protected function & getHeaderElement() {
$header = & $ this ->header;

foreach ($ this ->key as $subkey) {
if (!isset ($header[$subkey])) {
return $header[$subkey];
}
$header = & $header[$subkey];
}
return $header;
}

/**
*
*
*
* @param mixed $value
* @return mixed
*/

protected function setHeaderElement($ value ) {
$element = & $ this ->getHeaderElement();
$element = $ value ;
$ this ->daemon-> set (MemcacheCacher::HEADER_KEY_NAME, $ this ->header, false , 10000);
}

/**
*
*/

protected function createHeaderElement() {
$header = & $ this ->header;

foreach ($ this ->key as $subkey) {
if (!isset ($header[$subkey])) {
$header[$subkey] = array();
}
$header = & $header[$subkey];
}
$ this ->daemon-> set (MemcacheCacher::HEADER_KEY_NAME, $ this ->header, false , 10000);
}

}
?>

* This source code was highlighted with Source Code Highlighter .


To update everything that relates to comments you need to execute only such code:

$ cache-> delete ('comments');

or update only counters

$ cache-> delete ('comments | counters');

well, or just a user counter

$ cache-> delete ('comments | counters | user123');

The best thing about the work of the class will tell the unit test (they are often better than any documentation):

<?php
class TestOfMemcacheCacher extends UnitTestCase {

public function setUp() {
$ this ->cache = new MemcacheCacher( 'key' );
$ this ->cache-> set (1);
}

public function testOfCacheExists() {
$cache = new MemcacheCacher( 'key' );
$ this ->assertTrue($cache->exists());
$cache = new MemcacheCacher( 'key1' );
$ this ->assertFalse($cache->exists());
}

public function testOfCacheDelete() {
$cache = new MemcacheCacher( 'key' );
$ this ->assertTrue($cache->exists());
$cache->del();
$ this ->assertFalse($cache->exists());

}

public function testOfCacheGet() {
$cache = new MemcacheCacher( 'key' );
$ this ->assertEqual($cache-> get (), '1' );
$ this ->assertNotEqual($cache-> get (), '2' );
}

public function testOfTreeCacheExists() {
$cache = new MemcacheCacher( 'key|key1' );
$cache-> set (123);
$ this ->assertEqual($cache-> get (), '123' );
$ this ->assertTrue($cache->exists());
$cache = new MemcacheCacher( 'key' );
$cache->del();
$cache = new MemcacheCacher( 'key|key1' );
$ this ->assertFalse($cache->exists());
}

public function tearDown() {
$ this ->cache->flush();
}

}

?>


* This source code was highlighted with Source Code Highlighter .


A similar approach successfully worked on the photofile.ru project under the load of about 4 million hits per day.

PS You should not treat the code above as completely finished. This is an example, it can and should be optimized.

Source: https://habr.com/ru/post/64976/


All Articles