Details
-
Type:
Improvement
-
Status:
Resolved
-
Priority:
Minor
-
Resolution: Won't Fix
-
Affects Version/s: 1.6.0
-
Fix Version/s: None
-
Component/s: Zend_Cache
-
Labels:None
Description
I have extended the Memcached backed to implement a tagging system. I have not yet load tested or extensively bug tested this. My improvement is for a specific project, but I've adapted my extended class for submission as a potential improvement. Only a couple of methods are actually over-ridden here.
The basic idea is that you use a cached array to track the keys. When you save keys you retrieve the cached key array, add the ID being saved as another entry into each tag it is being associated with, and save the updated tag array. To remove by tag you look up the tag in the global tags array and loop through the entries deleting each one from memcache and updating the cached tag array.
Here is my example implementation:
class KeyEnabled_Memcached extends Zend_Cache_Backend_Memcached
{
private function getTagListId()
{
return "MyTagArrayCacheKey";
}
private function getTags()
{
if(!$tags = $this->_memcache->get($this->getTagListId()))
{
$tags = array();
}
return $tags;
}
private function saveTags($id, $tags)
{
// First get the tags
$siteTags = $this->getTags();
foreach($tags as $tag)
{
$siteTags[$tag][] = $id;
}
$this->_memcache->set($this->getTagListId(), $siteTags);
}
private function getItemsByTag($tag)
{
$siteTags = $this->_memcache->get($this->getTagListId());
return isset($siteTags[$tag]) ? $siteTags[$tag] : false;
}
/**
* Save some string datas into a cache record
*
* Note : $data is always "string" (serialization is done by the
* core not by the backend)
*
* @param string $data Datas to cache
* @param string $id Cache id
* @param array $tags Array of strings, the cache record will be tagged by each string entry
* @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
* @return boolean True if no problem
*/
public function save($data, $id, $tags = array(), $specificLifetime = false)
{
$lifetime = $this->getLifetime($specificLifetime);
if ($this->_options['compression']) {
$flag = MEMCACHE_COMPRESSED;
} else {
$flag = 0;
}
$result = $this->_memcache->set($id, array($data, time()), $flag, $lifetime);
if (count($tags) > 0) {
$this->saveTags($id, $tags);
}
return $result;
}
/**
* Clean some cache records
*
* Available modes are :
* 'all' (default) => remove all cache entries ($tags is not used)
* 'old' => remove too old cache entries ($tags is not used)
* 'matchingTag' => remove cache entries matching all given tags
* ($tags can be an array of strings or a single string)
* 'notMatchingTag' => remove cache entries not matching one of the given tags
* ($tags can be an array of strings or a single string)
*
* @param string $mode Clean mode
* @param array $tags Array of tags
* @return boolean True if no problem
*/
public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
{
if ($mode==Zend_Cache::CLEANING_MODE_ALL) {
return $this->_memcache->flush();
}
if ($mode==Zend_Cache::CLEANING_MODE_OLD) {
$this->_log("Zend_Cache_Backend_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached backend");
}
if ($mode==Zend_Cache::CLEANING_MODE_MATCHING_TAG) {
$siteTags = $newTags = $this->getTags();
if(count($siteTags))
{
foreach($tags as $tag)
{
if(isset($siteTags[$tag]))
{
foreach($siteTags[$tag] as $item)
{
// We call delete directly here because the ID in the cache is already specific for this site
$this->_memcache->delete($item);
}
unset($newTags[$tag]);
}
}
$this->_memcache->set($this->getTagListId(),$newTags);
}
}
if ($mode==Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG) {
$siteTags = $newTags = $this->getTags();
if(count($siteTags))
{
foreach($siteTags as $siteTag => $items)
{
if(array_search($siteTag,$tags) === false)
{
foreach($items as $item)
{
$this->_memcache->delete($item);
}
unset($newTags[$siteTag]);
}
}
$this->_memcache->set($this->getTagListId(),$newTags);
}
}
}
}
tags system for memcache, APC or xcache backends are not reliable because the system can drop any of your record to make some space for other records
so your proposal is good but not reliable
if memcache drop your MyTagArrayCacheKey record, what about CLEANING_MODE_NOT_MATCHING_TAG or CLEANING_MODE_MATCHING_TAG ?
in SVN trunk, there is a completly new backend called "TwoLevels" :
This virtual backend has plenty of advantages and of course support tags (throw the reliable backend)
Please have a look at this.
- the fast level can be memcache backend
- the slow level (but reliable) can be the file backend
This virtual backend has plenty of advantages and of course support tags (throw the reliable backend) Please have a look at this.