Comments 55
А зачем работать с кешэм явно?
Т.е. зачем это разделение на обычный и cached_ методы?
Есть же методы, которые не нуждаются в кэшировании (например какой-нибудь метод генерации псевдослучайной величины на основании поступивших данных). Наверняка можно придумать метод, который в одном месте вызова желательно кэшировать, а в другом нет. При этом по имени в контроллере сразу видно какие методы кэшируются, а какие нет. Да и в префиксе суть метода. Благодаря ему срабатывает метод __call.
Вся логика кэширования в одном месте… никаких лишних телодвижений при написании нового метода. Тэги и имя кэша раздаются следуя одной конвенции, что делает возможным легко и гибко чистить кэш. Если вас смущает длинный префикс, можно сократить например до «с_».
Кстати, забыл упомянуть, что данный способ кэширования разрабатывался для движка, который позволит максимально быстро писать промо-сайты. Необходимо это для поднятия в короткие сроки фирмы на ноги.
Я не пытаюсь развязать холивар. Наоборот хочу узнать — какие вы видите недостатки способа.
Вся логика кэширования в одном месте… никаких лишних телодвижений при написании нового метода. Тэги и имя кэша раздаются следуя одной конвенции, что делает возможным легко и гибко чистить кэш. Если вас смущает длинный префикс, можно сократить например до «с_».
Кстати, забыл упомянуть, что данный способ кэширования разрабатывался для движка, который позволит максимально быстро писать промо-сайты. Необходимо это для поднятия в короткие сроки фирмы на ноги.
Я не пытаюсь развязать холивар. Наоборот хочу узнать — какие вы видите недостатки способа.
Зачем использовать preg_match() для поиска префикса 'cached_', если достаточно использовать substr()?
я так понял что preg_match() в данном случае нужен не столько для поиска, сколько для выкусывания того, что идет после 'cached_'
$parts = explode('_', $name); if ('cached'==$parts[0] && method_exists($this,$parts[1]) ) {
?
Ну или if (substr($name, 0, 7) == 'cached_') { $method = substr($name, 7);… }
2 ворнинга при обращении к несущетвующим индексам массива, если имя не содержит '_'.
$parts = explode('_', $name); if (sizeof($parts)==2 && 'cached'==$parts[0] && method_exists($this,$parts[1]) ) {
)
Может быть поэтому используют preg_match, т.к. он лежит на поверхности.
$method = 'cached_method';
if (strtok($method, '_') == 'cached') echo 'а метод-то: '.strtok('_');
if (strtok($method, '_') == 'cached') echo 'а метод-то: '.strtok('_');
Ну во первых читал на одном из форумов про медлительность substr. Парень утверждает, что ускорил работу шаблонизатора Смарти в 5 раз заменив где только можно substr на preg_match.
Ну и во вторых это просто эстетичнее выглядит на мой взгляд. Люблю регулярные выражения.
Ну и во вторых это просто эстетичнее выглядит на мой взгляд. Люблю регулярные выражения.
А Вы сами пробовали тестировать? Не знаю, как там в шаблонизаторах, а у меня на скорую руку substr() примерно в полтора раза быстрее отрабатывает, чем preg_match().
А насчет эстетичности и любви к регуляркам — тут Вы противоречите стремлению к оптимизации. Да и несерьезно это, чистые эмоции ;) Про substr() забывать не стоит.
А насчет эстетичности и любви к регуляркам — тут Вы противоречите стремлению к оптимизации. Да и несерьезно это, чистые эмоции ;) Про substr() забывать не стоит.
Это утверждение противоречит всем тестам сравнения производительности substr и preg_match при равных условиях. Советую вам усомниться в результатах опубликованных на форуме и провести свои.
гениально =) причину тормозов смарти надо всегда искать в нативных php`шных функциях для работы со строками =)
а кеш получается вечный? Где задается время жизни кеша?
Ведь для разных моделей нужно будет разное время обновления информации
Ведь для разных моделей нужно будет разное время обновления информации
При первой инициализации $cache создаётся объект Zend_Cache в котором прописывается время жизни кэша. Время жизни в данном примере не вечное, но для всех моделей одинаковое. Спасибо за комментарий — наду будет доработать, но вообще нужные кэш-файлы чистятся при определённых событиях и необходимости автоматической очистки кэша у меня пока не возникало.
Идея конечно хорошая, сам к этому пришел. Но реализация…
Зачем же существует ActiveRecord?! И словосочетание ObservedCache вам не о чем не говорит?
Зачем же существует ActiveRecord?! И словосочетание ObservedCache вам не о чем не говорит?
Что-то гугл тоже не знает про «словосочетание ObservedCache». Может конечно не так искал.
Расскажите?
Расскажите?
Создаем класс, который будет следить за моделями.
Само отправило… Странно… Так вот пример.
По строчкам
observe List, Item — модели, за которыми он следит
after_save(record) — функция, выполняется если хотя бы одна модел создалась или обновилась
list = record.is_a?(List)? record: record.list — обновилась одна запись или список
expire_page — очистка страничного кеша
expire_action — очистка активного кеша
list.shares.each { |share| expire_page(:controller => «lists», :action => «show», :id => share.url_key) } — очистка группы страниц
class ListSweeper < ActionController::Caching::Sweeper
observe List, Item
def after_save(record)
list = record.is_a?(List) ? record : record.list
expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
expire_action(:controller => "lists", :action => "all")
list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
end
end
По строчкам
observe List, Item — модели, за которыми он следит
after_save(record) — функция, выполняется если хотя бы одна модел создалась или обновилась
list = record.is_a?(List)? record: record.list — обновилась одна запись или список
expire_page — очистка страничного кеша
expire_action — очистка активного кеша
list.shares.each { |share| expire_page(:controller => «lists», :action => «show», :id => share.url_key) } — очистка группы страниц
1) The call_user_method_array() function is deprecated as of PHP 4.1.0.
2) $argHash = md5(print_r('',$arguments));
может $argHash = md5(print_r($arguments,true));?
2) $argHash = md5(print_r('',$arguments));
может $argHash = md5(print_r($arguments,true));?
Ой, да… Извиняюсь. Первоначально у меня вместо print_r(), было implode()… Название функции исправил, а параметры видимо забыл изменить.
По мне так, довольно порочная практика. Потому как мы при работе с моделью должны держать в голове специфику ее работы: как она хранит данные, что случается за интерфейсом при вызове метода. Т.е. согласитесть, $model->cached_insert($row) будет работать уже совсем не так, как хотелось бы. Плюс кастомные методы модели, например, $employees->payTo($id)… А что будет если мы вызовем cached_payTo($id)? Т.е. логика работы с данными выносится за рамки класса. Мне кажется, что модель сама должна определять, что можно кэшировать, а что нет. Хотя для мелких проектов с парой-тройкой контроллеров может быть так и удобнее.
Хмм… Наверное соглашусь с Вами, что для крупных проектов данный подход не очень удобен. Хотя мне кажется программист в здравом уме не будет кэшировать метод с именем insert()… Да и любой другой, если не знает что за ним лежит. Получается есть свобода, а как ей пользоваться это уже от программиста зависит.
Так вот, порочная практика — это полагаться на здравый ум программиста… Как показывает опыт — это последнее на что можно полагаться, особенно если работаешь не один :))
Модель-обсервер не будет работать (кто-то хочет оповещать других о получении данных), или модель которая работает с каким-нибудь сингелтоном (тот же Zend_Registry)… Не знаю даже, что еще можно притянуть. :))) Вообщем, при росте проекта можно получить небольшой гемор (а куда без него), а так вполне нормальное решение.
Модель-обсервер не будет работать (кто-то хочет оповещать других о получении данных), или модель которая работает с каким-нибудь сингелтоном (тот же Zend_Registry)… Не знаю даже, что еще можно притянуть. :))) Вообщем, при росте проекта можно получить небольшой гемор (а куда без него), а так вполне нормальное решение.
С вероятностью в 99%, если интерфейс позволяет, программисту это обязательно когда нибудь понадобиться. Так, что делайте интерфейс проще и безопасней. Хотя не вижу ничего плохого в кешировании метода insert, который возвращает последний добавленный id — очень удобно кстати.
Подсказок на cached_* методы вы, скорее всего, лишитесь (если ваша IDE не поддерживает @method или если программисты не будут очень дисциплинированы).
Лично я предпочитаю явно объявлять все методы. Мой вариант выглядел бы примерно так:
Лично я предпочитаю явно объявлять все методы. Мой вариант выглядел бы примерно так:
function getXyz() {} function getXyzCache() { if (!$result = $cache->load('можем по-человечески определить ключ')) { $result = $ths->getXyz(); $cache->save($result, 'key' ); } }<pre> Бонус - со значением, которое кладется в или вынимается из кэша - можно провести еще какие-то манипуляции, если это нужно. А условие if (!$result = $cache->load()) - можно при желании вынести в дополнительную прослойку:
черт, отправилось раньше…
прослойка, примерно:
Можно и ОО-решение, само собой.
прослойка, примерно:
function cache_get_and_store($key, $callback){ if (!$result = $cache->load($key)) { // достаем из базы или откуда там надо, ну и сохраняем, знамо дело. } }
Можно и ОО-решение, само собой.
Для сеттеров и геттеров в PHP и были добавлены magic methods.
Конечно IDE не подскажет о их существовании, но взамен вы получаете чистый интерфейс без нескольких десятков set, get методов.
Конечно IDE не подскажет о их существовании, но взамен вы получаете чистый интерфейс без нескольких десятков set, get методов.
По поводу phpDoc'ов соглашусь, но неудобств не много.
1. Пишете $this->life->
2. Вылетает подсказка с доступными методами и их описанием
3. Выбираем нужный и дописываем префикс
Зато в минусы я отношу дублирование кода для каждого метода и ручное определение ключей(которые Вы отнесли к плюсам). Зная по какому алгоритму даются ключи, можно легко манипулировать очисткой, а в вашем случае каждый конкретный метод может иметь свой алгоритм.
1. Пишете $this->life->
2. Вылетает подсказка с доступными методами и их описанием
3. Выбираем нужный и дописываем префикс
Зато в минусы я отношу дублирование кода для каждого метода и ручное определение ключей(которые Вы отнесли к плюсам). Зная по какому алгоритму даются ключи, можно легко манипулировать очисткой, а в вашем случае каждый конкретный метод может иметь свой алгоритм.
Что ж, во многом это дело вкуса. Я предпочитаю явный интерфейс, вы — магические штучки ;) So be it.
Насчет производительности: в нашем проекте сейчас используется подход, очень похожий на ваш (правда, я надеюсь его изменить), так вот, сильных тормозов не наблюдаем, несмотря даже на использование serialize.
Насчет производительности: в нашем проекте сейчас используется подход, очень похожий на ваш (правда, я надеюсь его изменить), так вот, сильных тормозов не наблюдаем, несмотря даже на использование serialize.
Также вместо print_r можно использовать serialize для приведения массива к строке
Будет работать намного медленнее.
Неправда:
Вот тест на 10000 проходах для массива из 1000 строк+1000 чисел, случайно перемешанных.
array(3) {
[«serialize»]=>float(14.9195878506)
[«json»]=> float(15.6144771576)
[«print_r»]=> float(28.0943210125)
}
gist.github.com/743578
Вот тест на 10000 проходах для массива из 1000 строк+1000 чисел, случайно перемешанных.
array(3) {
[«serialize»]=>float(14.9195878506)
[«json»]=> float(15.6144771576)
[«print_r»]=> float(28.0943210125)
}
gist.github.com/743578
я думаю что стоит сделать флаг для кэширования в бутстрапе можно его в Zend_Registry записать ещё.
И включать и выключать кэширование по этому флагу и оставить метод fetchAll для всего
Кстати не замечали ли вы что при одном человеке на сайте кэширование работает медленне чем напрямую из базы данных? я думаю что это связанно с сериализацией/ десериализацей объектов которые хранятся в кэше.
Можно составить графики скорости выполнения sql запросов в зависимости от кол — ва пользователей. И в том месте где уже эффективнее использовать кэширование устанавливать флаг в true.
И включать и выключать кэширование по этому флагу и оставить метод fetchAll для всего
Кстати не замечали ли вы что при одном человеке на сайте кэширование работает медленне чем напрямую из базы данных? я думаю что это связанно с сериализацией/ десериализацей объектов которые хранятся в кэше.
Можно составить графики скорости выполнения sql запросов в зависимости от кол — ва пользователей. И в том месте где уже эффективнее использовать кэширование устанавливать флаг в true.
почему кэш ($argHash) зависит от имени модели и метода, а не от внутреннего состояния?
т.е. для двух новостей всегда будет возвращаться один и тот же результат?
$cache->load('model_'.$className.'_'.$methodName[1].'_'.$argHash)
в этой строке совсем никак не учтен PK сущности.
т.е. для двух новостей всегда будет возвращаться один и тот же результат?
$cache->load('model_'.$className.'_'.$methodName[1].'_'.$argHash)
в этой строке совсем никак не учтен PK сущности.
preg_match('/^cached_(.+)$/',....)
следовало бы заменить бы на:
strpos(...)
ps: ровно то же самое реализовывал ~ 1.5 года назад, только вместо print_r($args,1) применял serialize(), а дата создания кеша сохранялась в дату модификации файла.
следовало бы заменить бы на:
strpos(...)
ps: ровно то же самое реализовывал ~ 1.5 года назад, только вместо print_r($args,1) применял serialize(), а дата создания кеша сохранялась в дату модификации файла.
и еще я немного не понял, вы заведомо собираетесь по всей системе писать либо вызовы с кешируемыми методами либо нет. То есть возможность отключить кеширование одной настройкой не выйдет, либо постоянно будут костыли вида
if($cacheEnabled){
$result = $this->_life->getAll('Now!!');
}else{
$result = $this->_life->cached_getAll('Now!!');
}
нужно додумать что-то универсальное, зависящее от конфига, с возможностью задания времени актуальности кеша (не знаю есть ли в Zend такая возможность)
if($cacheEnabled){
$result = $this->_life->getAll('Now!!');
}else{
$result = $this->_life->cached_getAll('Now!!');
}
нужно додумать что-то универсальное, зависящее от конфига, с возможностью задания времени актуальности кеша (не знаю есть ли в Zend такая возможность)
Муть, кешированием данных должна управлять каждая модель сама, т к она лучше знает что это за данные, как часто они обновляются и т.д. И просто класть в кеш на определенное время данные — неэффективно, ведь они вполне могут уже обновиться в БД. Про возможность кеширования стоило думать до разработки проекта, а не после, теперь вы вынуждены будете пристраивать уродливые неэффективные костыли.
// И кстати отказ от Zend Framework думаю тоже мог бы ускорить работу :)
// И кстати отказ от Zend Framework думаю тоже мог бы ускорить работу :)
Является ли метод потенциально кэшируемым должен определять не программер, класс использующий, а сам метод.
Т.е., как указывалось выше, надо в самом начале детерминированного метода (без side effect'ов и, не зависящий от окружения) поставить проверку на наличие результата в кэше и возврат его в случае успеха.
Чтобы не заниматься копипэйстом эту функциональность можно выделить в одно место. И это, кстати, скорее будет метод My_Cache, принимающий на вход callback (хотя его и из стека можно вытащить) и arguments. Кстати, хорошая идея для доп. функциональности Zend_Cache :)
PS Но что меня все-таки смущает, это уникальность хэша.
Т.е., как указывалось выше, надо в самом начале детерминированного метода (без side effect'ов и, не зависящий от окружения) поставить проверку на наличие результата в кэше и возврат его в случае успеха.
Чтобы не заниматься копипэйстом эту функциональность можно выделить в одно место. И это, кстати, скорее будет метод My_Cache, принимающий на вход callback (хотя его и из стека можно вытащить) и arguments. Кстати, хорошая идея для доп. функциональности Zend_Cache :)
PS Но что меня все-таки смущает, это уникальность хэша.
Чем этот вариант лучше использования Zend_Cache_Frontend_Class?
Идея хорошая, но, по-моему, автор придумал велосипед.
Причем разработчики Zend-а придумали его лучше. Выше уже кто-то писал: логика кеширования должна быть инкапсулированна внутри класса.
Идея хорошая, но, по-моему, автор придумал велосипед.
Причем разработчики Zend-а придумали его лучше. Выше уже кто-то писал: логика кеширования должна быть инкапсулированна внутри класса.
Zend_Cache_Frontend_Function и Zend_Cache_Frontend_Class не подошли? Почему?
Я бы посоветовал вам прогнать профайлером свой код и определить узкие места, которые необходимо закэшировать или просто определить их методом здравого смысла. На мой взгяд не нужно кэшировать все методы модели. Непосредственно в методах модели, в табличном гейтвее как у вас или вообще на уровне гейтвея в домен, прописать логику кэширования. Для удобвства можно вынести настройки в конфигурационный файл, (лучше чем в бутстрапер).
Таким образом у вас получается легко конфигурируемая система кэширования для компонента + чистый интерфейс модели, где вам не нужно много думать «как оно там внутри кэшируется» вы просто вызываете одни и те же ясно именованые методы которые уже сами знают как себя кэшировать и просто возвращают данные.
Таким образом у вас получается легко конфигурируемая система кэширования для компонента + чистый интерфейс модели, где вам не нужно много думать «как оно там внутри кэшируется» вы просто вызываете одни и те же ясно именованые методы которые уже сами знают как себя кэшировать и просто возвращают данные.
Можно использовать следующий подход: создаем класс, который будет кешировать экземпляры классов:
В контроллере это используется так:
<?php class App_Cache { public static $frontendName = 'Class'; public static $backendName = 'File'; public static $frontendOptions; public static $backendOptions = array('cache_dir' => CACHE_DIR); static public function get($class) { $instance = new $class; self::$frontendOptions = array( 'cached_entity' => $instance ); return Zend_Cache::factory(self::$frontendName, self::$backendName, self::$frontendOptions, self::$backendOptions); } }
В контроллере это используется так:
$modelPages = App_Cache::get('Model_Page'); $list = $modelPages->getList();
Sign up to leave a comment.
Интересный подход для кэширования моделей