Ускоряем Joomla в 1000 раз

    Целевая аудитория: программисты, администраторы Joomla и другие пользователи имеющие элементарные навыки работы с PHP.

    Joomla — медленная, очень медленная. Joomla «из коробки» редко может выдавать более 4 запросов в секунду. Включим кеш, поставим PHP accelerator, займемся оптимизацией и возможно мы сможем получить 20 запросов в секунду.

    А что дальше, менять CMS? Конечно менять, но слишком часто пользователи просятся назад на Joomla. Сразу оговорюсь, задача статьи не повлиять на выбор CMS, обсуждать скорости работы различных CMS решений или недостатки архитектуры Joomla.

    Данные рекомендации будут полезны, если:
    — у вас или вашего клиента есть вебсайт на Joomla
    — ваш сайт состоит в основном из статического контента
    — вам недостаточно получать от сервера 20 запросов в секунду, вам надо 2000-3000.
    — вы задумались насколько время отклика вебсайта влияет на рейтинг в google. Немного информации на эту тему тут googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-search-ranking.html

    Joomla работает под PHP, даже если используется кеширование на уровне модулей Joomla, все равно используется PHP и происходит рендеринг страницы при каждом обращении. Это означает, что скорости в 2000 запросов в секунду будут недостижимы. Заставим Joomla сохранять копию сгенерированной страницы на диск, а Apache считывать эту копию напрямую, минуя PHP для всех последующих пользователей. Это старый добрый подход, который кстати может быть применен на других CMS, но только для статического контента. На Joomla используем тип Article, который как раз и представляет страницу со статическим контентом.

    Создаем новый плагин для Joomla, всего два файла:

    htmlcache.xml — файл описания плагина
    <?xml version="1.0" encoding="utf-8"?>
    <install version="1.5" type="plugin" group="system">
    <name>System - HTML cache</name>
    <author></author>
    <creationDate>March 2010</creationDate>
    <copyright></copyright>
    <license>www.gnu.org/licenses/gpl-2.0.html GNU/GPL</license>
    <authorEmail></authorEmail>
    <authorUrl></authorUrl>
    <version>0.6</version>
    <description>Creates static HTML versions of content pages. It will only cache SEO URLs, *.php pages will not be cached. </description>
    <files>
    <filename plugin="htmlcache">htmlcache.php</filename>
    </files>
    <params>
    <param name="html_cache_dir" type="text" default="" label="HTML Cache dir" description="Set the joomla HTML cache directory."/>
    <param name="cache_view_1" type="text" default="article" label="View to cache 1" description="Set view type to be cached. Only HTTP content can be cached."/>
    <param name="cache_view_2" type="text" default="" label="View to cache 2" description="Set view type to be cached. Only HTTP content can be cached."/>
    <param name="cache_view_3" type="text" default="" label="View to cache 3" description="Set view type to be cached. Only HTTP content can be cached."/>
    <param name="cache_view_4" type="text" default="" label="View to cache 4" description="Set view type to be cached. Only HTTP content can be cached."/>
    </params>
    </install>

    * This source code was highlighted with Source Code Highlighter.


    Теперь создаем обработчик события onAfterRender. Задача обработчика перехватывать только статьи типа Article, создавать в каталоге html_cache_dir структуру из папок в соответствии со структурой вебсайта и сохранять туда контент страницы. Дополнительно перед сохранением можно прогонять страницу через оптимизатор HTML (например этот Minify_HTML), которые оптимизирует размер страницы, убирая лишние комментарии и пробелы и тем самым увеличивая эффективность работы вебсайта.

    Отдельно отметим, что данный код работает для вебсайтов с включенным SEO и не кеширует страницы с любыми расширениями типа php или html. Кешироваться будут только URLs вида:
    /about
    /software/catalog


    Код придется поправить, если вы не используете SEO.

    htmlcache.php — непосредственно код.
    <?php
    defined( '_JEXEC' ) or die( 'Restricted access' );
    jimport( 'joomla.plugin.plugin' );
    class  plgSystemHtmlcache extends JPlugin
    {
    function plgSystemHtmlcache(& $subject, $config)
    {
    parent::__construct($subject, $config);
    }
     
    function onAfterRender()
    {
    global $mainframe;
            if($mainframe->isAdmin()) { return; }  //do not cache admin pages
     
    $document =& JFactory::getDocument();
    $doctype = $document->getType();
          $user =& JFactory::getUser();
          if($user->get('guest') != 1) { return; } //Only cache for non logged in users
          if ( $doctype != 'html' ) { return; } // Only render for HTML output
          $html_cache_dir=$this->param('html_cache_dir');
          if($html_cache_dir=='') { return; }  //Exit if no html_cache_dir specified
          if(!file_exists($html_cache_dir)) { mkdir($html_cache_dir); } //try to create folder if it does not exist
          // Only render for provided views
          if ((JRequest :: getVar('view')) != $this->param('cache_view_1') &&
               (JRequest :: getVar('view')) != $this->param('cache_view_2') && 
                 (JRequest :: getVar('view')) != $this->param('cache_view_3') && 
                   (JRequest :: getVar('view')) != $this->param('cache_view_4')) { return; }
          $relativePath=$this->request_uri();
          if (strpos($relativePath, '.')) { return; }  //exit if found DOT in the request_uri, we do not want to cache anything other than SEO
          $relativePath=str_replace('/',DS,$relativePath);
          //$body = Minify_HTML::minify(JResponse::getBody());
          $body =JResponse::getBody();
          $fullPath=$html_cache_dir.$relativePath;
          $parts=explode(DS,$relativePath);
          $currentPath=$html_cache_dir.DS;
     
       foreach( $parts as $p){    
         if($p==''){
            continue;
         }
         $currentPath.=$p;
         if((!file_exists($currentPath))&&(!is_file($currentPath))){
             mkdir($currentPath);
         }
             $currentPath.=DS;
       }//end for each
           $indexFile=$currentPath.DS.'index.html';
           if(!file_exists($indexFile)){ $this->writeToFile($indexFile,$body); }
         }
     
    function writeToFile($fileName,$content){
       $handle=fopen($fileName,'w');
       fwrite($handle,$content);
       fclose($handle);
    }
     
    function request_uri(){
    if (isset($_SERVER['REQUEST_URI'])){
       $uri = $_SERVER['REQUEST_URI'];
    }else{
      if (isset($_SERVER['argv'])){
        $uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['argv'][0];
      }else{
         $uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
      }
    }
    return $uri;
    } 
     
     function param($name){
          static $plugin,$pluginParams;
          if (!isset( $plugin )){ 
          $plugin =& JPluginHelper::getPlugin('system', 'htmlcache');
          $pluginParams = new JParameter( $plugin->params );
        }
        return $pluginParams->get($name);
     }
    }
     


    Оба файла можно закачать в /plugins/system или поместить в htmlcache.zip и установить через административный интерфейс Joomla. После установки задаем html_cache_dir=/opt/www/html/cache/content в настройках плагина и включаем плагин (Enabled: Yes).
    При первом обращении к странице в папке /opt/www/html/cache/content должны появиться папки и файлы — это означает что плагин работает и все в порядке с разрешениями на запись.

    Осталось заставить Apache отдавать эти файлы без обращения к PHP коду. В .htaccess, сразу после RewriteEngine on добавляем:
    RewriteCond %{REQUEST_URI} (/|/[^.]*)$ [NC]
    RewriteCond %{DOCUMENT_ROOT}/cache/content/%{REQUEST_URI}/index.html -f
    RewriteRule (.*) /cache/html/$1/index.html [L]

    Перезапускаем apache и проверяем через браузер. Для того чтобы проверить производительность запустим (под Linux):
    ab -n 10000 -c 50 -k www.azati.com
    ...
    Concurrency Level: 50
    Time taken for tests: 4.294054 seconds
    Complete requests: 10000
    Failed requests: 0
    Write errors: 0
    Keep-Alive requests: 10000
    Total transferred: 71155755 bytes
    HTML transferred: 67695750 bytes
    Requests per second: 2328.80 [#/sec] (mean)
    Time per request: 21.470 [ms] (mean)
    Time per request: 0.429 [ms] (mean, across all concurrent requests)
    Transfer rate: 16182.38 [Kbytes/sec] received
    ...

    Тест лучше запускать несколько раз, чтобы обеспечить «прогрев» для Apache. Запускать можно на самом вебсервере, чтобы исключить задержки в сети или по сети, но числа будут другие.

    www.azati.com замените адресом своего сервера, www.azati.com можно тестировать для сравнения. Не боимся, сайт не падает и статистика google не портится.

    К сожалению, скорость не дается даром:
    — Пропадает возможность редактировать статьи (Articles) через Joomla front-end, только через административный интерфейс.
    — После редактирования статьи, необходимо очищать кеш через административный интерфейс Tools -> Сlean cache

    Взвешиваем и решаем оправдан ли подход для вашего вебсайта.

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

    Средняя зарплата в IT

    113 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 10 037 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

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

    • НЛО прилетело и опубликовало эту надпись здесь
        +2
        Согласен, но все равно больно наблюдать в интернете тысячи тормозящих вебсайтов на Joomla со статическим или редко изменяемым контентом. Особый писк моды, это рандомайзеры контента или более старый вариант с выводом текущей даты на домашней странице.
          +5
          А вы в курсе что есть nginx?
            +2
            Конечно, в каком контексте вопрос?
              0
              Тогда зачем нужен ввелосипед? Тем более такой неуниверсальный?
              Конфиг на nginx написать — дело 10 минут, причем можно задать и время кэширования и нужные исключения, и без разницы какой контент используется.
                +3
                Рецепт достаточно прост и универсален для тех, кто держит небольшие сайты на shared хостинге, который ограничивает настройку стека. Таких много.
                0
                и да — комменты пишите в нужной ветке
                  +1
                  скоро уже привыкну к интерфейсу.
                0
                Как-то у меня всё нормально с джумлой сложилось…
                  +4
                  Джумле давно уже пора на покой. Такой лютый и беспощадный код в 2010ом году как-то совсем беспомощно смотрится. Слишком уж эта система старая, да и архитектура там кривая.
                  <Бывший ковырятель джумлы />
                    +2
                    а вы бы что посоветовали?
                      0
                      лично мне недавно друпал понравился. на более-менее мощном ноуте пережёвывает 200-300 req/sec (PHP-APC и встроенное друпаловское кэширование). притом не захлёбывается, если сделать ради интереса ab -c 200

                      лютый и беспощадный код тоже инсайд, но всё же это, слава богу, не джумла. детально не ковырял, но вроде жить можно
                        0
                        я с джумлой работал полгода; с друпалом на данный момент тоже полгода.
                        Хоть он и сложнее, но толку с него больше. Ну и вообще www.whydrupal.ru/
                      0
                      1. нафиг нужен кэш в самом CMS, если он не управляемый?
                      Сделайте удаление конкретной страницы по изменению и это будет уже лучше. Иначе внешний кэш по-любому лучше.

                      2. Добавьте gzip

                      и вообще — смотрите например плагин Wp-Super-Cache в Wordpress — там именно так и работает, только почти все грабли в прошлом.
                        0
                        спасибо, решение на самом деле легко оспорить по разным пунктам и я уже немного засомневался. Но факт востребованности оптимизации и эффективности подхода остается, Google сегодня подбодрил выдав для www.azati.com:
                        On average, pages in your site take 0.8 seconds to load (updated on May 2, 2010). This is faster than 96% of sites.

                        Приятно, черт побери, быть быстрее чем 96% других сайтов.

                      +1
                      Это выдает сервис Google Webmasters Tools
                        0
                        а, уже ответили… не заметил т.к. в другой ветке

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое