Как стать автором
Обновить

Комментарии 42

Хм… подумал что тэги это нечто типа «post117» которым тегается кэш как самого поста так и всех страниц где он светится. Наверное, имело бы смысл. А в текущей реализации это больше напоминает несколько разных кэшей, разве что с экономией памяти.

Может приведете более живой пример тэгов? В голову приходят например TAG_FRONTEND и TAG_BACKEND, но смущает что они не пересекаются никогда и тогда точно можно разные инстансы кэша использовать.
Дело в том, что для этого существуют парамерты, они позволяют разделить кеш, полученный из одной и той же функции с одними и теми же параметрами.

Что касается поводу удаления… Да, я думал над добавлением поддержки параментров PAR_* для удаления кеша, не только для добавления. В примере со статьями тег был бы TAG_ARTICLES а парамерт PAR_ARTICLEID c нужным айди. Если вдруг поднадобится удалить кеш этой конкретной статьи и ничего другого с тегом PAR_ARTICLES, нужно будет указать парамерт PAR_ARTICLEID перед удалением тега TAG_ARTICLES. Так пойдёт?
Тогда перебор с константами — TAG_ARTICLES не нужен если задан PAR_ARTICLEID. Ну и вообще, их имена могли бы быть определены в конфиге (если это было бы надо), а не в классе. Кроме того неясно как протэгать айдишники двух статей (если они обе выводятся на странице). Передавать массив? Тогда если нужно добавить тэг вызывать ::mergeParam() и т.п.

Но главное все же — реального примера (вместо сферического «смысла жизни») не хватает. Примере на гуглкоде с TAG_TOP5 по сути можно тэг вообще не ставить, а очищать кэш GetTop5Items(). Интересует есть ли реальные примеры когда ваши тэги пересекаются.
Я просто выбрал неудачное имя параметра — PAR_ID будет достаточно, его можно использовать в любых других местах, где есть какие-либо айдишники. Параметры не несут смысловой нагрузки и служат для различения контента, вышедшего из одной функции с одними и теми же параметрами.

Ещё раз: Если из одной функции/метода выходят разные данные, значит они зависят от каких-то внешних параметров. И значит их нельзя кешировать под одним именем. По этому мы добавляем кешевый «параметр»

MemcacheTag::SetParam(MemcacheTag::PARAM_ID, $currentArticleId);

а можно и несколько, если надо. Эти параметры добавляются к имени переменной в кеше, внутри системы. Т.о. для каждых данных у нас разные имена.

Что имеется в виду под пересечением тегов? Несколько тегов соответствуют одной переменной? Если у вас появляется необходимость использовать несколько тегов для одной переменной, можно указать их через запятую. Пример сходу придумать не могу. Но суть-то в том чтоб при удалении тега удалять все кешевые переменные, с ним ассоциированные. Пересекающиеся теги это частный случай. Если засунуть одну и ту же переменную под два разных тега, она не станет двумя разными переменными.

Единственная проблема, которю я предвижу, это, скажем, удаление всех закешированных статей, при удалении по тегу TAG_ARTICLE, если, к примеру, произошло редактирование всего какой-то одной. Что бы этого избежать я добавлю поддержку кешевых параметров для удаления по тегу. Скоро.
А зачем вам синглтон, если всё равно вы нигде в примерах не инстанцируете ни единого объекта этого класса?
Объект инстаницируется внутри класса, когда это необходимо. Если вы взглянете на код, вы увидите. Отсутствие new было задумано чтоб минимизировать свой код в чужом.
А работать с разными источниками данных? Перед каждым вызовом фетча, делать SetFunction и SetTags?
Да, настройки сбрасываются после каждого Fetch.
А что, если сделать еще и так? Версия с блокировками. Должно /*ли*/ помочь, при многочисленных обращениях к «медленным» функциям, результатов выполения которых в кеше по какой-либо причине не оказалось.
Ситуация, конечно, интересная. Не думал на столько далеко.
Но реализация с заморозкой скрипта на целую секунду сомнительна.
Нужно обдумать.
секунда взята методом сомнительного научного тыка по той причине, что «медленная» операция займет займет гораздо >> времни, чем секунда, на которую мы замораживаем скрипт.
И, если вы придумаете, обязательно пишите как и что :)
Я понял. Буду держать вас и остальных, кто заинтересован, в курсе дела.
Сегодня какое-то время думал над этим вопросом. В новом примере/сравнении(папка compare), кстати, это займёт кучу времени. Выходит +1 запрос на каждую переменую, получается минимум двухкратное увеличение количества запросов. Что в итоге выливается в худшее быстродействие, чем штатный Zend_Cache. А нужно как-раз наоборот. Хотя, это сферический пример в вакууме — 100 добавлений и удалений разных переменных.
Нужно будет по-играться с более приближенными к реальности данными и их количеством, может оно того и стоит в меньших масштабах. Добавлю это как опцию в крайнем случае.
Смотрели ли вы готовые реализации?
Чем это лучше Zend_Cache + Dklab_Cache?

ЗЫ обилие синглтонов — зло, тут оно совсем не надо.
Тем что моя реализация универсальна и не привязана ни к memcache ни к ZF.
Синглтон — создатся всего один объект, он спрятан внутри класса и никак не мешается в чужом коде. Автодополнение работает на ура. В этом была суть — в возможности безболезненной интеграции в чужой код без его изменения. Имеется в виду добавление логики (if/else/...), можно уложиться в 3 строчки на один элемент кеша.
1) на первый вопросы вы не ответили.
2) Что значит «привязан к ZF»? ZF, фактически, — это набор компонентов, можно брать любой и использовать (что делает например та же Symfony).
3) У Zend_Cache нет привязки ни к чему, кроме этого в поставке куча бэкендов (в том числе и memcache)
4) Любая библиотека должна давать возможность интегрироваться в чужой код
5)
Автодополнение работает на ура
Это весомый плюс? о_О

Теперь еще вопросы
1) как использовать несколько кешей?
2) как использовать многоуровневый кеш?
3) как использовать несколько мемкеш серверов?
4) как быстро заменить один кеш на другой?
//Until php 5.3 we won't have late static binding or some sane way to get static child class name. get_class() fails
protected static $provider = «MemcacheTag»; //So we hardcode it and create new
Вы еще думаете что Синглтон это круто? кстати эта проблема и до 5.3 обходится.
Предпочитаете раздувать класс сомнительной функциональностью, для которой он не предназначался?
Мне не нравится ZF. Это принципиально.
Помимо этого я хочу проще.
Вот, к примеру, я не смог за 10 минут запустить ZC, просто чтоб сравнить их быстродействие. Подозреваю, что решение от Zend будет немного медленнее. Не разобрался. Я на работе и не могу на это тратить много времени. Сравню из дому. Но, думаю, это уже дополнительный минус ZC от меня лично.

1) Возможно овтечу, если вы ответите на четывёртый пункт.
2) Многоуровневый кеш не реализован.
3) Либо добавить вызовы Memcache::AddServer() в конструкторе бэкенда Memcache самому и сейчас, либо дождаться следующего релиза.
4) Если вы про debug_backtrace(), то это не решение. Если знаете лучший способ, поделитесь.

Нет крутых паттернов, есть подходящий инструмент для поставленной задачи. Задача была поставлена как полная инкапсуляция — отсутствие переменных и минимальное присутствие в чужом коде.
Мне не нравится ZF. Это принципиально.

Ну, с таким подходом…

Про late static binding — да, там через debug_backtrace(), но если без одиночек, то такой проблемы просто нет.

На самом деле мне не сильно нужны были ответы, целью вопросов было показать, что синглтон здесь неудобен (ну и остальные просчеты архитектуры).

Нравится вам писать все в синглтонах — пишите, лень дальше продолжать этот спор. Посмотрим, сможет ли библиотека хотя бы полгода прожить, развиваться и получить коммъюнити (что даже по кол-ву отписавшихся тут сомнительно).
Не вижу здесь спора. Вы критикуете, указываете на слабости. Это нормально. Могу только сказать спасибо.
$res = GetMeaningOfLife(true, false, 42);

Fixed.
Да, глупая ошибка с моей строрны)
О блять, очередной говно велосипед. Удите уже и освойте Zend_Cache.
Обновилось. Смотрите конец статьи.
Тадам! Добавляем пару бэкендов для массовки ^_^ Sqlite backend
Source
Source for compare «Test version»

На моей машинке:
Zend: Time: 0.044195s.
Sqlite: Time: 0.640746s.
Забыл, в папку с phpcachetag обязательно должна быть разрешена запись юзеру www-data! Или от него создать -rw файл SqlitecacheTag::C_FILENAME (он же cache.db). Вот.
Вы нумеруете версии 0.х.х просто потому что нравится так или планируется релиз 1.0 через N времени?
Что-то странное. Дефолтный конфиг из свн и следующий код:
error_reporting(E_ALL);
require_once("CacheTag.class.php");

function f()
{
  return 2;
}

CacheTag::SetBackend('Memcache');
CacheTag::SetFunction('f');
CacheTag::SetTags(CacheTag::TAG_PRODUCT);
$res = CacheTag::Fetch().'<br>';

echo $res;



Генерируют вот такую цепочку вызовов:
SetBackend abstract class CacheTag
SetFunction abstract class CacheTag
GetInstance abstract class CacheTag
ImportBackend abstract class CacheTag
Memcache construct
__construct abstract class CacheTag
Memcache Get
SetTags abstract class CacheTag
GetInstance abstract class CacheTag
Fetch abstract class CacheTag
GetInstance abstract class CacheTag
Prepare abstract class CacheTag
GetVarName abstract class CacheTag
Memcache Get
IsRegistered abstract class CacheTag
GetFunctionValue abstract class CacheTag
ApplyTagsChange abstract class CacheTag
Memcache Set
ResetSettings abstract class CacheTag
2
__destruct abstract class CacheTag
SaveResetTags abstract class CacheTag
Memcache Replace



И так при каждом обновлении страницы. Т.е. из кэша ничего не берется.
Я что-то делаю не так?

PS: 5.2.13, memcache 2.2.5.
И еще баг репорт(помимо того, что класс для кеширования не кеширует):

example.php:
Notice: Undefined index: user in CacheTag.class.php on line 290
Warning: array_diff() [function.array-diff]: Argument #1 is not an array in CacheTag.class.php on line 290
Спасибо за репорты. Сейчас гляну.
Планируется релиз. Когда все задуманные вещи на данный момент будут реализованы и количество багов будет сведено до минимума.
Повысил версию. Ищите changelist в конце топика.
Действительно исправлено.
Баг репорт #2.
Пример:
error_reporting(E_ALL);
require_once("CacheTag.class.php");

function f()
{
  return rand(1,1000);
}

CacheTag::SetBackend(CacheTag::BACKEND_MEMCACHE);
CacheTag::SetFunction('f');
CacheTag::SetTags(CacheTag::TAG_PRODUCT);
//CacheTag::SetParam(CacheTag::PARAM_ID, 2);
CacheTag::SetTimeout(0);
$var=CacheTag::Fetch();
echo $var;
CacheTag::Flush();


* This source code was highlighted with Source Code Highlighter.


По-идее лимит времени- 0, в конце стоит очистка кэша. Но я уже пятую минуту обновляю и вижу всё те же 546. Меняются только при перезагрузки memcache.

PS: Система та же, конфиг из свн, код класса и моста для memcache не тронуты.
Собственно более просто: При указании времени=0 берется переменная protected $timeout = 30;
В общем метод Set в MemcacheTag.php показывает следующее(при CacheTag::SetTimeout(1);):
this->memcache_obj->set(product, Array,0, 60 * 1);
this->memcache_obj->set(_resetTags, Array,0, 60 * 0);

И следующее(CacheTag::SetTimeout(0);):
this->memcache_obj->set(product, Array,0, 60 * 31);
this->memcache_obj->set(_resetTags, Array,0, 60 * 0);

И еще вопрос. Зачем у нас при каждом запросе чего-то из кеша происходит два запроса а потом еще и замена?

Т.е. как-то так:
this->memcache_obj->get(_resetTags);
this->memcache_obj->get(product);
а потом мы всё равно:
this->memcache_obj->replace(_resetTags, Array, 0, 0);
Хабрахабр!=форум :)

В общем я бы предложил сделать так:
При добавлении нового элемента в кеш писать в массив с его тегом время истечения жизни обьекта. При удалении выкидывать его из массива.
Таким образом не потребуется никаких replace.
И вызовы будут не
__construct
Getting _resetTags
__get
Getting product
__get
__replace

а

__construct
Getting _resetTags
__get
Getting product
__get

PS: Думаю, это позволит выиграть еще пару микросекунд в синтетике с rand(0,132) и достаточно много времени на реальных проектах. Потому как сравнить время и восстановить обьект из кеша совершенно несопоставимые по сложности и ресурсам операции.
По баг-репорту с CacheTag::Flush():
Дело в том, что все указанные в начале настройки (установки тегов, параметров, времени жизни) сбрасываются при вызове Fetch/Flush. Т.е. у вас Flush() сбрасывает пустоту. По идее у вас будет место в коде, где потребуется конкретно этот элемент кеша сбросить. Вот там-то вы и будете указывать конкретный тег и, возможно, параметры — те же самые, что вы указывали при добавлении. Т.е. тег с параметрами нужно указывать при вызове метода Flush(), так же как и Fetch().

Тайм-лимит подразумевался становиться дефолтовым, если только не передаётся число больше нуля. Но всё же добавил эту возможность.

По поводу лишнего replace:
Вот где собака зарыта. Я упустил вывод дебаговой информации для метода Replace(). Если бы я не допустил этой ошибки, было бы видно, что помимо прочих кешевых транзакций в началае работы скрипта берётся из кеша переменная _resetTags, а в конце она же заменяется/записывается. Не при каждом запросе чего-то из кеша, а в начале и в конце работы всего скрипта. Т.е. +2 к общему количеству запросов. Т.е. должно быть вот так:
__construct
Getting _resetTags
__get

Getting product
__get

Replacing _resetTags
__replace

My bad. Дело было к четырём утра, не уследил.

Как бы то нибыло, я только что исправил этот недочёт и повысли версию. Changelog на привычном месте.
Меня, всё же терзает один момент. Было бы неплохо услышать сторонее мнение.
Если у переменной есть тег, и два параметра. А чуть далее по коду производится удаление по этому тегу и одному из этих параметров. Стоит ли удалять все значения, в которых участвует этот параметр, т.е. вышеописанную переменную? Или оставить это на программиста, мол, если ему нужно групповое удаление, пускай удаляет тегами, либо явно указывает остальные параметры?
Пример из example.php:
//Adding another var (note the new param)
CacheTag::SetFunction('f2');
CacheTag::SetTags(CacheTag::TAG_PRODUCT);
CacheTag::SetParam(CacheTag::PARAM_ID, 2);
CacheTag::SetParam(CacheTag::PARAM_USER, 7);
CacheTag::SetTimeout(0.1);
CacheTag::Fetch();

//Deleting vars tagged with Product and with param id = 2
CacheTag::SetTags(CacheTag::TAG_PRODUCT);
CacheTag::SetParam(CacheTag::PARAM_ID, 2);
CacheTag::Flush();

В текущей реализации кеш не будет обновлён.
Это очень хороший вопрос. Значит отходим от идеи: есть теги(группы), чтобы как-то разграничивать кешированные данные. Также при кешировании можно добавить дополнительные параметры(PARAM_ID,PARAM_USER), чтобы при разном поведении внешнего мира(не дай глобалсы) не происходило клюков кеша. В противном случае мы просто напишем
CacheTag::SetFunction('f2',$arg1,$arg2,$arg3); ну и далее по группам и выводу.

Если я верно понял суть и назначение PARAM_ID и PARAM_USER, то думаю, что явное указание дополнительных параметров обязательно.
Параметры практически обязательны. Используя тег сам по себе, получаем имя переменной из имени тега. К примеру, два безпараметровых тега = две кешевых переменных. А уже параметры это вторичные имена, они разветвляют кешевые переменные. Они же помогают излечить global и прочее, очень верно подмечено.

Изначально я выдавал переменным кеша имена по имени вызываемой функции + переданным в неё параметрам, но вскоре перешёл на кешевые параметры PAR_*, так как они как-раз это и покрывают и дают больше контроля и ясности.
Спасибо за разьяснение. Не подумал бы что параметры функции не учавствуют в названии переменной, помещенной в кэш. Хм. А если не секрет, чем это яснее? Искренне думал, что PAR_ что-то вроде контекстов и просто помогают уточнить какие-то странные штуки вроде global b=(2 || 3) и в зависимости от 2-х или 3-х разные данные запишутся/прочитаются из кеша.
Если представить себе простую функцию, требующую кеширования, то да, это кажется странным. Но если функция подтягивает значения извне, их обязательно нужно указывать дополнительно, для этого придуманы PAR_*. Т.е. использование PAR_* по крайней мере опционально.

Все имена кэшевых переменные хранятся в специальном многомерном массиве, что делает несколько затруднительным однозначное структурирование их по тегу, имени, сгенерированному по функции и параметрам этой функции, а так же неким дополнительным параметрам. Причём параметры функции и дополнительные параметры кэша нужно однозначно разделять, так как они могут пересечся по именам. По-этому я решил сделать использование кэшевых параметров обязательным и полностью отказаться от генерации по функции. Да, приходится писать болше. Но для одного проекта обычно достаточно объявить всего пару параметров, как в дефолтном конфиге — PAR_USER, PAR_ID, (...). В сочетании друг с другом и с тегами они формируют свою смысловую переменную, к примеру: айди продукта(TAG_PRODUCT + PAR_ID), айди коментария (TAG_COMMENT + PAR_ID), страница новостей (TAG_PAGE + PAR_TYPE). Параметры весьма абстрактны, как можно заметить. Из них удобно лепить.

Так же не стоит забывать, что для удаления конкретного элемента из кэша нам придётся всё же его каким-то образом полноценно идентифицировать. А если переменной изначально имя было дано как-то автоматически и не явно, внутри библиотеки, то объяснить кешу что конкретно мы хотим удалить будет весьма непросто.

Т.е. обозначать единицу кеша нужно в любом случае.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации