Статьи Архив статей

Перевод: Мациевский Николай aka sunnybear
Опубликована: 1 марта 2009

Разгоняем Drupal

Примечание: ниже расположен перевод заметки "Improving Drupal's page loading performance", в которой рассматриваются прикладные методы увеличения клиентской производительности для сайтов, построенных на этой CMS. Весьма интересен ход рассуждений при построении высокопроизводительного сайта. Мои комментарии далее курсивом.

Введение

Google доминирует на рынке поисковых систем очень во многом благодаря своим чисто спартанским интерфейсам без рюшечек и лишних украшательств. Но больше всего уважения заслуживает его непревзойденная скорости загрузки (которая не в последнюю очередь обусловлена именно спартанским интерфейсом).

Поскольку вы читаете эту статью, то, скорее всего, являетесь разработчиком под Drupal. И по всей видимости, некоторые из посетителей вашего сайта, сделанного на Drupal, недовольны скорость загрузки страниц. И это не зависит от того, расположен сайт на виртуальном хостинге, VPS или выделенном сервере. А если ваши посетители физически расположены вдали от самого сервера, то проблемы эти для них приобретают еще больший масштаб.

В этой статье рассказывается, как с этим бороться.

Клиентская производительность

Быстрый сервер с большим количеством оперативной памяти до известной степени улучшит производительность вашего сайта. Но прежде чем ваш сайт станет достаточно большим, у него все равно появится некоторое количество узких мест, которые могут быть устранены с гораздо большей отдачей. И затраты на это будут совсем невелики. Обычно на получение HTML-файла уходит меньше 20% всего времени загрузки страницы. Это означает, что остальные 80+% приходятся на все то, что есть в самом HTML: CSS, JS, картинки, видео. И во многих случаях это 80% — это лишь оценка снизу.

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

Огромная благодарность отправляется исследовательскому центру Yahoo!, который явил миру свои четырнадцать заповедей вместе с инструментом YSlow (мы к нему перейдем буквально через секунду), с помощью которого можно проверить, насколько производительность сайта соответствует указанным правилам. Если вы успешно примените все 14, то ваш сайт будет просто летать. (При том предположении, что время создания страницы не является сверхбольшим.) Всегда можно провести еще немного оптимизации для сайта. Некоторые наиболее эффективные способы будут освещены в конце статьи.

YSlow

Для начала стоит убедиться, что вы установили Firefox, Firebug и YSlow for Firebug (версия 0.9 или более поздняя).

Firebug просто должен быть у каждого веб-разработчика, и совсем не важно, профессионал вы или новичок. YSlow является дополнением к Firefox, разработанном к недрах Yahoo!, и позволяет анализировать вашу веб-страницу и находить те места (вспоминаем те 14 правил?), где она действительно тормозит и почему так происходит (игра слов между "why slow" и "y-slow"). Но в то же самое время этот инструмент показывает, как можно устранить все эти недочеты. Чем меньше номер правила, тем значительнее результат.

Следом я разбираю особенности Drupal 5 и 6 относительно каждого правила и возможности этой CMS, ее настройки и рекомендации для ускорения загрузки вашего сайта.

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

Правило первое: уменьшаем число HTTP-запросов

ТребованиеDrupal 5Drupal 6
Объединение CSS-файловдада
Объединение JS-файловнетда
Автоматическое создание CSS Spritesнетнет

В Drupal даже есть возможность автоматически сжимать CSS-файлы (путем вырезания комментариев и пробелов). Объединение JS-файлов было добавлено в Drupal 6. Насколько я знаю, ни одна существующая CMS/CMF не позволяет создавать CSS sprites «не лету». И ни в одной системе нет даже модуля или расширения, которое бы позволяло это делать. Это может стать ключевой возможностью в Drupal, если появится ее поддержка. И любая CMS значительно выиграет от этого. На данный момент автоматизировать процесс только создания data:URI из исходных CSS-файлов.

Решение

Самый простой путь для уменьшения числа запросов — это включения в Drupal объединения CSS- и JS-файлов. Соответствующие настройки находятся в admin/settings/performance на вашем сайте Drupal.

В случае Drupal 5 существует полезное дополнение, которое обеспечивает ту же функциональность из Drupal 6'по объединению JS-файлов. Скачать ее можно из этой заметки — я проспонсировал создание этого патча.

В Drupal нет модуля для автоматического создания CSS Sprites. Если у сайта достаточно тяжелый дизайн, то скорость загрузки при использовании CSS Sprites может возрасти значительнее, чем после объединения CSS- и JS-файлов. Я очень надеюсь, что кто-нибудь (или какая-то компания, занимающая разработкой на основе Drupal) позаботится об этом и возьмет инициативу в свои руки.

В то же время есть бесплатный инструмент для создания CSS Sprites, если вы не против ручной работы при оптимизации.

Правило второе: используйте CDN

ТребованиеDrupal 5Drupal 6
Динамически изменять URL подключаемых файловнетнет

API Drupal по работе с файлами требует существенной доработки, чтобы оно позволяло изменять адрес файла, основываясь на его размере или типе.

Решение

Я предпочел разобраться с этой проблемой самостоятельно, потому что использование CDN значительно улучшает удобство использования вашего сайта для посетителей, которые расположены вдалеке от ваших серверов. К тому же я работаю над одним проектом, у которого аудитория весьма сильно распределена по поверхности Земного шара.

Первое, что нам, очевидно, понадобится, — это дополнить ядро Drupal поддержкой функции изменения URL для файлов. Я выбрал путь создания новой функции, и завел file_url(), которая должна была создавать URL для всех файлов, в том числе URL для дополнительных CSS-Файлов в шаблоне page.tpl.php (например, для файла print.css). Это дополнение также обеспечивало и следующий новый метод: hook_file_server(), с помощью которого модули могли заявлять новые файловые сервераs. Для задания файлового сервера «по умолчанию» была добавлена настройка File servers в форму File system settings. Если какой-то сервер оказывается не в состоянии выдать запрошенный файл, то Drupal попробует обратиться к следующему серверу по списку, и так далее. И всегда путь перебора серверов закончится на исходном веб-сервере, на котором находится сам Drupal — если все сервера окажутся недоступными.

На данный момент доступен патч только для Drupal 5 (он включен в модуль интеграции с CDN, который выложен в конце этой статьи), поскольку я хочу получить некоторое количество отзывов перед тем, как буду поддерживать данный модуль для двух различных ветвей Drupal. Как только патч закрепится в своей финальной форме, я выложу его версию для Drupal 6 и, естественно, буду готов сделать это для Drupal 7. По этому поводу создан инцедент на Drupal.org.

Вторая часть (интеграция с CDN), по-видимому, должна включать реализацию функции hook_file_server(). Именно поэтому появится на свет модуль интеграции CDN. Он был написан с учетом большой гибкости: он поддерживает дополнения для синхронизации файлов (на данный момент доступно одно из них — FTP), может создавать уникальные имена файлов или директорий (будет необходимо для сохранения относительных путей), обеспечивает рядом инструментов для проверки, насколько хорошо работают фильтры (статистика по каждой странице и целиком для сайта), а сами фильтры могут быть заданы при помощи естественных для Drupal параметров функции file_scan_directory().

В данный момент находится в работе статья, в которой рассматривается производительность при внедрении модуля CDN. Она же должна будет включить полное руководство по установке этого модуля.

Правило третье: добавляем заголовок Expires

ТребованиеDrupal 5Drupal 6
Не выставлять заголовок Expires для веб-страницдада
Выставлять заголовок Expires для всех остальных файловдада
Выставлять заголовок Expires в далекое будущее: возможность изменять URL файлов динамически для сброса кэшированиянетнет

Выставляю для файла заголовок Expires, вы сообщаете браузеру, что файл этот можно закэшировать.

Drupal выставляет заголовок Expires для всех файлов, отличных от HTML, сроком на две недели. В большинстве случаев этого вполне достаточно. Однако для максимальной производительности, эта дата должна быть выставлена в далекое будущее (например, на 10 лет вперед от времени доступа), что потребует использования уникальных имен файлов: каждый раз, когда файл меняется, нам нужно менять его имя (чтобы форсировать сброс кэша у пользователей), именно поэтому необходимым требованием является изменение имени файлов. Если этого не сделать, то некоторые из ваших пользователей будут видеть старые версии файлов (потому то они будут загружаться из кэша).

Решение

Выставить будущую дату для заголовка Expires достаточно просто: нужно просто отредактировать ваш файл .htaccess. На сервере Apache при этом должен быть установлен mod_expires, по умолчанию это доступно на большинстве серверов. Однако создания уникальных имен файлов — это из совсем другой оперы. Проблема изменения имени файлов уже решена при разборе второго правила. Поэтому все, что нужно сделать, это настроить файловый сервер, который это поддерживает. Уже упомянутый модуль интеграции с CDN обладает этой функциональностью, но для его использования, естественно, нужно арендовать CDN.

Правило четвертое: применяем GZIP

ТребованиеDrupal 5Drupal 6
GZIP для веб-страницдада
GZIP для CSS- и JS-файловнетнет

Когда в Drupal включено кэширование страниц, то сами страницы записываются в кэш уже в виде gzip-архивов! Чтобы узнать немного больше о том, как Drupal работает с gzip, вы можете запустить следующую команду из корневой директории сайта, для которого установлен Drupal:

egrep ?rn "gzip" .

Не забудьте точку в конце!

Однако до сих пор Drupal не позволяет применять gzip-сжатие для CSS- и JS-файлов.

Решение

Дополнение к ядру Drupal позволяет обеспечить данную функциональность, но, к несчастью, он уже некоторое время не доступен.

Если вы используете мой модуль интеграции с CDN, то об этом можно забыть: модуль по умолчанию применяет gzip, если браузер его поддерживает.

Альтернативное решение

В качестве альтернативы можно предложить настроить сам сервер Apache дл автоматического сжатия файлов.

Например, для Apache 2.x: можно добавить следующие строки в ваш .htaccess или httpd.conf:

AddOutputFilterByType DEFLATE text/css application/x-javascript

Правило пятое: располагаем CSS в начале страницы

ТребованиеDrupal 5Drupal 6
Метод для добавления CSS-файлов на веб-страницыдада
Месторасположение файлов по умолчанию находится в <head>-секции XHTML-документадада

У Drupal есть общий метод: drupal_add_css().

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

Правило шестое: располагаем JS в конце страницы

ТребованиеDrupal 5Drupal 6
Метод для добавления JS-файлов на веб-страницыдада
Месторасположение файлов по умолчанию находится перед </body> в XHTML-документенетнет

В Drupal также есть и следующий метод: drupal_add_js().

JS-файлы должны располагаться в конце страницы, потому что браузеры ждут загрузки всех ресурсов, упомянутых в <head>. Как вы, вероятно, знаете, JS-файлы сейчас достаточно велики, поэтому их загрузка выливается в ощутимое время, которое сказывается на замедлении отображения страницы (браузеры показывают белый экран все это время, не имея возможности что-либо предпринять). Если вы разместите JS-файлы в конце страницы, тогда браузеры смогут отобразить ее содержание, продолжая загружать сами JS-файлы! Это также позволит добиться большого числа параллельных потоков, что значительно уменьшит время загрузки.

Обсуждение на эту тему уже было на groups.drupal.org.

Решение

К несчастью, по умолчанию у параметра $scope для drupal_add_js() неудачное значение: 'header'. Если мы поменяем его на просто 'footer', то будет уже хорошо. Число дополнительных модулей, которые принудительно загружают файлы в 'header' крайне невелико, поэтому в общем случае будет достаточно просто осуществить эту операцию. И я еще не встречал модулей, с которыми возникали бы проблемы при перемещении вызовов JS-файлов из заголовка страницы в ее низ.

Более сложной частью этого решения являются сами JS-Файлы Drupal: misc/jquery.js и misc/drupal.js. Они могут быть расположены в низу страницы без каких-либо проблем. Но что если дополнительный модуль захочет разместить свои файлы в заголовке страницы? Они могут вообще не загрузиться! Для увеличения совместимости нам нужно будет по-прежнему размещать базовый комплект JS-файлов в head-секции, если хотя бы один модуль захочет поместить туда свои файлы.

Я выложил дополнения как для Drupal 5, так и для версии 6, но ни в одном из них не внедрена более сложная часть, заявленная выше. По моему мнению, Drupal должен принять строгие правила относительно JS-файлов: они все должны быть «ненавязчиво-совместимы» (в оригинале используется footer-compatible, но данная политика в любой случае недостаточна для сложных веб-приложения — они все должны поддерживать загрузку в динамическом или отложенном режиме, проверяя все зависимости перед инициализацией). Пока кто-нибудь не укажет мне на необходимость расположения JS-файлов в начале странице, я не изменю своего мнения по поводу упомянутого правила.

Альтернативное решение

Есть и другой метод, чтобы решить описанную проблему, который не требует вмешательства в ядро Drupal. Однако он более трудоемкий, потому что предполагает повторения действий для каждой используемой темы оформления. Предположим, что вы используете стандартную тему Drupal — Garland. Тогда откройте файл themes/garland/page.tpl.php в вашем любимом редакторе. Отыщите вверху этого файла строчку:

<?php print $scripts ?>

Вырежьте ее оттуда и переместите перед следующей строкой:

<?php print $closure ?>

Итак, результат будет выглядеть примерно так:

<?php print $scripts ?>
<?php print $closure ?>
</body>
</html>

Как вы видите, это позволит вызвать все подключения скриптов прямо перед закрывающим тегом <body>. (На самом деле, также еще до вывода $closure, который создается всеми вызовами hook_footer().)

Правило седьмое: избегаем CSS-выражений

ТребованиеDrupal 5Drupal 6
Ни одна тема по умолчанию не должна их использоватьдада

CSS-выражения совсем не рекомендуется использовать при верстке страниц, потому что они могут выполняться великое множество раз на одной и той же странице: когда она отрисовывается, или изменяется ее размер, а также когда выполняется прокрутка по странице (здесь освещена более подробно проблема CSS-выражений). Пересчет выражения может происходить даже при движении мышью!

Ни одна из стандартных тем Drupal не использует CSS-выражений. Поэтому нужно просто убедиться, что их нет в вашей теме.

Правило восьмое: CSS- и JS-файлы должны быть внешними

ТребованиеDrupal 5Drupal 6
CSS- и JS-код не должен вставляться внутри страницы или включаться по минимумудада

Если ваш сайт является стартовой страницей для многих пользователей, вы, скорее всего, будете использовать альтернативный подход и ознакомитесь с этой рекомендацией. В противном случае вы можете вообще проигнорировать это правило.

Правило девятое: уменьшаем DNS lookups

ТребованиеDrupal 5Drupal 6
Использование 2-4 хостов по умолчанию: возможность динамически изменять URL выдаваемых файловнетнет

На моей памяти ни один провайдер услуг хостинга не предлагает по умолчанию статический файловый сервер (скорее всего, имеется в виду легкие сервера типа thttpd, nginx или 0W). Поэтому вполне очевидно, что и Drupal по умолчанию не настроен на работу с ним. Однако мы можем заставить Drupal поддерживать это решение за счет внешних дополнений.

Если вы используете довольно много так называемых виджетов (небольшие информационные блоки от Flickr, del.icio.us, MyBlogLog и т. д.) на вашем сайте, то стоит предпринять некоторые дополнительные действия.

Решение

Проблема с изменением URL для файлов уже решена при рассмотрении второго правила. Поэтому все, что вам нужно, — это настроить файловый сервер, который это поддерживает.

Если вы уже используете мой модуль интеграции с CDN, вы уже будете использовать 2 или более хоста, но это потребует, естественно, самого CDN.

В качестве альтернативы вы можете использовать обычный файловый сервер. В статье Robert Douglass' по использованию Drupal с сервером для статических файлов очень хорошо описаны все за и против такого подхода и настройки сервера (на данный момент nginx будет более резонным выбором по сравнению с thttpd, тем более что nginx может быть одновременно и фронтендом для вашего Apache).

Также стоит ознакомиться с мыслями Yahoo на эту тему.

Решение для виджетов

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

Например, если вы используете Google Analytics, то установите модуль Google Analytics, в которой есть настройка по Локальному кэшированию самого .js-файла (и ежедневном его обновлении, чтобы быть уверенным в том, что вы предоставляете последнюю версию этого счетчика).

Правило десятое: уменьшаем JavaScript

ТребованиеDrupal 5Drupal 6
Минимизация JavaScriptнетнет

Этот подход уже был изначально включен в ядро Drupal 6. Однако он был удален из-за ряда возникших проблем с неработоспособным JS-кодом после минимизации. В качестве минимизатора в Drupal 6 использовался Dean Edwards' packer также известный как Packer (довольно странный выбор, если учесть, что YUI Compressor выдает лучший результат при применении дополнительного gzip-сжатия).

Решение

Packer Не только уменьшает код, он также подвергает его обфускации. При минимизации удаляются только лишние пробелы и комментарии (и некоторые другие конструкции, согласно спецификации кода), обфускатор однако дополнительно видоизменяет код: переименовывает переменные и функции (в более короткие). Для этого packer использует собственный алгоритм (в котором зашифровано само его имя). Это приводит к серьезному увеличению времени загрузки кода: вплоть до 200 мс для распаковки JS! Дополнительно, эффективность от применения gzip-сжатия существенно снижается (более подробно о результтах сжатия JS-файлов можно прочитать в соответствующей статье).

Более разумными альтернативами данного инструмента будут JSMIN (минимизатор / преобразователь кода), Dojo Shrinksafe (минимизатор / обфускатор кода) или YUI Compressor (минимизатор кода). Последние два созданы на основе Rhino, движка JavaScript от Mozilla, написанного на Java. Однако все это не подходит для ядра Drupal. Существует Реализация JSMIN на PHP, поэтому я считаю, что это наилучший выбор для использования в PHP-проектах (YUI Compressor требует установленной java на сервере).

И уже создана заявка на включение его в Drupal 7.

Правило одиннадцатое: избегаем перенаправлений

ТребованиеDrupal 5Drupal 6
Отсутствие редиректов по умолчаниюдада

Drupal может перенаправлять пользователей при доступе к URL без алиаса /node/11 к его мнемонической версии /about, но по умолчанию этого не происходит.

Модуль Global Redirect позволяет осуществить описанный функционал разумным способом. Более подробно описано на странице этого проекта.

Правило двенадцатое: удаляем дублирующиеся скрипты

ТребованиеDrupal 5Drupal 6
Метод для добавления JS-файлов на страницудада

В Drupal, как было уже упомянуто в шестом правиле, есть соответствующий метод: drupal_add_js(). Вы можете использовать статическую переменную для предотвращения добавления одного и того же файла несколько раз. Например, можно ознакомиться с модулем jCarousel.

Правило тринадцатое: настраиваем ETag

Это единственное правило, которое полностью зависит от конфигурации сервера. ETag уникальным образом характеризует файл, и используется для того, чтобы констатировать изменение файла на сервера относительно той копии, что есть у браузера.

Основная проблема заключается в том, что атрибут этот полностью зависит от самого сервера (в частности, от inodes), с которого отдается данный файл. Это означает, что если вы используете несколько серверов для выдачи одного и того же файла (например, CDN), то один раз вы можете получить файл с сервера 1, а в другой — уже с сервера 2. И так как ETag не совпадет, браузер загрузит данный файл снова!

Решение

Если вы используете несколько серверов, просто отключите ETag. Для Apache нужно просто добавить следующую строку в файл httpd.conf:

FileETag none

Для IIS решение будет более заключаться в более сложной процедуре.

Альтернативное решение

Можно также использоваться только один сервер для выдачи статических файлов (или загрузки их в CDN). Подробнее данное решение уже освещалось в правиле втором и девятом.

От себя могу добавить, что вообще возможно настроить выдачу ETag в произвольном формате, но для этого нужно использовать свои обработчики для файлов. Может быть, уже есть соответствующий модуль для nginx или Apache. Также правильная настройка выдачи заголовка Last-Modified способна решить заявленную проблему (по сути, данный заголовок является более простой альтернативой для ETag).

Правило четырнадцатое: делаем AJAX кэшируемым

ТребованиеDrupal 5Drupal 6
Подключение дополнительных форматов отображениянетнет
Возможность настройки gzip-сжатия для разных форматов (как в правиле 4)нетнет

Над заявленными возможностями уже работали для включения их в Drupal 6, но не успели вовремя.

Возможность установки gzip-сжатия для различных файлов (см. четвертое правило) должна контролироваться каким-либо дополнением или улучшением, ибо это значительно влияет на производительность при AJAX-запросах.

Другие правила (но по сути менее важные) уже автоматически работают для таких запросов. Девятое и тринадцатое — потому что AJAX-ответы отправляются с того же Drupal сервера, что и HTML-документы. Одиннадцатое правило — потому что перенаправления, скорее всего, отсутствуют для AJAX-ответов в Drupal. А десятое правило вообще имеет мало смысла по той причине, что JSON-формат дальше минимизировать уже нельзя, тут поможет только gzip-сжатие..

Решение

Уже было сделано замечание по поводу отображения узлов вебсайта на Drupalcon Boston 2008 code sprint, поэтому, скорее всего, через несколько месяце оно будет реализовано.

Практическое применение

Выше приведено, конечно, огромное количество информации. Наиболее простым путем для применения их всех для вашего сайта является установка моего модуля интеграции с CDN и использование дополнения для ядра Drupal для объединения JS-файлов и перемещения их в конец страницы (путем изменения первоначального расположения подключаемых файлов).

Живые примеры

Перед тем, как начать применять вышеописанные советы, вы, скорее всего, захотите каким-либо образом убедиться, что вся эта оптимизация вообще имеет какой-либо смысл. Без проблем. Вы уже ощутили ее. (имеется в виду, конечно, загрузка страницы с исходной статьей, однако это справедливо и для webo.in, хотя он построен и не на Drupal). Эта страница загружается меньше, чем за секунду. При этом в Drupal отключено кэширование страниц, установлен eAccelerator и включено кэширование для запросов в MySQL.

В качестве другого примера можно привести DrupalBin. Сайт расположен на виртуальном хостинге (DreamHost) без eAccelerator и кэширования на уровне MySQL — что объясняет достаточно медленное создание страниц на сервере.

Дополнительная оптимизация

В порядке эффективности применения:

  • Модуль Boost включает в Drupal статическое кэшированеи страниц. Это означает, что страницы, созданные динамически, записываются в файлы, и затем с помощью магии mod_rewrite отдаются обратно сервером уже в виде файлов (если они существуют), при этом не производится ни одного запроса к базе!
  • Статья на 2bits битком набита советами по оптимизации производительности Drupal.
  • Дополнение для ядра расширенное кэширование снова от Robert Douglass позволяет кэшировать отдельные блоки, комментарии, структуру форума, встроенные узлы, строки пути, популярные поисковые запросы и таксономические деревья.

Заявления по поводу Drupal

Дополнительная информация

Благодарности отправляются

  • Yahoo! за их работу над YSlow.
  • Greg Knaddison (greggles) за предварительное ознакомление с этой статьей и несколько весьма ценных предложений, которые существенно дополнили саму статью.
ВложениеРазмер
Дополнение для размещения JS-файлов в конце страницы для Drupal 51,08 Кб
Дополнение для размещения JS-файлов в конце страницы для Drupal 61,28 Кб
hook_file_server для Drupal 511,61 Кб

Читать дальше

Все комментарии (habrahabr.ru)