0 голосов
от (610 баллов) в категории Документация
Почему следует отключить кэширование в PrestaShop и, если очень хочется его использовать, то какие будут советы?

1 Ответ

+1 голос
от (610 баллов)
редактировать от

Вступление

О чём расскажу. Есть базовые системы кэширования в PrestaShop — кэширование скомпилированных файлов Smarty и кэширование SQL-запросов БД. Опции по включению кэширования находятся на странице настроек "Расширенные параметры / Производительность" (см. скриншот).

Эти опции — не волшебные. С их включением придётся мириться или бороться с проблемой некорректного отображения данных. Расскажу причины возникновения этой проблемы, дам советы и варианты решения, как для администратора (владельца) сайта, так и для программиста. Цель статьи — обозначить проблемы кэширования в PrestaShop, раскрыть нюансы реализации систем кэширования без глубокого анализа и помочь пользователям PrestaShop сделать собственный выбор по решению проблем с кэшированием, опираясь на моё экспертное мнение и советы.

Заголовок статьи выбран таким (вопросом, содержащим ответ), исходя из моего понимания, что сегмент PrestaShop — это маленькие интернет-магазины (как зачастую, любит выражаться сама компания - бутики), поэтому напрашивается самое простое и действенное решение — отключение указанных видов кэширования и перенос сайта на производительный хостинг.

Что выходит за рамки статьи. Отдельно есть ещё кэш JavaScript и CSS, как, собственно, и других файлов, а в PrestaShop 1.7 кэша файлов стало очень много, вплоть до файлов перевода. Есть также специальные модули для улучшения кэширования, например, Page Cache Ultimate — в своей основе они используют базовые системы кэширования PrestaShop для шаблонов Smarty и для SQL-запросов БД, и предоставляют собой надстройку над ними.

Кэширование запросов БД

Информация для администраторов сайтов

В PrestaShop эта система была сделана плохо и, по сей день, спустя много лет в лучшую сторону мало, что поменялось. С кэшированием запросов БД наблюдаются постоянные проблемы по части отображения неактуальных данных, как на фронте, так и в админке. Насчёт производительности — часто с ним она не намного вырастет (потому что часто используют дешёвый хостинг, который недостаточно производителен). На слабых хостингах производительность PrestaShop может даже упасть. Здесь, конечно, много зависит от того, как написаны SQL-запросы, ведь помимо SQL-запросов, описанных в ядре PrestaShop, может быть много запросов от модулей, которые написаны без учёта нюансов системы кэширования и с ошибками (например, использование в запросе динамических данных, таких как "текущая метка времени" при сравнении поля с датой). Тем не менее, администратор даже маленького сайта магазинчика столкнётся с отображением неактуальных данных.

Вывод — для большинства случаев, не надо использовать кэширование БД, а лучше арендовать производительный VDS. Вряд ли мы дождёмся корректную, стабильную и производительную подсистему кэширования запросов БД в ближайший 1-2 года точно, а может, никогда (и так багов в PrestaShop 1.7 полно). Вы можете измерить производительность сами на продолжительном тестировании вашего сайта и сделать собственную оценку, нужен ли вам этот вид кэширования или нет.

Информация для программистов

Архитектура подсистемы кэширования в PrestaShop сделана так, что при включённой опции кэширования, кэшируются все запросы, несмотря на то, хочет того программист, или нет *. Также подсистема кэширования по своему алгоритму выбирает, когда ей сбрасывать кэш и сколько времени его хранить. Кроме того, приглядитесь, как система всё это делает (см. класс Cache и дочерние классы) — например, сброс кэша лишь для одной сущности приведёт к сбросу кэша связанных сущностей (то есть, почти никакого контроля и неэффективность кэширования). В добавок к не очень удачному подходу, получили от разработчиков PrestaShop ещё и не очень качественный код, который не закрывает проблемы подхода. Эти все проблемы, помимо неэффективности кэширования, приводят к тому, что в БД данные актуальные, а в кэше — нет. Кроме того, из-за кэша я иногда сталкивался с пока необъяснимым для меня поведением, когда происходило дублирование данных в таблице сущности Configuration, что приводило к другой проблеме — невозможности получения через Configuration::get() ожидаемого значения по интересуемой настройке, а это, в свою очередь, приводило к некорректной работе одного моего модуля (на данный момент, проблему пока не смог воспроизвести).

А что делать, если очень хочется использовать кэширование запросов БД и при этом избежать проблем?

Не всё так плохо.

1. * Если, как программист, вы понимаете, что запрос не нужно кэшировать, то передаёте параметр $use_cache = false в методах Db::execute(), Db::getValue() и прочих.

2. Если, понимаете, что запрос нужно кэшировать, но вам нужно больше свободы по настройке по тому, как долго хранить кэш и, как его самостоятельно сбрасывать без затрагивания данных других сущностей, то нужно использовать класс Cache для создания кэша на конкретные данные, которые долго получаются или обрабатываются. При таком подходе нужно отключить кэширование запросов БД для всего сайта в настройках PrestaShop. Я сделал более удобную обёртку (CacheProvider) над этим классом.

Зачастую я использую CacheProvider для кэширования тех данных, которые требуют долгого получения или обработки, и не важно, касается ли это запросов БД или, например, HTTP-запроса в веб-сервис.

Пример использования CacheProvider:

public function getAllCustomers() {
    $cacheKey = CacheProvider::getKeyName(__METHOD__);

    $data = CacheProvider::getInstance()->get($cacheKey);
    if (false === $data) {
        $data = Db::getInstance()->executeS('SELECT * FROM ps_customer', true, false);
        CacheProvider::getInstance()->set($cacheKey, $data, 60 * 60 * 24);
    }

    return $data;
}

Метод CacheProvider::getInstance() имеет два параметра для удобства выбора системы кэширования (тут имеется в виду способ кэширования и хранения данных — FileCache, Memcache или Memcached). Благодаря этим параметрам можно выбрать для себя более подходящий вариант использования компонента, специфичный для вашего случая. Например, в своих модулях я оставляю параметры по-умолчанию, то есть — использовать способ кэширования, указанный в настройках PrestaShop и использовать файловый кэш, как предпочитаемый способ кэширования в том случае, когда кэширование запросов БД в PrestaShop отключено.

Дополню, зачем мне понадобилась именно такая реализация CacheProvider:  

1. Я всегда буду уверен при вызове метода CacheProvider::getInstance(), что получу экземпляр для работы, а где хранятся данные — мне, по сути, не важно (в файловом кэше, в Memcache или ещё где-то).

2. Чтобы не тянуть другие библиотеки в модуль — проще иметь простую обёртку над классами ядра PrestaShop.

3. Решает все задачи, которые перед мной ставятся (поэтому мне не потребовалось тратить время на изучение и разработку более сложного компонента).

Кэширование скомпилированных файлов Smarty

Информация для администраторов сайтов

С кэшированием скомпилированных файлов Smarty тоже есть нюансы. Не очевидная сложность системы и невнимательность программиста при использовании этой системы приводят к тому, что после изменения данных (или самого файла шаблона), на страницах сайта они не меняются. Есть две причины, из-за которых это происходит: некорректно сформированный ключ кэша для файла шаблона, и вторая — нужно позаботиться о сбросе кэша, что бывает не просто, поэтому программист допускает недоработки. Вывод — не использовать этот вид кэширования, если приводит к отображению неактуальных данных.

Информация для программистов

Уточню (для более широкой аудитории читателей), что файл шаблона Smarty – это файл с расширением tpl, в котором описан HTML-код со специальными языковыми конструкциями Smarty (иногда также включается инлайн код JavaScript и CSS, а также могут быть вызовы статических методов и функций PHP).

Скомпилированный файл Smarty — это файл PHP, а кэш скомпилированного файла — это результат его выполнения, то есть файл HTML.

Объяснение опций Smarty (см. также скриншот в начале).

1. Опция компиляции файлов Smarty в файлы PHP. Суть её — дать знать системе, когда перекомпилировать изменённый файл Smarty.

2. Опция кэширования скомпилированных файлов. Суть её — выполнить скомпилированный файл (PHP), чтобы получить файл HTML, который будет отдаваться "клиенту" без расходов на обработку до тех пор, пока не будет перекомпилирован шаблон Smarty.

Для корректного сброса кэша файла шаблона нужен ключ кэша, который содержит информацию о всех данных из шаблона или, который, если не основан на всех данных, то на таких, чтобы он мог полностью идентифицировать изменения этих данных (см. пример 1). Технически, такой ключ не всегда можно сделать. Также важно, чтобы затраты на генерацию ключа и обслуживание кэша не превосходили в итоге затраты на отрисовку шаблона без использования кэширования. Если такой ключ кэша невозможно корректно сгенерировать, то можно сделать иначе – дополнительно описать метод по сбросу кэша (см. пример 2). Во втором случае — работы нужно сделать больше.

Пример 1: ключ кэша полностью идентифицирует изменения данных.

public function renderBanner(Banner $banner)
{
    // Ключ кэша баннера полностью идентифицирует изменения данных о баннере (условимся, что
    // атрибут date_upd всегда обновляется при изменениях объекта).
    $cacheKey = md5(implode(':', [
        $banner->id,
        $banner->id_lang,
        $banner->date_upd,
    ]));

    $template = 'banner.tpl';
    if (false === $this->isCached($template, $cacheKey)) {
        $this->smarty->assign([
            'banner' => $banner,
        ]);
    }

    return $this->display(__FILE__, $template, $cacheKey);
}

Пример 2: ключ кэша не полностью идентифицирует изменения данных.

public function renderPage(Page $page)
{
    // Ключ кэша страницы не полностью идентифицирует изменения данных о странице,
    // поэтому нужно ещё предусмотреть сброс кэша: см. getCacheKey() и hookActionPageUpdate().
    $cacheKey = $this->getCacheKey([
        $page->id,
        $page->id_lang,
    ]);

    $template = 'page.tpl';
    if (false === $this->isCached($template, $cacheKey)) {
        $this->smarty->assign([
            'page' => $page,
        ]);
    }

    return $this->display(__FILE__, $template, $cacheKey);
}

private function getCacheKey(array $params)
{
    return md5(implode(':', $params));
}

public function hookActionPageUpdate(array $params)
{
    // Делаем сброс кэша для страницы, которая была изменена (условимся, что метод
    // hookActionPageUpdate запускается всегда, когда происходит изменение данных о странице)
    $this->_clearCache('page.tpl', $this->getCacheKey([
        $params['pageId'],
        $params['languageId'],
    ]));
}

Примечание: объекты типа Banner и Page – выдуманные; метод hookActionPageUpdate() – тоже; не надо надеяться, что вы скопируете код и он сразу заработает — это примеры, чтобы была понятна суть подходов ;)

Примеров, как делать не надо — полно. Фактически можете открыть любой модуль, включая те, которые сделали разработчики PrestaShop ;) Пример, не из плохих, но показательный — модуль BlockBestSellers — кэширование и его сброс реализовали, но не сделали так, чтобы кэш жил до тех пор, пока он остаётся актуальным, что менее эффективно, потому что сбрасываться он будет даже тогда, когда не было изменения конкретных данных. Иногда попадается реализация чрезмерного кэширования — это тогда, когда создают кэш на множественные варианты отображения одного и того же объекта, который (кэш) редко используется. Если делать по таким примерам, то получите вычислительные затраты на постоянную перегенерацию кэша без реального профита от его применения.

Заключение

Опции настройки PrestaShop по включению кэширования — не волшебные. С их включением придётся мириться или бороться с некорректным отображением данных. Решается эта проблема нажатием другой волшебной кнопки — сбросом всего кэша на сайте, что является довольно частым действием для администратора сайта ;) По факту, увеличение производительности, за счёт указанных видов кэширования, работающих из коробки PrestaShop, может быть несущественным, потому что принесёт существенные проблемы с некорректным отображением данных. Выбирайте, что вам подходит больше при ваших условиях. А мой совет такой — самое простое и действенное решение — это отключение указанных видов кэширования, чтобы избавиться от проблем, связанных с неактуальными данными и перенос сайта на производительный VDS. Он обойдется вам, скорее всего, дешевле работы программиста.

Чтобы использовать базовые возможности кэширования PrestaShop эффективно, необходимо много внимания программиста к сайту, это требует постоянную работу с кодом и повышенное внимание к тестированию. Наибольший профит по производительности сайта можно получить от правильного кэширования шаблонов Smarty для блоков контента, которые наиболее часто запрашиваются на сайте. Поэтому совет такой — начинайте оптимизацию производительности сайта с кэширования таких блоков. Затем, смотрите, какая обработка занимает больше всего времени — если медленно производится получение или обработка каких-то данных (из БД или другого сервиса), то применяйте кэширование итоговых данных (здесь будет полезен мой CacheProvider). С ростом посещаемости сайта, работу по оптимизации повторяйте для появившихся новых узких мест.

Добро пожаловать на сайт. Здесь можно задавать вопросы по CMS PrestaShop и получать на них ответы.
...