MODX Revolution. Оптимизация и кэширование
Эта тема уже не раз поднималась. Хочу поделится своим опытом оптимизации более менее высоко нагруженного сайта на MODX Revolution.
Статистика и цифры
Посещаемость ресурса (далее сайта), о котором идет речь, в сутки более 6 тысяч уникальных посетителей. В пики около 20 тысяч.
История
Изначально сайт работал на MODX Evolution с 2008 по 2013 года. В середине 2013 нужно было реализовать ещё один похожий сайт, но с некоторыми общими элементами. После некоторого раздумья решили оставить Evo в покое и сделать новый сайт на MODX Revolution. На это было несколько весомых причин:
- Контексты(мультидоменность). Так как вместо одного сайта нужно делать два, но с общими элементами, то тут без контекстов в Revo не обойтись. В Evo такого функционала нет.
- Регулярные обновления ядра. Evo заброшен официальными разработчиками и давно не обновляется. В Revo с этим проблем нет.
Разработка нового сайта
Большая часть функционала была реализована своими сниппетами или сниппетами-обертками готовых компонентов. Сниппеты приходилось вызывать некешируемыми, так как было много блоков, которые выводят рандомную информацию. В итоге среднее время генерации страницы было ~0.2-0.4. Но были и большие страницы, которые отдавались около секунды. Кто-то скажет что 0.3-0.4 это нормально, но нас это не устраивало. Посещаемый сайт должен работать гораздо быстрее.
Оптимизация
Ищя «тяжелые» места на сайте, сразу же нашелся виновник. Это была правая колонка.
В ней была куча вызовов других сниппетов и причем вызовы были некешируемыми.
В шаблоне было примерно так:
<!DOCTYPE HTML> <html> <head> <title>title</title> </head> <body> [[!right_column]] [[*content]] </body> </html>
А сниппет right_column выглядел примерно следующим образом:
[[!snippet1? ¶m1=`1` ¶m2=`2`]] [[!snippet2? ¶m1=`1` ¶m2=`2`]] [[!snippet3? ¶m1=`1` ¶m2=`2`]] [[!snippet4? ¶m1=`1` ¶m2=`2`]] [[!snippet5? ¶m1=`1` ¶m2=`2`]]
То есть парсеру MODX приходится трудится и обрабатывать вызовы вложенных сниппетов.
Попробовали переделать правую колонку полностью на php. Получилось что то вроде:
$output = ''; $output .= $modx->runSnippet('snippet1', array('param1' => 1, 'param2' => 1,)); $output .= $modx->runSnippet('snippet2', array('param1' => 1, 'param2' => 1,));
Но выигрыша это в скорости не особо дало. Тогда пошли другим путем и вынесли всю логику в один общий сниппет. Тогда разница была ощутима.
Идем дальше. Помните я говорил про рандомные блоки и что их нельзя кешировать?
Мы придумали другой способ кеширования.
Смысл такой: например нам нужно выводить 5 случайных статей из раздела «Статьи». Тогда получаем 5 статей запросом с сортировкой при помощи rand() и записываем эти 5 статей в кеш, например, на 5 минут. И при выводе перемешиваем их на php при помощи функции shuffle. Тогда запрос в бд будет всего один раз в 5 минут. А пользователь даже ничего не заподозрит, что меняются всего 5 элементов :)
Пример такого сниппета:
$cache_handler = $modx->getOption('cache_handler', null, 'xPDOFileCache'); $cache_lifelime = 300; $cacheOptions = array( xPDO::OPT_CACHE_KEY => 'right_column', xPDO::OPT_CACHE_HANDLER => $cache_handler, ); $items = array(); if(($results = $modx->cacheManager->get('articles' . $modx->context->key, $cacheOptions))){ $items = $results; }else{ $query = $modx->newQuery('modResource') ->where(array('parent' => 1)) ->select($modx->getSelectColumns('modResource', 'modResource', '', array('id', 'uri', 'pagetitle', 'introtext')) ) ->limit(5) ->sortby('rand()'); if($query->prepare() && $query->stmt->execute()){ if ($results = $query->stmt->fetchAll(PDO::FETCH_ASSOC)) { $items = $results; } } $modx->cacheManager->set('articles' . $modx->context->key, $items, $cache_lifelime, $cacheOptions); } if(is_array($items) && !empty($items)){ // работаем с массивом $items }
Ещё следует заметить, что хороший прирост в скорости дает отключение галочки «Статичный» у всех элементов(шаблоны, чанки, сниппеты...). Спасибо Евгению Борисову за ценную информацию о вредной статичности.
Итог
После проведения работ по оптимизации, скорость загрузки выросла до ~0.04-0.08.