Яндекс.Метрика

    Песочница

    Радикальное кеширование в Joomla 1.5

    image

    Бесплатная CMS это всегда компромисс, компромисс между целым рядом очевидных факторов, перечислять которые в рамках данного ресурса смысла пожалуй нет, т.к. они всем давно известны. Безусловно, среди всего разнообразия бесплатных СМS можно выделить некоторые, которые будут лучше или хуже в каких-то отдельных номинациях, вроде скорости работы, простоты освоения новичком и т.п. Но в данной статье речь не об этом. Я просто хочу поделиться опытом успешного решения проблемы скорости генерации страниц в Joomla с помощью кеширования. Причём кеширования очень радикального, на «уровне» index.php.

    Хочу сразу отметить, что сие повествование не является каким-то принципиальным новшеством, и я не претендую на какие-либо лавры. Надеюсь, моё решение поможет хотя бы кому-то продлить «малой кровью» жизнь старого проекта на Joomla.

    Сразу хочу упредить критику в свой адрес и прошу некоторой снисходительности. Дело в том, что я не являюсь профессиональным программером ни в каком виде. Я технический директор маленькой типографии. Все мои старания в области программирования это всего лишь попытки снизить затраты на IT в компании.

    Как мне кажется, достаточно типичной (во всяком случае, в среде программистов-любителей, к коим я себя причисляю) является следующая ситуация. В своё время, начав писать для себя сайт, я выбрал Joomla, т.к. это было очевидное, хоть и не очень дальновидное, решение. Со временем, я столкнулся с некоторыми ограничениями Joomla, одним из которых стало время генерации страниц.

    Для тех, кто в теме, наверное не секрет, что из-за загрузки тяжеловесного фреймворка, плагинов и модулей, ну и как плата за универсальность CMS в целом, типовое время генерации страницы может составлять порядка 1-2 секунд. Использование более мощного железа проблему особо не решает, т.к. если на один запрос CMS делает несколько десятков (а то и под сотню) обращений к БД, потребуется увеличение производительности железа (и всех его компонентов) на порядок, что очень дорого, мягко говоря.

    Надо сказать, что сама joomla 1.5 тоже что-то умеет в плане кеширования. К своему стыду, уже точно не помню что, т.к. давно в это вникал. Но точно помню, что толку от этого кеширования практически нет.

    Первой попыткой оптимизации и ускорения было написание костыля для кеширования компонента Joomla Virtuemart. Virtuemart это известный (можно сказать «классика жанра») компонент интернет-магазина для Joomla. Конечно же, можно много нелестных слов о нём сказать и прочитать, т.к. он имеет массу недостатков, продиктованных целым рядом причин, начиная от почтенного возраста, отсутствия всяких там рефакторингов, заканчивая открытостью и универсальностью, которые, как правило, ходят рядом с громоздкостью. Тем не менее, он (Virtuemart) есть, работает, и для многих является простой точкой входа в «мир электронного бизнеса», если так можно выразиться. Да и вообще, я хотел бы сделать акцент не на том, как «правильно готовить» Joomla, какие и как компоненты использовать. Наоборот, я хотел бы поделиться рецептом, что делать, если она уже приготовлена неправильно. Решение, которое можно кратко охарактеризовать как «Ничего не трогай, ничего не меняй, только сделай быстрее, чтобы работало быстрее!»

    В моём случае Virtuemart используется в режиме каталога, что несколько упростило задачу написания костыля кеширования. Описывать, как я это делал смысла нет, т.к. полученный прирост существенного влияния на скорость загрузки не оказал. Ведь у меня в Joomla есть ещё компоненты, любящие ресурсы, например JoomFish. Кроме того, чтобы Virtuemart отдал свой кеш, всёравно нужно загрузить весь фреймворк Joomla, что само по себе требует времени.

    Я начал искать решение, как бы ещё ускорить загрузку. Ситуация усугублялась проблемами на виртуальном хостинге, где мои соседи имели архинеоптимальные сайты, в связи с чем очень нагружали сервер и время генерации иногда доходило до 10-20 секунд.
    Поразмыслив, пришёл к выводу, что никакая оптимизация joomla, по крайней мере в разумных приделах — после которой joomla останется joomla, не даст ускорения в разы, а именно эта цель стояла передо мной. Поэтому было решено рассмотреть вопрос написания фронтенда. Я сразу отбросил и не рассматривал варианты с готовыми решениями (даже не знаю есть ли они), т.к. не имел на это ни желания, ни времени. Кроме того, мой экземпляр joomla прилично допилен местами и типовые (типовые для Joomla) решения не факт, что заработали бы из коробки.

    Что получилось в результате:
    • кеширование практически всех страниц сайта в режиме неавторизированного пользователя
    • скорость генерации порядка 100-200 мс
    • минимальное кол-во обращений к БД при отдаче страницы из кеша — 1-2 шт, больше в том случае если на страницах присутствуют динамические (или периодически обновляемые, с меньшим чем, сама страница, TTL) модули
    • интеграция в механизм сессий joomla без запуска фреймворка самой joomla, благодря чему корректно работают все ассинхронные навороты внутри кешируемых страниц, т.к. при получении POST или GET от пользователя, гуляющего (без единой загрузки фреймворка) по кешу сайта, joomla видит его сессию в своей базе данных и всё работает как надо. У меня, по крайней мере, чудесно заработали стандартные AJAX-компоненты JComments и ProofReader, так же заработали пару самописных AJAX-компонетов.


    Рассмотрим реализацию

    • В моём случае я уместил весь фронтенд в файле index.php, ведь именно в нём начинается и заканчивается генерация.
    • Первым делом анализируем URL на предмет того, разрешено ли кеширование данной страницы. В моём случае в joomla используется модуль генерации SEF-ссылок, который заменяет ссылки на почти всех страницах сайта на кирилические. Так что в моём случае запрещены к кешированию все ссылки в которых есть /index.php

      $fe_disalolwed_urls_array = array(
      "/index"
      );
      .........................

      if (fe_check_url($fe_disalolwed_urls_array,$fe_uri_cp1251)) {}

      .........................

      function fe_check_url($a, $i)
      {
      foreach ($a as $aa)
      {
      if (substr($i,0,strlen($aa)) == $aa)
      {
      return false;
      }
      }
      return true;
      }


      Для этой оперции не требуется обращение к БД, всё происходит в файле index.php.
      Если кеширование страницы запрещено, уходим на штатную генерацию страницы с загрузкой фремворка и т.д. и т.п.

    • Так же проверяем наличие в запросе специальной куки, которую ставит joomla пользователям прошедшим авторизацию с птицей «запомнить меня». Эта кука имеет имя, которое определяется как md5('_secret_JLOGIN_REMEMBER'), где _secret_ это случайная строка, генерируемая при инсталяции экземпляра joomla, найти эту строку можно в админке.

      Если находим такую куку, так же передаём управление «великой и ужасной» — пусть пробует по остальным зашифрованным кукам установить, кого она должна «вспомнить».
      Для этой операции так же не требуется доступ к БД и файловой системе, мы всё ещё в index.php.

    • Если мы всё ещё здесь, идём дальше. Создаём фиктивную сессию с именем таким же, как это делает joomla, а именно md5(md5('_secret_site')) и проверяем, есть ли такая сессия с таким id в таблице jos_session джумлы, и если есть — является ли эта сессия сессией авторизированного пользователя. Так же в этом месте надо удалить устаревшие сессии из БД, хотя это не обязательно — об этом может позаботиться сама joomla, когда какой-то из запросов таки её запустит.

      Обратите внимание, что это первое обращение к БД.

      По результатам проверки, решаем что делать дальше. Если такой сесси нету (а так же в случае, если такая сессия есть и она не принадлежит зарегистрированному пользователю), переходим к следующему пункту, где будем определять возможность чтения страницы из кеша. В случае, если сессия принадлежит зарегистрированному пользователю, «уходим» на стандартную генерацию. В принципе в этом месте можно было бы «копать» дальше — кешировать страницы для авторизированных пользователей, но я в это углубляться не стал, т.к. специфика моего сайта такова, что ливьную долю посещений составляют незарегистрированные пользователи, при том, что размер фронтенда, который кешировал бы страницы зарегистрированных пользователей значительно возрос бы. Кроме того, на данный момент, мы переехали на новых хостинг, где время генерации, даже без кеша, вполне стабильно.

    • Теперь надо определить, есть ли кешированная версия данной страницы. Если она есть, то отдаём кеш, если её нету, то делаем генерацию, сохраняем в кеш результат и отдаём его клиенту. В случае генерации нужно не забыть удалить сессию, т.к. joomla сама будет её создавать!

      Так же, в случае если «отдаём кеш» и сессии с таким именем в joomla нету, важно добавить сессию в таблицу сессий joomla. Это нужно для обеспечения корректной работы самой joomla, если пользователь перейдёт из кешируемой в зоны в некешируемую, для работы AJAX-модулей и т.п. Кроме того, нужно выполнить обработку кеша на предмет динамических модулей перед отправкой клиенту, об этом ниже.

      В этом месте есть ряд нюансов связанных с динамическими модулями.
      В моём случае это следующие модули:
      • кол-во пользователей на сайте за последний час
      • последние новости сайта
      • новости производства (этот модуль отображает последние события в офисной ERP)
      • модуль отображающий случайного пользователя joomla (в данном случае отображается фотка случайного сотрудника компании)
      • авторизации joomla, который сам по себе статичен, но имеет Spoof-защиту (в случае использования компонента Community Builder за это отвечает функция fe_cbSpoofString — ищем поиском в исходниках joomla)

      Для обеспечения их корректной работы, было сделано следующее:
      • при запуске генерации joomla, которую предстоит поместить в кеш, делаем некий define
      • в коде генераци упомянутых модулей, если видим этот define, вместо тела модуля выводим в тело ответа какое-нибудь волшебное слово
      • во фронтенде пишем (с помощью Ctrl-C, Ctrl-V и маленького напильничка) функции, генерирующие текст модулей без загрузки фреймворка
      • результат генерации (в котором вместо модулей содержатся волшебные слова) кладём в кеш
      • при отдаче контента из кеша меняем банальным str_replace() наши волшебные слова на результаты работы определённых нами функций



    Собственно на этом всё. В итоге я получил ускорение почти на порядок (почти в десять раз).

    image

    Результаты host-tracker
    Физически хостинг находится в Киеве, 99% целевой аудитории там же.
    Оценить скорость работы сайта можно на нём самом: link

    На картинке ниже — скрин из Google Webmasters Tools

    image

    Всплеск в феврале вызван одной проблемкой, для решения которой мне приходилось на протяжении всего месяца раз в 2-3 дня удалять весь кеш фронтенда. Но сама эта проблема, к фронтенду отношения не имела.

    Очищается кеш фронтенда из админки joomla, для этого ничего не пришлось делать, кроме как сложить сам кеш в папочку /cache/FrontEnd — joomla сама его увидела и предоставила возможность очищать…

    PS: уверен, что такой подход может быть применён не только в рамках Joomla 1.5 и не только в рамках Joomla. Конечно это потребует некоторых навыков, но, в то же время, это может расширить область применения простых в изучении бесплатных CMS.

    Спасибо за внимание.