Pull to refresh

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, т.к. он лежит на поверхности.
вы ведь прекрасно знаете зачем придумали регулярные выражения…
чтобы описывать легко СЛОЖНЫЕ шаблоны.
проверить что строка начинается со слова «cached_» — это не сложный шаблон, и логично использовать обычные строковые функции.

быть логичным или нет — каждый выбирает сам)
$method = 'cached_method';
if (strtok($method, '_') == 'cached') echo 'а метод-то: '.strtok('_');
Ну во первых читал на одном из форумов про медлительность substr. Парень утверждает, что ускорил работу шаблонизатора Смарти в 5 раз заменив где только можно substr на preg_match.
Ну и во вторых это просто эстетичнее выглядит на мой взгляд. Люблю регулярные выражения.
А Вы сами пробовали тестировать? Не знаю, как там в шаблонизаторах, а у меня на скорую руку substr() примерно в полтора раза быстрее отрабатывает, чем preg_match().
А насчет эстетичности и любви к регуляркам — тут Вы противоречите стремлению к оптимизации. Да и несерьезно это, чистые эмоции ;) Про substr() забывать не стоит.
Это утверждение противоречит всем тестам сравнения производительности substr и preg_match при равных условиях. Советую вам усомниться в результатах опубликованных на форуме и провести свои.
Да, я уже понял. Просто при написании кода не задумался даже… Прочитал, в памяти отложилось и как аксиома…
гениально =) причину тормозов смарти надо всегда искать в нативных php`шных функциях для работы со строками =)
а кеш получается вечный? Где задается время жизни кеша?
Ведь для разных моделей нужно будет разное время обновления информации
При первой инициализации $cache создаётся объект Zend_Cache в котором прописывается время жизни кэша. Время жизни в данном примере не вечное, но для всех моделей одинаковое. Спасибо за комментарий — наду будет доработать, но вообще нужные кэш-файлы чистятся при определённых событиях и необходимости автоматической очистки кэша у меня пока не возникало.
Идея конечно хорошая, сам к этому пришел. Но реализация…

Зачем же существует ActiveRecord?! И словосочетание ObservedCache вам не о чем не говорит?
Что-то гугл тоже не знает про «словосочетание ObservedCache». Может конечно не так искал.

Расскажите?
Создаем класс, который будет следить за моделями.
Само отправило… Странно… Так вот пример.
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));?
Ой, да… Извиняюсь. Первоначально у меня вместо print_r(), было implode()… Название функции исправил, а параметры видимо забыл изменить.
лучше таки serialize, а не print_r, учитывая, что:
This function<print_r> uses internal output buffering with this parameter so it can not be used inside an ob_start() callback function.
По мне так, довольно порочная практика. Потому как мы при работе с моделью должны держать в голове специфику ее работы: как она хранит данные, что случается за интерфейсом при вызове метода. Т.е. согласитесть, $model->cached_insert($row) будет работать уже совсем не так, как хотелось бы. Плюс кастомные методы модели, например, $employees->payTo($id)… А что будет если мы вызовем cached_payTo($id)? Т.е. логика работы с данными выносится за рамки класса. Мне кажется, что модель сама должна определять, что можно кэшировать, а что нет. Хотя для мелких проектов с парой-тройкой контроллеров может быть так и удобнее.
Хмм… Наверное соглашусь с Вами, что для крупных проектов данный подход не очень удобен. Хотя мне кажется программист в здравом уме не будет кэшировать метод с именем insert()… Да и любой другой, если не знает что за ним лежит. Получается есть свобода, а как ей пользоваться это уже от программиста зависит.
Так вот, порочная практика — это полагаться на здравый ум программиста… Как показывает опыт — это последнее на что можно полагаться, особенно если работаешь не один :))

Модель-обсервер не будет работать (кто-то хочет оповещать других о получении данных), или модель которая работает с каким-нибудь сингелтоном (тот же Zend_Registry)… Не знаю даже, что еще можно притянуть. :))) Вообщем, при росте проекта можно получить небольшой гемор (а куда без него), а так вполне нормальное решение.
С вероятностью в 99%, если интерфейс позволяет, программисту это обязательно когда нибудь понадобиться. Так, что делайте интерфейс проще и безопасней. Хотя не вижу ничего плохого в кешировании метода insert, который возвращает последний добавленный id — очень удобно кстати.
Вы меня заинтересовали.
Объясните, пожалуйста, простым смертным что «неплохого» в кешировании метода insert?
Кешировать insert не лучшая идея. Думаю не стоит развивать эту тему дальше.
Подсказок на 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 методов.
По поводу phpDoc'ов соглашусь, но неудобств не много.
1. Пишете $this->life->
2. Вылетает подсказка с доступными методами и их описанием
3. Выбираем нужный и дописываем префикс

Зато в минусы я отношу дублирование кода для каждого метода и ручное определение ключей(которые Вы отнесли к плюсам). Зная по какому алгоритму даются ключи, можно легко манипулировать очисткой, а в вашем случае каждый конкретный метод может иметь свой алгоритм.
Что ж, во многом это дело вкуса. Я предпочитаю явный интерфейс, вы — магические штучки ;) So be it.

Насчет производительности: в нашем проекте сейчас используется подход, очень похожий на ваш (правда, я надеюсь его изменить), так вот, сильных тормозов не наблюдаем, несмотря даже на использование 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
я думаю что стоит сделать флаг для кэширования в бутстрапе можно его в Zend_Registry записать ещё.
И включать и выключать кэширование по этому флагу и оставить метод fetchAll для всего

Кстати не замечали ли вы что при одном человеке на сайте кэширование работает медленне чем напрямую из базы данных? я думаю что это связанно с сериализацией/ десериализацей объектов которые хранятся в кэше.

Можно составить графики скорости выполнения sql запросов в зависимости от кол — ва пользователей. И в том месте где уже эффективнее использовать кэширование устанавливать флаг в true.
почему кэш ($argHash) зависит от имени модели и метода, а не от внутреннего состояния?

т.е. для двух новостей всегда будет возвращаться один и тот же результат?

$cache->load('model_'.$className.'_'.$methodName[1].'_'.$argHash)

в этой строке совсем никак не учтен PK сущности.
а, пардон, вы же кешируете массив сущностей, дописывая прокладку к тейбл адаптеру, сори…
preg_match('/^cached_(.+)$/',....)
следовало бы заменить бы на:
strpos(...)

ps: ровно то же самое реализовывал ~ 1.5 года назад, только вместо print_r($args,1) применял serialize(), а дата создания кеша сохранялась в дату модификации файла.
и еще я немного не понял, вы заведомо собираетесь по всей системе писать либо вызовы с кешируемыми методами либо нет. То есть возможность отключить кеширование одной настройкой не выйдет, либо постоянно будут костыли вида
if($cacheEnabled){
$result = $this->_life->getAll('Now!!');
}else{
$result = $this->_life->cached_getAll('Now!!');
}

нужно додумать что-то универсальное, зависящее от конфига, с возможностью задания времени актуальности кеша (не знаю есть ли в Zend такая возможность)
Муть, кешированием данных должна управлять каждая модель сама, т к она лучше знает что это за данные, как часто они обновляются и т.д. И просто класть в кеш на определенное время данные — неэффективно, ведь они вполне могут уже обновиться в БД. Про возможность кеширования стоило думать до разработки проекта, а не после, теперь вы вынуждены будете пристраивать уродливые неэффективные костыли.
// И кстати отказ от Zend Framework думаю тоже мог бы ускорить работу :)
ускорить работу кода или программистов?
Является ли метод потенциально кэшируемым должен определять не программер, класс использующий, а сам метод.

Т.е., как указывалось выше, надо в самом начале детерминированного метода (без side effect'ов и, не зависящий от окружения) поставить проверку на наличие результата в кэше и возврат его в случае успеха.

Чтобы не заниматься копипэйстом эту функциональность можно выделить в одно место. И это, кстати, скорее будет метод My_Cache, принимающий на вход callback (хотя его и из стека можно вытащить) и arguments. Кстати, хорошая идея для доп. функциональности Zend_Cache :)

PS Но что меня все-таки смущает, это уникальность хэша.
Чем этот вариант лучше использования Zend_Cache_Frontend_Class?
Идея хорошая, но, по-моему, автор придумал велосипед.
Причем разработчики 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.

Articles