Sliding Expiration в memcached

PHP
Разрабатывая в основном под .NET столкнулся с тем, что в memcached отсутствует Sliding Expiration вариант хранения данных.

Первым шагом что бы решить эту проблему было написание «менеджера» дописывающего к данным, которые кладутся в кэш тип (Абсолютный или Sliding) и время хранения, что бы при последующих чтениях из кэша брать эти дополненные данные, смотреть на тип хранения и в случае Sliding перезаписывать данные.

Будут рассмотрены только Get и Set операции, т.к. остальные делаются по аналогии. Increment / Decrement тут не поддерживаются.

Выглядело это примерно так:
public function Set($Key, $Value, $LifeType, $LifeTime)
{
 $Value = new CacheItem($Value, $LifeType, $LifeTime);
 return memcache_set($cnn, $Key, $Value, 0, $LifeTime);
}

public function Get($Key)
{
 // CacheItem is returned
 $obj = memcache_get($cnn, $Key);

// Allow object to stay in cache for some more time
 if ($obj->LifeType == ICacheProvider::LifeTypeLastGet)
 memcache_set($cnn, $Key, $obj, 0, $obj->LifeTime);

return $obj->Value;
}


Достаточно грубо, но, на первое время, вполне устраивало. Ровно до тех пор пока не стало расти количество кэш промахов из-за простой нехватки памяти. Ради интереса залез в dump memcached и обнаружил, что порядка 25% приходится на хранение обертки вокруг данных. первым решением был простой отказ от Sliding Expiration в пользу абсолютного времени хранения, но при этом количество кэш промахов осталось практически таким же, как и в случае, когда памяти не хватало.

Объекты слишком часто выпадали из кэша (

И недавно, просматривая документацию по memcache.get обратил внимание на параметр flags, про который пишут

If present, flags fetched along with the values will be written to this parameter. These flags are the same as the ones given to for example memcache_set().


В нем у можно воспользоваться 2-мя вехними байтами! (Если пользуетесь PECL < 3.0.3 то и целами 3-мя).

Как оказалось из опыта реально получается пользоваться только 15-ю верхними битами, т.е. знаковый бит трогать нельзя (виндовый memcached при использовании падает).

Было решено тип хранения (абсолютный или Sliding) вообще не указывать, а хранить только время (если = 0, то значит абсолютное хранение). Причем время хранить не просто в секундах, а первый бит времени сделать типом (0 — секунды, 1 — минуты). При этом максимальное время, которое удается записать в 15 бит получается = 60 * ((1 << 14) — 1) = 982980 сек ~ 11 дней, чего более чем достаточно. Конечно при этом величины, хранения более 4.5 часов начинают округляться по минутам, но и это ничего страшного.

Вот, что получилось в результате.

const MaxLifeTime = 982980; // 60 * ((1 << 14) - 1);
const TimeChunkSize = 16383; // (1 << 14) - 1;

public function Set($Key, $Value, $LifeTime, $LifeType = ICacheProvider::LifeTypeAbsolute)
{
 $this->checkKey($Key);

// 2 bytes is reserved for by memcached, other 2 are user to store $LifeTime information
 $flags = $this->_compress;
 if ($LifeType == ICacheProvider::LifeTypeLastGet)
 $flags |= $this->ConvertTimeToFlag($LifeTime) << 16;

return memcache_set($cnn, $Key, $Value, $flags, $LifeTime);
}

private function ConvertTimeToFlag($LifeTime)
{
 // First 1 bytes for type (Second or Minute)
 // Other 15 bytes for value
 if ($LifeTime <= TimeChunkSize)
 return $LifeTime;

$Minutes = floor($LifeTime / 60;
 if ($Minutes <= MemcacheProvider::TimeChunkSize)
 return 0x4000 | $Minutes; // 0x4000 = (1 << 14)

return 0;
}

public function Get($Key)
{
 $flags = 0;
 $val = memcache_get($cnn, $Key, $flags);

$LifeTime = ($flags >> 16) & 0x7FFF;
 if ($val !== false && $LifeTime != 0)
 {
 $LifeTime = ($LifeTime & 0x4000) ? 60 * ($LifeTime & 0x3FFF): $LifeTime;
 memcache_set($cnn, $Key, $val, $this->_compress | ($flags & 0x7FFF0000), $LifeTime);
 }

return $val;
}


По последним данным процент кэш-промахов составляет всего 1.25%


0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.