Примечание: ниже расположен перевод заметки "Improving Drupal's page loading performance", в которой рассматриваются прикладные методы увеличения клиентской производительности для сайтов, построенных на этой CMS. Весьма интересен ход рассуждений при построении высокопроизводительного сайта. Мои комментарии далее курсивом.
Google доминирует на рынке поисковых систем очень во многом благодаря своим чисто спартанским интерфейсам без рюшечек и лишних украшательств. Но больше всего уважения заслуживает его непревзойденная скорости загрузки (которая не в последнюю очередь обусловлена именно спартанским интерфейсом).
Поскольку вы читаете эту статью, то, скорее всего, являетесь разработчиком под Drupal. И по всей видимости, некоторые из посетителей вашего сайта, сделанного на Drupal, недовольны скорость загрузки страниц. И это не зависит от того, расположен сайт на виртуальном хостинге, VPS или выделенном сервере. А если ваши посетители физически расположены вдали от самого сервера, то проблемы эти для них приобретают еще больший масштаб.
В этой статье рассказывается, как с этим бороться.
Быстрый сервер с большим количеством оперативной памяти до известной степени улучшит производительность вашего сайта. Но прежде чем ваш сайт станет достаточно большим, у него все равно появится некоторое количество узких мест, которые могут быть устранены с гораздо большей отдачей. И затраты на это будут совсем невелики. Обычно на получение HTML-файла уходит меньше 20% всего времени загрузки страницы. Это означает, что остальные 80+% приходятся на все то, что есть в самом HTML: CSS, JS, картинки, видео. И во многих случаях это 80% — это лишь оценка снизу.
В зависимости от того, с каким сайтом проводится работа, какая физическая мощность у сервера, и т. д., вы можете урезать от 25 до >100 (примерно) процентов от текущего времени загрузки страницы. Как первоначальная (с пустым кэшем), так и последовательная (с полным кэшем) загрузка страницы будет значительно быстрее, если конечно вы еще не провели своих собственных мероприятий по оптимизации.
Огромная благодарность отправляется исследовательскому центру Yahoo!, который явил миру свои четырнадцать заповедей вместе с инструментом YSlow (мы к нему перейдем буквально через секунду), с помощью которого можно проверить, насколько производительность сайта соответствует указанным правилам. Если вы успешно примените все 14, то ваш сайт будет просто летать. (При том предположении, что время создания страницы не является сверхбольшим.) Всегда можно провести еще немного оптимизации для сайта. Некоторые наиболее эффективные способы будут освещены в конце статьи.
Для начала стоит убедиться, что вы установили Firefox, Firebug и YSlow for Firebug (версия 0.9 или более поздняя).
Firebug просто должен быть у каждого веб-разработчика, и совсем не важно, профессионал вы или новичок. YSlow является дополнением к Firefox, разработанном к недрах Yahoo!, и позволяет анализировать вашу веб-страницу и находить те места (вспоминаем те 14 правил?), где она действительно тормозит и почему так происходит (игра слов между "why slow" и "y-slow"). Но в то же самое время этот инструмент показывает, как можно устранить все эти недочеты. Чем меньше номер правила, тем значительнее результат.
Следом я разбираю особенности Drupal 5 и 6 относительно каждого правила и возможности этой CMS, ее настройки и рекомендации для ускорения загрузки вашего сайта.
Если вы хотите избежать теоретических обоснований и сразу получить результат, то пропустите эту часть и переходите сразу к практическим советам для оптимизации вашего сайта.
Требование | Drupal 5 | Drupal 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, если вы не против ручной работы при оптимизации.
Требование | Drupal 5 | Drupal 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 5 | Drupal 6 |
---|---|---|
Не выставлять заголовок Expires для веб-страниц | да | да |
Выставлять заголовок Expires для всех остальных файлов | да | да |
Выставлять заголовок Expires в далекое будущее: возможность изменять URL файлов динамически для сброса кэширования | нет | нет |
Выставляю для файла заголовок Expires
, вы сообщаете браузеру, что файл этот можно закэшировать.
Drupal выставляет заголовок Expires
для всех файлов, отличных от HTML, сроком на две недели. В большинстве случаев этого вполне достаточно. Однако для максимальной производительности, эта дата должна быть выставлена в далекое будущее (например, на 10 лет вперед от времени доступа), что потребует использования уникальных имен файлов: каждый раз, когда файл меняется, нам нужно менять его имя (чтобы форсировать сброс кэша у пользователей), именно поэтому необходимым требованием является изменение имени файлов. Если этого не сделать, то некоторые из ваших пользователей будут видеть старые версии файлов (потому то они будут загружаться из кэша).
Выставить будущую дату для заголовка Expires
достаточно просто: нужно просто отредактировать ваш файл .htaccess
. На сервере Apache при этом должен быть установлен mod_expires
, по умолчанию это доступно на большинстве серверов. Однако создания уникальных имен файлов — это из совсем другой оперы. Проблема изменения имени файлов уже решена при разборе второго правила. Поэтому все, что нужно сделать, это настроить файловый сервер, который это поддерживает. Уже упомянутый модуль интеграции с CDN обладает этой функциональностью, но для его использования, естественно, нужно арендовать CDN.
Требование | Drupal 5 | Drupal 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
Требование | Drupal 5 | Drupal 6 |
---|---|---|
Метод для добавления CSS-файлов на веб-страницы | да | да |
Месторасположение файлов по умолчанию находится в <head> -секции XHTML-документа | да | да |
У Drupal есть общий метод: drupal_add_css()
.
Расположение таблиц стилей в HEAD
-секции документа позволяет загружать страницу быстрее, поскольку обеспечивает постепенное ее отображение на экране.
Требование | Drupal 5 | Drupal 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()
.)
Требование | Drupal 5 | Drupal 6 |
---|---|---|
Ни одна тема по умолчанию не должна их использовать | да | да |
CSS-выражения совсем не рекомендуется использовать при верстке страниц, потому что они могут выполняться великое множество раз на одной и той же странице: когда она отрисовывается, или изменяется ее размер, а также когда выполняется прокрутка по странице (здесь освещена более подробно проблема CSS-выражений). Пересчет выражения может происходить даже при движении мышью!
Ни одна из стандартных тем Drupal не использует CSS-выражений. Поэтому нужно просто убедиться, что их нет в вашей теме.
Требование | Drupal 5 | Drupal 6 |
---|---|---|
CSS- и JS-код не должен вставляться внутри страницы или включаться по минимуму | да | да |
Если ваш сайт является стартовой страницей для многих пользователей, вы, скорее всего, будете использовать альтернативный подход и ознакомитесь с этой рекомендацией. В противном случае вы можете вообще проигнорировать это правило.
Требование | Drupal 5 | Drupal 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
-файла (и ежедневном его обновлении, чтобы быть уверенным в том, что вы предоставляете последнюю версию этого счетчика).
Требование | Drupal 5 | Drupal 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 5 | Drupal 6 |
---|---|---|
Отсутствие редиректов по умолчанию | да | да |
Drupal может перенаправлять пользователей при доступе к URL без алиаса /node/11
к его мнемонической версии /about
, но по умолчанию этого не происходит.
Модуль Global Redirect позволяет осуществить описанный функционал разумным способом. Более подробно описано на странице этого проекта.
Требование | Drupal 5 | Drupal 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
).
Требование | Drupal 5 | Drupal 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 — что объясняет достаточно медленное создание страниц на сервере.
В порядке эффективности применения:
mod_rewrite
отдаются обратно сервером уже в виде файлов (если они существуют), при этом не производится ни одного запроса к базе!gzip
-сжатие для CSS и JS от Owen Barton (Grugnog2) и Ted Serbinski (m3avrck). Уже есть некоторое количество вариантов, но они пока не синхронизированы (с основной ветвью разработки).file_url()
и hook_file_server()
от Wim Leers.Подробно объясняются все правила в YSlow.
В статье объясняется, зачем нужны CSS Sprites, как они работают и возможные проблемы.
Опыт внедрения CSS Sprites. Может быть полезен, если вы интересуетесь этой темой.
Анонс уже упомянутого генератора CSS Sprites.
Автоматическое создание CSS Sprites для CSS-фреймворка Blueprint.
На пальцах объясняется, почему так важно помнить о просмотрах страницы с пустым кэшем.
В статье достаточно подробно объясняется, как отдавать сжатые с помощью gzip
CSS- и JS-файлы.
Сравнение, насколько хорошо сжимаются JS-файлы после использования того или иного минимизатора. Также объясняется, почему стоит избегать использовать Packer из-за его алгоритма обфускации.
Анонс модуля Boost.
Документ поможет вам найти и исправить узкие места при загрузке ваших страниц.
Вложение | Размер |
---|---|
Дополнение для размещения JS-файлов в конце страницы для Drupal 5 | 1,08 Кб |
Дополнение для размещения JS-файлов в конце страницы для Drupal 6 | 1,28 Кб |
hook_file_server для Drupal 5 | 11,61 Кб |