Алгоритмы и задачи клиентской оптимизации

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

Разбить на страницы
Показывать лекцию целиком

8.1. Разгоняем ASP .NET: 100 баллов и оценка "А" в YSlow

Этот раздел написан под впечатлением статьи Viktar Karpach YSlow and ASP.NET: 100 points "A" grade is possible (http://www.karpach.com/ yslow-and-asp-net-100-points-a-grade.htm). Здесь мы разберем по шагам, как максимально ускорить работу вашего сайта на ASP .NET. Далее приводятся пункты рекомендаций советов от Yahoo!, с которыми могут возникнуть сложности.

(рис 8.1) Измерения скорости загрузки сайта при помощи YSlow. Источник: www.karpach.com

8.1.1. Меньше HTTP-запросов

Во-первых, нужно объединить в один файл таблицы стилей и файлы скриптов. Это стоит сделать только для рабочего (не тестового) выпуска сайта при помощи MS BuiLd:

<ItemGroup>
<TextFiles Include="*.css" Exclude="global.css"/>
</ItemGroup>
<Exec Command="echo y| type %(TextFiles.Identity) >> global.css"/>

Таким образом мы можем объединить все CSS- и JS-файлы. Некоторые разработчики зададут резонный вопрос: а что можно сказать по поводу WebResource.axd? В новом AJAX ControL TooLkit есть TooLkitScriptManager, который позволяет объединить большинство ваших файлов WebResource.axd. Также он может применять к ним gzip-сжатие (это будет важно для следующих разделов).

Затем нам нужно создать CSS Sprites. Более подробно они уже были описаны в четвертой лекции. Идея заключается в том, чтобы группировать такие изображения по оси повторения. Например, все изображения, повторяющиеся по вертикали, сложить в vbackground.png, а все повторяющиеся по горизонтали — в hbackground.png. Затем можно использовать background-position: -OffsetPixeLs 0 для позиционирования внутри вертикального файла и background-position: 0 -OffsetPixeLs — для горизонтального.

8.1.2. Используем CDN (Content Delivery Networks)

Сети доставки содержания (англ. CDN, Content Delivery Network) стоят довольно дорого. Но можно поступить проще и использовать для этого GoogLe (подробнее рассказывается в шестой лекции). Естественно, придется немного попотеть, прежде чем собрать из GoogLe App Engine свою идеальную CDN.

Можно также применить msbuild для изменения файлов стилей при использовании CDN. Ниже приведен рабочий пример (в нем пути с ../images/ заменяются на http://karpach.appspot.com/cdn/images/):

<Import Project=".\References\MSBuild.Community.Tasks.targets" />
<Target Name="Release">

  <FileUpdate Files="$(OutputPath)styles\basic.css" Regex="\.\.\/images/(["\)]*)" ReplacementText= 
  "http:=""//karpach.appspot.com/cdn/images/$1" />
</Target>

Теперь нужно положить в CDN все ваши изображения, файлы стилей и скриптов. Здесь как раз и всплывает проблема сброса кэша. Что будет, если пользователь зашел к вам на сайт как раз перед тем, как вы выложили новую версию, в которой изменился файл стилей и некоторые изобра- жения? По-видимому, этот пользователь будет видеть сайт со старыми стилями и картинками еще 7 дней, и он просто не обнаружит изменений, пока не истечет срок действия кэша в его браузере. Это не есть хорошо. Если озвученный вопрос становится актуальным, то стоит посмотреть, что Yahoo! сделали на своем веб-сайте. В название картинки они "зашивают" метку даты и версию. Например, trough_2.0_062308.gif. Это выглядит разумно. Если вы изменяете какой-либо из ваших CSS Sprites, то его нужно вручную переименовать при помощи новой даты и версии (на самом деле процесс переименования файлов должен быть заложен в сам инструмент создания CSS Sprites; подробнее о сбросе кэша и объединении файлов было рассказано в лекции 4).

Однако, по всей видимости, этот подход может быть неприменим для файлов стилей. Для начала стоит обеспечить контроль изменений версий таблиц стилей (в данном случае это SVN). Для картинок это не играет существенной роли, потому что они меняются не так часто. Основная идея заключается в том, что у нас может быть один физический файл (например, basic.css), а на странице используется адрес другого файла (например, basic_14102262.css), где 14102262 — это текущая версия приложения. Затем мы можем применить технологию url rewrite, так что basic_14102262.css будет указывать на basic.css. Пользователь с закэшированной версией basic_14102261.css будет вынужден загрузить новую версию basic_14102262.css, поскольку изменилось имя файла.

Движок Google Apps позволяет легко реализовывать rewrite для URL. Для этого можно просто изменить ваш app.yaml следующим образом:

- url: /cdn/styles/basic_\d*\.css
static_files: cdn/styles/basic.css
upload: cdn/styles/basic\.css

Более подробно о настройке CDN от Google рассказывалось в предыдущей лекции.

8.1.3. Добавляем заголовок Expires

У всех файлов, расположенных в CDN, уже выставлен этот заголовок. Для всех остальных можно создать следующий HttpModule:

private readonly static string[] CACHED_FILE_TYPES = new
string[] { ".jpg", ".gif", ".png",".css" };
public void Init(HttpApplication context)
{
  context.AcquireRequestState += new
EventHandler(context_AcquireRequestState);
}
void context_AcquireRequestState(object sender, EventArgs e)
{
    HttpContext context = HttpContext.Current;
    if (context != null  context.Response != null)
    {
    string fileExtension = Path.GetExtension
    (context.Request.PhysicalPath).ToLower();
    if (context.Response.Cache != null 
    Array.BinarySearch<string>(CACHED_FILE_TYPES, fileExtension)
    >= 0)
    {
      HttpCachePolicy cache = context.Response.Cache;
      TimeSpan duration = TimeSpan.FromDays(365);
      cache.SetCacheability(HttpCacheability.Public);
      cache.SetExpires(DateTime.Now.Add(duration));
      cache.SetValidUntilExpires(true);
      cache.SetNoServerCaching();
      cache.SetMaxAge(duration);
    }
 }
}

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

8.1.4. Располагаем CSS-файлы в начале страницы

Это делается очень просто. Просто надо ввести эти действия в привычку. Помните об этом при разработке модулей на стороне сервера и используйте коллекцию Header.Controls для добавления таблиц стилей на страницу.

8.1.5. Располагаем JavaScript-файлы в конце страницы

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

Library.js

может содержать

function DoSomething()
{
}
А после этого прямо в коде страницы:
<script type='text/javascript'>
  if (typeof (DoSomething) == 'undefined')
  {
  alert('Library is not loaded yet');
  }
</script>

Приведенный пример будет недостаточно верным, потому что загружаемая функция может быть доступна на странице после некоторого времени. Лучше всего создавать дополнительную клиентскую логику, использующую определенный модуль, в самом этом модуле либо проверять через равные промежутки времени, доступен ли необходимый функционал. Более подробно данные техники были освещены в лекции "Ненавязчивый JavaScript" книги "Разгони свой сайт".

8.1.6. Уменьшаем число DNS-запросов

Для этого стоит скопировать внешние ресурсы (картинки, JavaScript файлы) к себе на сайт.

Например, на блоге располагается иконка валидной страницы от W3.Изначально эта иконка находилась на сайте W3 Schools, но для ускорения загрузки сайта стоит скопировать ее в свой проект. Тогда она будет располагаться локально, а при загрузке сайта понадобится на 1 DNS-запрос меньше.

8.1.7. Уменьшаем JavaScript

Минимизацию JS-кода можно осуществлять при публикации очередного выпуска сайта при помощи MS Build (это наиболее разумная позиция — осуществлять все оптимизационные процедуры при превращении сайта из тестового в рабочий). Для этой цели можно использовать YUI compressor. Ниже приведен вариант скрипта для MS Build.

<Target Name="Compress">
  <Message Text="Create temp files ..." />
  <Copy SourceFiles=".\$(ProjectName)\Javascript\ColorPicker.js"
  DestinationFiles=".\$(ProjectName)\Javascript\ColorPicker.js.full"/>
  <Copy SourceFiles=".\$(ProjectName)\Styles\ColorPicker.css"
  DestinationFiles=".\$(ProjectName)\Styles\ColorPicker.css.full"/>
  <Exec Command="java -jar yuicompressor-2.4.2.jar —type js
  .\$(ProjectName)\Javascript\ColorPicker.js.full
  >.\$(ProjectName)\Javascript\ColorPicker.js"/>
  <Exec Command="java -jar yuicompressor-2.4.2.jar —type css
  .\$(ProjectName)\Styles\ColorPicker.css.full
>.\$(ProjectName)\Styles\ColorPicker.css"/>
</Target>

8.1.8. Удаляем дублирующиеся скрипты

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

8.1.9. Настройте ETag

Кэширующий модуль и CDN должны решить эту проблему. Но стоит также иметь в виду, что в ASP .NET есть специальный метод для ETag:

Response.Cache.SetETag

8.2. Разгоняем Drupal

Данный раздел подготовлен при помощи Елены Цаплиной (aka Касихиной) — программиста-разработчика и руководителя нескольких Интернет-проектов: студии дизайна и разработки Интернет-сайтов Aquanther (http://www.aquanther.ru/)), ежедневного женского журнала "Мои подружки" (http://www.moipodruzhki.ru/), программного обеспечения (под управлением ОС Windows) под единым названием — HomAff (сокращение от home affairs).

Елена известна своей публикацией о CMS DrupaL DrupaL ( http://drupaL.ru/node/26290 ), получившей широкое распространение в Рунете.

В данный момент под ее руководством (на базе студии Aquanther) разрабатывается мультимедийный курс по созданию и управлению сайтом с использованием CMS DrupaL. В дальнейшем вместе с мультимедийным курсом планируется распространение модифицированных версий CMS DrupaL, специализированных для определенных типов сайтов, и оказание платной поддержки данной CMS.

8.2.1. Вступление

DrupaL — довольная распространенная CMS, и это наложило на нее свой отпечаток: базовая поставка DrupaL является не готовым решением для определенного вида сайта, а фундаментом для его создания. Существуют "сборки" на базе DrupaL, специализированные под определенные виды сайтов, например, под новостные сайты. Но подобные сборки в данный момент мало распространены и плохо поддерживаются. В связи с этим при создании Интернет-сайта на основе стандартной поставки DrupaL используется большое количество готовых дополнительных модулей и тем оформления для DrupaL либо разрабатываются новые модули и темы специально для данного проекта. Последним этапом работ по созданию сайта является его оптимизация, которую условно можно разбить на 4 шага:

  • встроенная оптимизация Drupal;
  • оптимизация Drupal с помощью модулей;
  • оптимизация конфигурации и обслуживания Drupal;
  • оптимизация сервера.
  • 8.2.2. Встроенная оптимизация Drupal

  • Отключим все неиспользуемые модули, так как при генерации страницы перед отправкой ее браузеру пользователя код определенных модулей может выполняться, даже если функционал данного модуля не используется на сайте. На выполнение кода будет тратиться процессорное время сервера, что приведет к более долгой генерации страницы. Пример такого модуля — Statistics. Вместо статистики, выдаваемой данным модулем, можно взять статистику сервиса — GoogLe AnaLytics.

    При создании сайта используем DrupaL версии 6, так как в нем лучше реализованы внутренние средства кэширования. Также в дополнительных модулях (Views, PaneL и т. д.) для DrupaL версии 6 внедрены эффективные методы кэширования. К сожалению не все модули DrupaL версии 5 реализованы для DrupaL версии 6 (например, модуль Sphinx), о чем не следует забывать при планировании разработки Интернет-сайта. Далее будем рассматривать только DrupaL версии 6.

    Хорошо обдумаем варианты использования модулей наподобие CCK (Content Construction Kit) перед реализацией запланированного. Например, простая задача на хранение в базе сайта тысячи типов продуктов, их названий и описаний, решается с помощью: I создания тысячи терминов таксономии и привязки их к определенному типу материала; I добавления определенному материалу дополнительного поля CCK, в котором будет храниться тип продуктов. При усложнении задачи с помощью условия, что все типы продуктов должны быть разбиты на 10 групп, задача решается двумя вариантами. Вариант первый, с использованием таксономии.

  • Таксономия позволяет создавать иерархию терминов в словаре, т.е. в словаре с терминами может быть 10 терминов первого уровня, а остальные термины будут потомками одного из этих 10 терминов. Создадим нужную иерархию терминов в словаре.
  • Установим дополнительный модуль — HierarchicaL SeLect (http://drupal.org/project/hierarchical_select ), который позволяет в зависимости от вложенности уровней словаря таксономии отображать определенное количество выпадающих списков. Иначе говоря, пользователю при добавлении новой статьи о продукте на сайт будет выведен один выпадающий список, в котором можно выбрать один из 10 терминов первого уровня (группы типов продуктов). После осуществления выбора будет отображен еще один выпадающий список, в котором будут приведены потомки данного термина в иерархическом дереве терминов данного словаря таксономии (типы продуктов).

    Вариант второй, с использованием CCK.

  • Необходимо определенному материалу добавить 2 дополнительных поля CCK: одно — для хранения групп продуктов, второе — для хранения типов продуктов.
  • Нужно настроить взаимодействие данных полей в зависимости от выбора значений в них.
  • Главные ошибки при решении подобных задач, создающие дополнительную нагрузку на базу данных:
  • создание у определенного материала 10 полей CCK, по полю на каждую группу;
  • при создании у определенного материала поля CCK не указана его длина (поэтому по умолчанию считается, что она максимально возможная).
  • Используем встроенное кэширование Drupal. Оно позволяет кэшировать информацию, извлеченную из базы данных, а также информацию, полученную при обработке извлеченной информации из PHP.
  • Кэширование системы меню, фильтров форматов ввода, переменных администрирования (например, название сайта) и настроек модуля производится автоматически. Остальные параметры кэширования можно настроить на странице "Управление — Производительность" ( http://www.exampLe.ru/admin/settings/performance )

    На данной странице можно настроить:

  • компрессию страниц;
  • кэширование блоков;
  • оптимизацию CSS-файлов;
  • оптимизацию JavaScript-файлов.
  • Включим кэширование страниц в режим "нормальный". В данном режиме кэширования страниц при просмотре страницы в первый раз (анонимным, незарегистрированным в системе пользователем) производится сохранение сгенерированной страницы в кэш. В дальнейшем при просмотре данной страницы (анонимным пользователем) она не генерируется заново, а берется из кэша, что значительно ускоряет работу Drupal.

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

    (рис 8.2) Настройки производительности для Drupal

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

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

    Включим кэширование блоков. Принцип работы кэширования блоков аналогичен принципу кэширования страниц. Для супер-пользователя (первого зарегистрированного пользователя при установке DrupaL, его id равен 1) блоки никогда не кэшируются.

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

    8.2.3. Оптимизация Drupal с помощью модулей

  • Установим модуль Authenticated User Page Caching (Authcache), скачать его можно по адресу http://drupaL.org/project/authcache . Данный модуль позволяет кэшировать страницы, как для анонимных пользователей, так и для аутентифицированных ("залогинившихся") пользователей, более качественно, чем встроенное кэширование DrupaL. При установке данного модуля необходимо перенастроить динамический контент на страницах (например, вывод имени аутентифицированного пользователя).

    Authcache сохраняет сжатый кэш страниц отдельно для каждого пользователя или роли. Кэш сохраняется в базе данных или в стороннем средстве кэширования (memcahed, APC, и т. д.). Кэшированные версии страниц для аутентифицированных пользователей (кроме супер-пользователя) передаются с помощью AJAX, поэтому достигается очень быстрое отображение страницы в браузере. Если у аутентифицированного пользователя в браузере отключены JavaScript, то он получает страницы не из кэша. На некоторых серверах скорость загрузки страницы уменьшается до 1 миллисекунды.

    Для установки модуля:

  • скачаем его;
  • распакуем модуль в папку /sites/all/modules ;
  • скопируем файл ajaxauthcache.php из папки модуля в корневую директорию сайта (там же находится файл index.php )
  • откроем файл settings.php (в папке /sites/default ) и добавим следующий код (без примечаний за // ...) в начало файла после тега <?php:
    $conf['cache_inc'] =
    './sites/all/modules/authcache/api/authcache.inc';
    $conf['authcache'] = array(
      'default' => array(
      // технология кэширования - apc, memcache, db, file,
      // eacc or xcache
      'engine' => 'db',
      // если используем memcached (host:port, например,
      // 'localhost:11211')
      'server' => array(),
      // если используем процесс memcached, shared или single
      'shared' => TRUE,
      // кэш ключа префикса (для нескольких сайтов)
      'prefix' => '',
      // если используем кэширование на файлах — указываем
       // путь их сохранения
      'path' => 'files/filecache',
      // статический массив кэша (расширенный)
      'static' => FALSE,
       ),
    );
  • В данном коде устанавливаются настройки модуля Authcache. Указываем, что будем хранить кэш страниц в базе данных ('engine' => 'db'), поэтому все остальные установки не имеют значения, и мы оставляем их без изменений. Более подробно о параметрах данного кода можно прочитать на странице http://drupaL.org/project/cacherouter (на английском языке).

    Включим модуль Authcache на странице "Управление — Модули" ( http://www.exampLe.ru/admin/buiLd/moduLes ). После чего настроим его работу на странице "Управление — Производительность — Authcache" ( http://www.exampLe.ru/admin/settings/performance/authcache ):

  • укажем роли, для которых необходимо кэшировать контент;
  • аннулируем все пользовательские сессии (переключатель — Invalidate all user sessions ) при первом запуске;
  • выставим время хранения кэша (в часах);
  • нажмем кнопку "сохранить и очистить кэш" ( Save clear cached pages ) для сохранения изменений.
  • (рис 8.3) Настройка модуля Authcache

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

    В шаблонах тем оформления используем переменные:

  • $user_name - для отображения имени аутентифицированного пользователя;
  • $user_link - для отображения ссылок, связанных с профилем пользователя;
  • $is_page_authcache — если установлен в TRUE, то все хуки данного шаблона темы оформления будут сохранены в кэш.
  • Можно также ознакомиться с примером /sites/all/modules/authcache/modules/authcache_example, который показывает, как настроить блоки с пользовательским содержанием (с контентом пользователя).

  • Если необходимо кэшировать страницы только для анонимных пользователей (без аутентифицированных), можно установить модуль Cache Router. Данный модуль лежит в основе модуля Authcache и кэширует страницы лучше встроенного кэширования Drupal. Скачаем модуль по адресу http://drupal.org/project/authcache . После скачивания распакуем модуль в папку /sites/all/modules. Включим модуль Cache Router на странице "Управление — Модули" (http://www.example.ru/admin/build/modules ). Откроем файл settings.php (в папке /sites/default ) и добавим следующий код в начало файла перед тегом <?php:
    $conf['cache_inc'] = './sites/all/modules/cacherouter/cacherouter.inc';
    $conf['cacherouter'] = array(
      'default' => array(
      'engine' => 'db',
      'server' => array(),
      'shared' => TRUE,
      'prefix' => '',
      'path' => 'sites/default/files/filecache',
      'static' => FALSE,
      'fast_cache' => TRUE,
      ),
     );

    После осуществления действий, приведенных выше, страницы создаваемого сайта будут отдаваться сервером браузеру пользователя в сжатом виде, а вот CSS и JavaScript — нет. Исправим это:

  • cкачаем модуль CSS Gzip со страницы http://drupal.org/project/css_gzip ;
  • cкачаем модуль JavaScript Aggregator со страницы http://drupal.org/project/javascript_aggregator ;
  • распакуем модули в папку /sites/all/modules ;
  • включим модули на странице ldquo Управление - Модули rdquo ( http://www.example.ru/admin/build/modules );
  • активируем сжатие CSS и JavaScript на странице "Управление - Производительность" ( http://www.example.ru/admin/settings/performance ), отметив чекбоксы "GZip CSS" и "GZip JavaScript" ;
  • внесем изменение в файл .htaccess, расположенный в корневой директории сайта (на основании данных из README.txt, входящего в состав модуля CSS Gzip), пропишем меду тегами <IfModule mod_rewrite.c> и </IfModule> следующий код:
    ### START CSS GZIP ###
    # Requires mod_mime to be enabled.
    <IfModule mod_mime.c="">
      # Send any files ending in .gz with x-gzip encoding
      # in the header.
      AddEncoding x-gzip .gz
    </IfModule>
    # Gzip compressed css files are of the type 'text/css'.
    <FilesMatch "\.css\.gz$">
      ForceType text/css
    </FilesMatch>
    <IfModule mod_rewrite.c="">
      RewriteEngine on
      # Serve gzip compressed css files
      RewriteCond %{HTTP:Accept-encoding} gzip
      RewriteCond %{REQUEST_FILENAME}\.gz -s
      RewriteRule ^(.*)\.css $1\.css\.gz [L,QSA,T=text/css]
    </IfModule>
    ### End CSS GZIP ###
  • сохраним настройки и очистим кэш.

    Если в шаблонах темы оформления необходимо использовать дополнительные CSS и JavaScript, то желательно подключать их с помощью следующих команд:

  • drupal_add_css(‘путь к CSS относительно корневой директории сайта’);
  • drupal_add_js(‘путь к JavaScript относительно корневой директории сайта’);
  • для того, чтобы они оптимизировались (включались в один исходный CSS или JavaScript-файл) и сжимались совместно со всеми остальными CSS- или JavaScript-файлами, используемыми на сайте.

    8.2.4. Оптимизация конфигурации и обслуживания Drupal

    От оптимизации Drupal с помощью модулей, перейдем к более сложной оптимизации — оптимизации конфигурации и обслуживания Drupal.

  • Уменьшим время хранения пользовательских сеансов. Так как DrupaL хранит их в своей базе данных, то сокращение времени их хранения разгрузит базу данных, особенно, если на сайт приходят тысячи пользователей в день. По умолчанию сеансы хранятся 55 часов, уменьшим время их хранения до 24 часов. Для этого на сервере в папке /sites/default в файле settings.php изменим строку
    ini_set('session.gc_maxlifetime', 200000);

    на

    ini_set('session.gc_maxlifetime', 86400); // 24 часа (в секундах)

    Также в этом файле можно сократить время жизни кэшированных страниц сеансов до 24 часов, изменив строку

    ini_set('session.cache_expire', 200000);

    на

    ini_set('session.cache_expire', 1440); // 24 часа (в минутах)

    Напоследок в этом же файле изменим время хранения cookie в браузере пользователя, сократив его до 24 часов:

    ini_set('session.cookie_lifetime', 86400); // 24 часа (в секундах)

    Если установить время хранения cookie в браузере пользователя равным 0, то cookie будет удаляться сразу после закрытия Интернет-браузера пользователем.

  • Сократим количество сообщений протоколирования работы сайта, сохраняемых в базе данных. На странице "Управление - Отчеты и сообщения - Отчеты в базе данных" ( http://www.example.ru/admin/settings/logging/dblog ), выставим необходимый максимум отчетов, хранимых в базе данных. Данные отчеты полезны для просмотра попыток взлома сайта, поэтому минимум, который можно выбрать, — это 100 записей. Просмотреть данные отчеты можно, перейдя на страницу "Управление $$\Rightarrow$$ Недавние записи в системном журнале" (http://www.example.ru/admin/reports/dblog ).
  • Настроим выполнение регулярных процедур (задачи cron), так как при их выполнении очищаются журналы записей сообщений протоколирования работы сайта, устаревшие записи кэша и другие статистические данные. Самым простым способом настройки автоматического запуска регулярных процедур является установка модуля — Poormanscron. Скачаем данный модуль по адресу http://drupal.org/project/poormanscron . Распакуем его в папку /sites/all/modules, активируем модуль на странице "Управление - Модули" (http://www.example.ru/admin/build/modules ). Установим интервал запусков Cron на странице "Управление - Poormanscron" (http://www.example.ru/admin/settings/poormanscron) равным 360 минут (один раз в 6 часов).
  • В составе Drupal имеется модуль Throttle, который производит оценку количества посетителей сайта и отключает некоторые функцио- нальные возможности, если достигнут порог, установленный администратором. После активации модуля на странице "Управление - Модули" (http://www.example.ru/admin/build/modules) можно увидеть, что у не которых модулей на данной странице кроме флажков включения появились флажки, отмечающие, должен ли данный модуль регулироваться Throttle или нет. Также некоторые блоки могут регулироваться Throttle("Управление -Блоки" (http://www.example.ru/admin/build/block). Настройка Throttle производится на странице "Управление - Регулятор" (http://www.example.ru/admin/settings/throttle), где указывается минимальное количество анонимных посетителей и минимальное количество зарегистрированных пользователей для включения ограничения функционала сайта для них. На этой странице установим вероятностный ограничитель авторегулятора на 20%, чтобы для одного из каждых 5 запросов на выдачу страницы для браузера пользователя производился 1 запрос к базе данных для определения нагрузки на сайт."
  • 8.2.5. Оптимизация сервера

    Так как сервер сайта может работать под управлением разных операционных систем:

  • Windows
  • Linux
  • FeeBSD
  • то в каждом случае настройки оптимизации сервера будут отличаться (т. е. установка eAccelerator в Windows и Lunux сильно различается). Ниже приведены только основные рекомендации по оптимизации сервера. Подробно из рекомендаций рассмотрена лишь установка PHP-акселератора на сервер Ubuntu 8.04, так как PHP-акселератор значительно ускоряет работу сайта.

  • Установим eAccelerator. Он является PHP-акселератором, основное назначение которого состоит в кэшировании бинарного представления кода.
  • Соединимся с сервером по SSH и авторизуемся с правами root. Выполним команды для установки дополнительного пакета php5-dev:
    sudo apt-get install php5-dev
    sudo apt-get install make
  • Выполним команды для установки eAccelerator:
    sudo cd /tmp/
    sudo wget http://bart.eaccelerator.net/source/0.9.5.3/eaccelerator-
    0.9.5.3.tar.bz2
    sudo tar xvjf eaccelerator-0.9.5.3.tar.bz2
    sudo cd eaccelerator-0.9.5.3
    sudo phpize
    sudo ./configure —enable-eaccelerator=shared
    sudo make
    sudo make install
  • Отредактируем файл php.ini в папке /etc/php5/apache2, вставим в начале файла после тега [PHP] следующий код:
    ; eAccelerator configuration
    ; Note that eAccelerator may also be installed as a PHP extension
    or as a zend_extension
    ; If you are using a thread safe build of PHP you must use
    ; zend_extension_ts instead of zend_extension
    ;extension = "/usr/lib/php5/20060613+lfs/eaccelerator.so"
    zend_extension = "/usr/lib/php5/20060613+lfs/eaccelerator.so"
    eaccelerator.shm_size = "16"
    eaccelerator.cache_dir = "/var/cache/eaccelerator"
    eaccelerator.enable = "1"
    eaccelerator.optimizer = "1"
    eaccelerator.check_mtime = "1"
    eaccelerator.debug = "0"
    eaccelerator.filter = ""
    eaccelerator.shm_max = "0"
    eaccelerator.shm_ttl = "0"
    eaccelerator.shm_prune_period = "0"
    eaccelerator.shm_only = "0"
    eaccelerator.compress = "1"
    eaccelerator.compress_level = "9"
    eaccelerator.allowed_admin_path = "/var/www/eaccelerator"
  • При использовании Zend Optimizer и/или ionCube Loader приведенный выше код будет выглядеть так:
    ; eAccelerator configuration
    ; Note that eAccelerator may also be installed as a PHP extension
    or as a zend_extension
    ; If you are using a thread safe build of PHP you must use
    ; zend_extension_ts instead of zend_extension
    ;extension = "/usr/lib/php5/20060613+lfs/eaccelerator.so"
    zend_extension = "/usr/lib/php5/20060613+lfs/eaccelerator.so"
    eaccelerator.shm_size = "16"
    eaccelerator.cache_dir = "/var/cache/eaccelerator"
    eaccelerator.enable = "1"
    eaccelerator.optimizer = "1"
    eaccelerator.check_mtime = "1"
    eaccelerator.debug = "0"
    eaccelerator.filter = ""
    eaccelerator.shm_max = "0"
    eaccelerator.shm_ttl = "0"
    eaccelerator.shm_prune_period = "0"
    eaccelerator.shm_only = "0"
    eaccelerator.compress = "1"
    eaccelerator.compress_level = "9"
    eaccelerator.allowed_admin_path = "/var/www/eaccelerator"
    ; ionCube Loader configuration
    zend_extension=/usr/local/lib/ioncube/ioncube_loader_lin_5.2.so
    ; Zend Optimizer configuration
    zend_extension=/usr/local/lib/Zend/ZendOptimizer.so
    zend_optimizer.optimization_level=15
  • Создадим кэш-каталог для eAccelerator, выполнив команды
    sudo mkdir -p /var/cache/eaccelerator
    sudo chmod 0777 /var/cache/eaccelerator
  • Перезапустим Apache:
    sudo /etc/init.d/apache2 restart
  • Рекомендуем установить Web-сервер nginx и настроить его работу с веб-сервером Apache так, чтобы страницы он отдавал браузеру пользователя Apache, а статический контент (CSS, JavaScript, фото и т. д.) —nginx. Либо полностью замените веб-сервер Apache веб-сервером nginx.
  • Установим в Apache модуль mod_expires , который позволяет Drupal посылать HTTP-заголовки Expires , кэшируя все статические файлы (изображения, CSS, JavaScript и т. п.) в Интернет-браузере пользователя на определенный срок или до момента появления новых версий файлов. Настройки взаимодействия Drupal и модуля mod_expires веб-сервера Apache находятся в файле .htaccess в корневой директории сайта:
    # Включить mod_expires.
    <IfModule mod_expires.c="">
      # Разрешить истечение срока.
      ExpiresActive On
      # Кэшировать все файлы на две недели после доступа (A).
      ExpiresDefault A1209600
      # Не кэшировать динамически генерируемые страницы.
      ExpiresByType text/html A1
    </IfModule>
  • Для ускорения обработки .htaccess файлов веб-сервером их содержание можно перенести в главный файл конфигурации Apache — httpd.conf. После чего необходимо запретить поиск файлов .htaccess в пределах корневого каталога веб-сервера, установив AllowOverride в None:
    <Directory/>
    AllowOverride
    …
    </Directory>
  • Ввиду того, что некоторые модули внутри своих каталогов могут соержать файлы .htaccess, следует аккуратно работать с данным видом оптимизации, чтобы при переносе содержимого всех файлов .htaccess в httpd.conf не пропустить не один файл .htaccess.
  • Установим на сервере:
  • систему анализа лог-файлов (например, AWstats);
  • систему мониторинга производительности сервера (например,Munin);
  • систему для учета сетевого трафика (например, Vnstat).
  • Включим кэш MySQL и установим его размер равным 64 мегабайтам. Для этого отредактируем файл my.cnf в папке /etc/mysql (при использовании Ubuntu 8.04). Изменим значение
    # query_cache_limit = 1M
    # query_cache_size = 16M

    на

    query_cache_limit = 1M
    query_cache_size = 64M

    После чего перезапустим MySQL командой

    /etc/init.d/mysql restart

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

  • Проверим: загрузку центрального процессора, нехватку оперативой памяти или места на диске и возможную перегрузку линии связи сервера с Интернетом. Если необходимо разместить базу данных на отдельном сервере, то изменим настройки соединения с базой данных, — они находятся в файле settings.php (в папке /sites/default ) — либо разместим сайт на кластере из серверов (например, воспользовавшись услугами сервиса Amazon C2).
  • 8.2.6. Заключение

    Для просмотра информации о сервере из Drupal существует удобный модуль — System information (http://drupal.org/project/systeminfo ). После его установки и активации информацию о вашем сервере можно посмотреть на странице http://www.example.ru/admin/reports/systeminfo.

    8.3. Разгоняем Wordpress

    Wordpress (http://www.wordpress.org/) является сейчас наиболее популярной платформой для одиночного хостинга блогов. Ряд хостинг-провайдеров уже даже предлагают площадки с предварительно установленным Wordpress, а в большом количестве изданий рассуждают, как лучше заработать на новом блоге или правильно его использовать. Ниже будет освещен ответ на один из основных вопросов, встающих перед администраторами блогов: как сделать так, чтобы сайт быстро работал. Нижеизложенный материал рассчитан на максимально широкую аудиторию пользователей.

    8.3.1. Основные положения

    Ускорение работы любой системы возможно, в основном, за счет кэширования некоторых (тут стоит подчеркнуть, что именно некоторых, а не всех подряд) часто используемых операций. Все кэширующие мероприятия, в том числе и для Wordpress, можно разбить на несколько основных частей:

  • база данных;
  • компиляция серверных скриптов (PHP);
  • статические страницы;
  • клиентская составляющая.
  • Данную проблему можно проиллюстрировать при помощи следующего рисунка:

    8.3.2. База данных

    Так уж сложилось, что основное узкое место практически любой системы заключается в базе данных, поэтому ее стараются ускорить всеми возможными способами. Стоит отметить, что проблема многочисленных вызовов к базе данных не решается просто уменьшением их количества (для предоставления той же самой информации), тут надо подходить более комплексно и настраивать многоуровневый кэш для запросов. Относительно MySQL это сделать довольно просто: достаточно прописать в конфигурационном файле my.cnf (или my.ini ) следующие параметры (в случае большого количества оперативной памяти 20 Мб может быть увеличено до любого приемлемого количества, но не стоит здесь увлекаться: скорость поиска данных в кэше напрямую зависит от размера самого кэша):

    (рис 8.4) Кэширующие звенья для Wordpress, источник: www.arnebrachhoLd.de
    query-cache-type=1
    query-cache-size=20M

    Для оптимизации таблиц (что позволит уменьшить время запросов на 20—50%) можно воспользоваться дополнением Optimize DB (http://yoast.com/wordpress/optimize-db/ ), которое позволит существенно уменьшить размер таблиц MySQL и улучшить их структуру. Для кэширования запросов к базе данных также существует специальное дополнение, DB Cache Reloaded (http://wordpress.org/extend/plugins/db-cachereloaded/ ).

    8.3.3. Компиляция серверных скриптов

    Каждый раз, когда исполняется PHP-скрипт, он заново компилируется в памяти в исполняемый код, что требует значительного времени. Что бы избежать повторной компиляции одних и тех же скриптов, используются такие приложения, как APC ( http://pecl.php.net/package/APC) или eAccelerator (http://eaccelerator.net/), которые сохраняют уже скомпилированный код в памяти и позволяют выполнять его значительно (до нескольких десятков раз) быстрее. Также данные решения хорошо справляются с большим количеством маленьких файлов, которые подключаются при обработке запроса к странице, снижая издержки при обращении к файловой системе. PHP-движок не загружает каждый раз файлы с диска (или из дискового кэша) — он получает сразу исполняемый код, что наного увеличивает скорость выполнения. После оптимизации базы данных (настройки кэширования) это одно из наиболее узких мест (за исключением создания статических страниц вместо динамической их генерации)."

    8.3.4. Статические страницы

    Следующим шагом для борьбы с большим временем подготовки страницы на сервере будет полное кэширование создаваемой страницы в один файл или одну запись в оперативной памяти. Для включения внутреннего кэширования на уровне самого Wordpress достаточно раскомментировать (или добавить) в файл wp-config.php следующие строки (предварительно проверив, что директория wp-content/cache доступна для записи, иначе ничего не получится):

    define('ENABLE_CACHE', true );
    define('CACHE_EXPIRATION_TIME', 900);

    Более серьезных результатов кэширования можно добиться при помощи дополнения WP-Super-Cache (http://ocaoimh.ie/wp-supercache/ , базирующегося на WP-Cache, http://mnm.uib.es/gallir/wpcache-2/) или Hyper Cache (http://www.satollo.com/english/wordpress/hyper-cache), которое вообще не будет осуществлять никаких запросов к базе данных для отображения внешних веб-страниц. Однако при этом станет невозможно учитывать статистику посещений через встроенные в Wordpress методы (только через внешние счетчики или по логам сервера). Для Wordpress, установленного на IIS, также лучше всего будет использовать именно WP-Super-Cache вместо IIS Output Caching. Это подробно рассматривается в соответствующей заметке; ниже приведено число запросов в секунду при том или ином методе серверного кэширования.

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

    (рис 8.5) Рис. 8.5. Производительность кэширования Wordpress для IIS, источник: bLogs.iis.net

    8.3.5. Клиентская часть

    Основной минус богатства тем для Wordpress — то, что они в основном разрабатываются любителями. Как следствие, такие темы могут состоять из большого количества картинок и файлов стилей, которые в совокупности загружаются очень медленно. Однако ситуация поправимая. Для ускорения загрузки сайта в самом браузере (а это, по мнению экспертов Yahoo!, занимает 95% времени общей загрузки страницы) можно воспользоваться несколькими решениями:

  • Включить сжатие страниц в самом Wordpress. Делается это через "Настройки - чтение" ("WordPress должен упаковывать статьи (gzip), если браузер запросит это").
  • CSS Compress (http://dev.wp-plugins.org/wiki/css-compress) — дополнение к Wordpress, которое автоматически минимизирует и сжимает CSS-файлы.
  • PHP Speedy (http://aciddrop.com/php-speedy/) позволяет объединить все CSS- и JS-файлы, настроить клиентское кэширование и сжатие текстовых файлов. Это позволяет значительно ускорить за грузку страниц для конечных пользователей (особенно если это ваши постоянные посетители). Устанавливается и как плагин, и как отдельное приложение."
  • Web Optimizer (http://code.google.com/p/web-optimizator/) устанавливается и как отдельное веб-приложение, и как встроенный плагин. Обладает более широкими возможностями, в частности, автоматическим созданием CSS Sprites (что ускоряет загрузку страниц для пользователей IE) и добавлением всех серверных правил в .htaccess-файл (что обеспечивает более широкую совместимость и снимает нагрузку с PHP-скриптов на анализ кэширования и осуществление сжатия). Также Web Optimizer позволяет настроить "ненавязчивую" загрузку JavaScript и распределить изображения по нескольким статическим хостам.
  • В общем, даже самый обычный блог может быть ускорен в несколько (десятков) раз за считанные минуты. Скорее всего, в ближайшем будущем уже появятся отдельные сборки Wordpress, настроенные на максимальную производительность при задействовании любой темы и произвольной посещаемости блога. Сейчас же можно просто воспользоваться вышеприве- денными советами и порадоваться за значительное увеличение числа посещений и постоянных читателей.

    8.4. Разгоняем Joomla! 1.5

    Скорее всего, многие уже слышали о медленной работе JoomLa! (http://www.joomLa.org/), одной из самых популярных бесплатных CMS, равно как и о ее сильной уязвимости для атак хакеров. Благодаря простоте создания расширений для JoomLa! сейчас доступно несколько (десятков) тысяч разнообразных модулей, компонентов и расширений, позволяющих установить на сайт практически произвольный функционал: от полноценной социальной сети до Интернет-магазина.

    Естественно, что у такой простоты есть и обратная сторона: большинство расширений создаются без учета требований высокой производительности и какой-либо оглядки на мощности конечных серверов, очень часто даже не выделенных или виртуальных, а расположенных на общем хостинге. Разработка бесплатных решений оборачивается 100-500% замедлением в скорости загрузки сайта. Давайте разбираться, как с этим можно бороться.

    8.4.1. Серверная часть

    Для начала посмотрим, что можно сделать с серверной производительностью. Мы исследовали стандартную сборку JoomLa!, но даже в такой комплектации на отдельном сервере время создания страницы занимало 0,312 с (замер времени ответа производился с помощью curl, интерфейс к которому выложен на webo.in,http://webo.in/my/action/timings/). Это не очень много, но в условиях виртуального хостинга может возрасти многократно, даже на изначально хорошо оптимизированных окружениях.

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

    Кэширование на стороне сервера

    Включение встроенного кэширования в Joomla! 1.5 сократило время ответа тестируемого сервера примерно на 30% (на 0,107 с). Прекрасно понятно, что в большинстве случаев оно будет практически бесполезно: если необходимо сократить время создания страниц на порядок, то нужны более кардинальные методы.

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

    Если просто включить Web Optimizer ( http://code.google.com/p/weboptimizator/ ) в процесс создания страниц, то время обработки документа возрастет незначительно (после создания всех кэширующих файлов на 0,006 с или 3% на тестовом сервере). Дополнительно включив HTML-кэши- рование в Web Optimizer, можно сократить время отдачи документа до 0,08 с (почти в 4 раза по сравнению с исходным временем создания страницы). Сразу стоит отметить, что установка вроде бы аналогичного по функциональ- ности дополнения Content Static ( http://extensions.joomla.org/extensionssite-management/cache/5104/details/ ) визуально на производительности никак не отразилась.

    Очевидно, что более грамотным будет кэшировать отдельные модули на странице, оставляя нужные места (или, как их любят называть в шаблонных движках, — заглушки) динамическими. Однако данное решение требует существенного вмешательства в алгоритм работы самой CMS. Дополнение System-Cache (включено в сборку по умолчанию) работает именно по такому принципу и на данный момент обеспечивает практически наилучшую производительность (при большой стабильности алгоритма).

    Заканчивая речь про кэширование создаваемых страниц на стороне сервера, стоит упомянуть, что дополнение JoomLa Performance Booster (http://www.joomLatwork.com/products/components/joomLa-performance.htmL) в ходе тестирования показало результаты примерно на 50% (пятикратный прирост производительности по сравнению с "обычной" версией) лучше, чем System-Cache, однако может работать не очень стабильно.

    Кэширование запросов к базе данных

    К серверному кэшированию можно подойти и с другой стороны: ограничить число запросов к базе данных, — обычно именно эта часть вызывает наиболее серьезную "утечку" производительности. Для JoomLa! существует расширение, позволяющее закэшировать все (или почти все) запросы к базе. Тут стоит понимать, что база данных сама по себе может работать достаточно быстро, и подобное решение будет эффективно только в том случае, если восстановление закэшированного значения выборки на порядок (или хотя бы в разы) быстрее, чем осуществление самой выборки (например, 1 мс против 10 мс). В противном случае прироста производительности не произойдет.

    Дополнение Query Cache (http://extensions.joomLa.org/extensions/site-management/cache/3180/detaiLs) позволяет задействовать как файловую систему, так и популярные кэширующие подсистемы (APC, Memcache и др.) для сохранения выполненных запросов.

    8.4.2. Клиентская часть

    Для оценки эффективности решений для клиентской оптимизации использовалось хорошо зарекомендовавшее себя (и относительно беспристрастное) дополнение к Firefox — YSLow.

    "Чистая" система

    "Чистая" установка Joomla! 1.5 набрала 65 баллов из 100. Вполне приемлемо. Стоит понимать, что если на систему дополнительно поставить десяток модулей и компонентов, то оценка резко ухудшится до 30-40.

    Следующий этап: архивирование

    В Joomla! есть встроенный gzip. Однако, во-первых, он работает через PHP, во-вторых, только для HTML-файлов. Грустно, что и видно по оценке: она поднялась только до 67.

    CssJsCompress

    Довольно известное дополнение (http://extensions.joomla.org/extensions/site-management/cache/7350/details), позволяющее объединять CSS- и JS-файлы. Однако оно не добавляет к ним всех кэширующих заголовков и сжатия, что и отразилось на результате: всего 72 балла по YSlow. В самой Joomla! gzip при этом был включен. Дополнение CSS/JS Cache ( http://extensions.joomla.org/extensions/site-management/cache/7801/details) не удалось заставить корректно работать."

    Joomla Perfomance Booster

    Joomla Performance Booster (http://www.joomlatwork.com/products/components/joomla-performance.html) является платным дополнением (39 евро) и представляет собой наиболее мощное "встроенное" решение для Joomla! 1.5. После его установки и настройки (объединение JavaScript работало "со скрипом" и его пришлось выключить) был достигнут результат в 73 балла (вполне вероятно, что при правильной работе с JavaScript оценка YSlow поднялась бы и до 75). В целом достаточно мощное дополнение, поскольку обеспечивает кроме самого кэширования еще и очень гибкое управление созданным кэшем."

    Однако данное дополнение возможно подключить вместе с приложением Web Optimizer (которое возьмет на себя всю логику преобразования клиентской части), что позволит существенно ускорить работу сайта на Joomla! практически любой сложности.

    Smart Optimizer

    Далее был протестирован Smart Optimizer (http://farhadi.ir/works/smartoptimizer, как отдельное PHP-приложение) — по характеру работы полностью аналогичный известному Minify (http://code.google.com/p/minify/, дополнение Minify4Joomla, http://extensions.joomla.org/extensions/site-management/cache/7183/details , "завести" не удалось). Установка у него достаточно сложная для непрофессионала, к тому же приходится править шаблоны вручную, нет возможности объединять файлы из разных директорий. Однако все остальное на высоте: оценка поднялась до 85. В самой Joomla! gzip при этом был включен."

    Web Optimizer

    Web Optimizer (www.web-optimizer.ru, как отдельное PHP-приложение или как плагин), естественно, устанавливается в "два клика" и обладает более мощным клиентским арсеналом: при отключенном сжатии в самой Joomla! оценка поднялась до 94 (с 65 изначально). Наверное, тут уже дополнительных комментариев не нужно.

    8.4.3. Заключение

    На данный момент для Joomla! 1.5 не существует более мощного бесплатного решения для оптимизации производительности, чем Web Optimizer. PHP Speedy (http://code.google.com/p/phpspeedy/), к сожалению, доступен только для Joomla! 1.0.

    8.5. Разгоняем Joostina

    Материал для данного раздела и консультирование по вопросам производительности Joostina предоставил Николай Кирш — веб-разработчик, основатель и технический лидер проекта Joostina CMS (http://www.joostina.ru/). Профессиональные приоритеты: качественный код, оптимизация под нагрузки, клиентская оптимизация.

    Joostina родилась и развивается с изначальной целью: быть максимально быстрой и эффективно использовать ресурсы сервера, не уменьшая при этом удобств как для пользователя, так и для администратора сайта. В основе системы лежит CMS Joomla! 1.0.x, считающаяся уже классикой. За время развития проекта было учтено максимум пожеланий пользователей по насыщению системы необходимым функционалом, изначально отсутствующим в Joomla!. Но кроме новых возможностей также добавились новые настройки, позволяющие оптимизировать сайт под более конкретные задачи и типовые Интернет-решения.

    Оптимизацию CMS Joostina можно разделить на 3 ступени:

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

    8.5.1. Оптимизация через базовые настройки системы

    Самый быстрый и безопасный способ настроить свой сайт на более высокую скорость и выжать из него максимум возможностей — основательно ознакомиться с настройками, располагающимися в "Глобальной конфигурации". Для доступа к настройкам необходимо авторизоваться с правами Супер-администратора в административной части, называемой так же панелью управления: http://www.exampLe.ru/administrator. Далее надо выбрать пункт меню "Сайт — Глобальная конфигурация", или прямо на главной страницы панели управления, через кнопку быстрого доступа "Глобальная конфигурация".

    Отключить генерацию RSS (syndicate)

    Joostina, как и большинство современных CMS, умеет формировать RSS-ленты из материалов, размещенных на сайте. Чтобы браузеры при отображении страниц сайта автоматически подхватывали RSS-ленты, ссылки на них прописываются в HTML-код страниц через теги примерно такого содержания:

    <link rel="alternate" type="application/rss+xml" title="Joostina v 1.3.0 b"
    href="http://www.example.ru/index2.php?option=com_rssfeed=0amp;no_html=1" />

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

    Использовать шаблон

    Для каждого пункта меню в панели управления можно выбрать уникальное отображение и состав модулей. Но также для каждого из пунктов меню можно назначить уникальный шаблон. Если такая возможность на сайте не используется, то ее следует отключить. Для этого и создана данная настройка. Параметр позволяет выбрать единый шаблон для всего сайта, что исключит один запрос и его обработку для выбора конкретного шаблона. Аналогичная настройка существует для панели управления.

    Отключить мамботы группы system

    Мамботы — это чаще всего небольшие PHP-сценарии, срабатываю щие на определенном этапе работы системы. Мамботы группы system сра- батывают в момент инициализации системы. Обычно в группу входят расширенные обработчики SEF и библиотеки подключения Javascript. Все мамботы этой группы можно посмотреть в панели управления "Меню - Мамботы -Мамботы сайта". Если справа в списке выбора типа нет группы system, то настройку рекомендуется отключить, это сделает ненужным один запрос в базу и инициализацию механизма.

    Отключить мамботы группы content,

    Отключить мамботы группы mainbody

    Действие данного пункта аналогично группе system, но поступать тут надо внимательнее. Группа content — основная, за счет нее выводятся изображения, вставленные в текст через тег {mosimage}, разбивка на страницы внутри текста и т. д. Безопаснее всего поочередно снимать мамботы с публикации и смотреть, что изменилось на сайте. Если все мамботы не опубликованы, а сайт отображается верно, — можно отключить всю группу.

    Использовать неопубликованные мамботы

    Мамботы группы content часто работают, заменяя определенные те- ги в тексте, например, {mosimage}. Но если мамбот не опубликован, то система его все равно использует — чтобы убрать из текста этот самый тег {mosimage}. Если на сайте такие мамботы не используются, то лучше активировать данную настройку, исключив неиспользуемые обращения к базе данных и подключение лишних файлов.

    Авторизация на сайте

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

    Время существования сессии на фронте

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

    Отключить сессии на фронте

    При посещении сайта авторизованным пользователем или даже гостем для него запускается механизм инициализации сессий, что влечет за собой запись данных в базу, создание cookie у пользователя и постоянную проверку авторизации. Если авторизация на сайте не важна, то параметр рекомендуется отключить. Но учтите, что модуль отображающих посетителей будет выдавать не точную информацию, так как он основывается на данных записанных в таблице сессий. Параметр рекомендуется использовать совместно с пунктом "Авторизация на сайте".

    Отключить контроль доступа к содержимому

    Хотя в Joostina имеется не очень много возможностей для полноценного создания и управления правами пользователей, доступ к содержимому всегда ведется с учетом прав текущего пользователя. Это добавляет в SQL-запрос дополнительное условие. На сайтах, где доступ не разграничен на зарегистрированных и гостей, параметр лучше активировать.

    Считать число прочтений содержимого

    При прочтении каждого содержимого увеличивается значение поля счетчика в таблице содержимого. Постоянные изменения даже одного поля таблицы содержимого сводят на нет встроенный в mysql механизм кэширования, да и дополнительный запрос в базу тоже лучше исключить. Настройку рекомендуется выключить, а ведение статистики доверить специализированным сервисам, типа li.ru или Google Analytics.

    Отключить проверки публикации по датам

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

    GZIP-сжатие страниц

    Позволяет передавать пользователю более компактные страницы. Содержимое пакуется через gzip-алгоритм на сервере, а распаковывается автоматически в браузере пользователя. Получается экономия на трафике, но немного больший расход ресурсов сервера; более подробно расчет оптимальности применения gzip приведен во второй главе книги "Разгони свой сайт".

    Блокировка компонентов

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

    Рейтинг/Голосование

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

    Ежедневная оптимизация таблиц базы данных

    Добавляет в работу сайта ежедневную оптимизацию всех таблиц базы данных через выполнение OPTIMIZE TABLE для каждой. Данная процедура уменьшает фрагментацию и производит общую оптимизацию таблиц встроенными средствами mysql.

    Сжатие CSS- и JS-файлов

    Позволяет выдавать вместо обычных JS- и CSS-файлов их упакованные аналоги. Экономит трафик и позволяет указать более длительное время кэширования. Работает только для встроенных файлов.

    Значение тега revisit:

    Позволяет указать параметр тега:

    <meta name="revisit" content="10 days" />

    который указывает, как часто сайт должен посещать поисковый робот. Правильно установленное значение позволит исключить повышенную нагрузку на сервер, вызванную слишком частым посещением поискового робота.

    8.5.2. Встроенное кэширование

    Включить кэширование

    В Joostina встроен механизм, позволяющий кэшировать результаты выполнения ресурсоемких функций или инициализаций объектов. Использование кэширования позволяет уменьшить число запросов в базу, уменьшить число подключаемых файлов и увеличить общую скорость работы системы. Кэширование лучше активировать после полного построения и отладки сайта.

    Тип кэширующей системы

    Кэшировать можно как в файлы, так и в специальные акселераторы кэширования. Joostina поддерживает работу кэширования с использованием apc, eacceLerator, xcache и memcache. Первые 3 — это не только кэш-акселераторы, но и общие оптимизаторы работы php. Наличие любого из них — очень большой плюс в работе сайта.

    Оптимизация кэширования

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

    Автоматическая очистка каталога кэша

    При кэшировании в файлы каталог кэша может содержать множество просроченных объектов. Система следит, чтобы таких случаев не было, но для большей уверенности рекомендуется активировать и этот параметр. На оптимизацию работы сайта число файлов в каталоге кэша имеет прямое влияние: чем больше файлов в каталоге одного уровня — тем дольше файловая система будет их отдавать.

    Кэширование меню панели управления

    При работе в панели управления в верхней части отображается меню, которое содержит пункты для доступа к основным операциям. Меню частично формируется из базы данных. Например, список установленных компонентов или список разделов и категорий. Такие данные изменяются не очень часто, и лучше произвести кэширование этого участка. Активация параметра также сделает вывод меню через внешний JavaScript- файл, код которого исключится из тела страниц и будет кэшироваться еще и на стороне пользователя — в браузере.

    Каталог кэша (/dev/shm)

    По умолчанию файлы кэша складываются в каталог /cache в корне сайта. В зависимости от настроек сервера можно попытаться перенести этот каталог в более быстрое место, например, на диск с другой файловой системой или /dev/shm. Не забудьте убедиться, что PHP-интерпретатор имеет полный доступ к указанному каталогу

    Время жизни кэша

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

    8.5.3. Отключение встроенной статистики

    Включить сбор статистики

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

    Вести статистику просмотра содержимого по дате

    Аналогично предыдущему параметру — лучше отключить и вести все учеты на серверах специальных сервисов.

    Статистика поисковых запросов

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

    8.5.4. Отключение неиспользуемых расширений

    Один из основных принципов оптимизации — использование только необходимого функционала. Joostina по своей сути является универсальной системой, и это влечет за собой некоторую ограниченность и сложность. Базовый дистрибутив системы имеет набор встроенных расширений, часть которых может не использоваться на сайте. Всего в Joostina можно отключить расширения всех 3-х типов:

  • компоненты,
  • модули,
  • мамботы.
  • Отключение компонентов лишь косвенно влияет на оптимизацию сайта. Но блокировка доступа — это отличный способ уменьшить число страниц для индексации или попыток спама. Чем меньше страниц, тем быстрее их проиндексирует поисковый робот и тем меньше времени эти страницы надо будет генерировать серверу для поисковика.

    Отключить неиспользуемые компоненты можно в панели управления на странице управления компонентами: "Меню - Компоненты - Управление компонентами". Для работы данного механизма необходимо, чтобы в глобальной конфигурации была активирована настройка "Блокировка компонентов".

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

    Заходим в меню управления модулями: "Меню - Модули - Модули сайта".

    Выбираем все модули и нажимаем кнопку "Скрыть", находящуюся в верхней части страницы — на тулбаре.

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

    Отключение мамботов позволяет существенно сократить непосредственное время генерации страницы. Мамботы разделены на группы, про это уже сообщалось ранее, и каждая группа отвечает за отдельные участки. Наиболее часто используемые — мамботы группы content, они позволяют обрабатывать содержимое, выдаваемое компонентом. Чаще всего такие мам-боты отвечают за замену в тексте специально оформленных тегов на необходимый функционал или оформление. Например, мамбот bot_mosimage отвечает за замену тега {mosimage} на необходимую картинку. Но для такой работы производится обработка текста регулярными выражениями, что не очень хорошо сказывается на производительности. Отключить неиспользуемые мамботы можно по той же схеме, что и модули, только на другой странице панели управления: Меню — Мамботы — Мамботы сайта.

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

    8.6. Пара советов для Ruby on Rails

    Уже много людей писали руководства, помогающие вашему веб-приложению работать быстрее. В этом разделе будут освещены самые простые, но наиболее эффективные методы, которые дадут вам возможность существенно ускорить ваше приложение без потери какого-либо функционала из Ruby on Rails.

    Часто бывает так, что одно веб-приложение подгружает сразу несколько JavaScript-файлов и CSS-стилей. Это существенно замедляет загрузку страницы, так как веб-браузер каждый раз заново запрашивает новый файл.

    Решение заключается в том, чтобы уменьшить количество внешних ресурсов на вашей странице, объединив их все в один файл. Поможет нам в этом плагин AssetPackager (http://synthesis.sbecker.net/pages/asset packager). Ставим

    script/plugin install git://github.com/sbecker/asset_packager.git

    Пример config/asset_packages.yml:

    javascripts:
    - base:
    - prototype
    - effects
    - controls
    - dragdrop
    - application
    - secondary:
    - foo
    - bar
    stylesheets:
    - base:
    - screen
    - header
    - secondary:
    - foo
    - bar

    И запускаем rake-задачу:

    rake asset:packager:build_all

    Дальше для JavaScript пишем

    <%= javascript_include_merged :base %>

    или

    <%= javascript_include_merged 'prototype', 'effects', 'controls',
    'dragdrop', 'application' % >

    Для стилей пишем:

    <%= stylesheet_link_merged :base %>

    или

    <%= stylesheet_link_merged 'screen', 'header' %>

    В итоге получаем для режима разработки наш старый код, например:

    <script type="text/javascript" src="/javascripts/prototype.js"></script>
    <script type="text/javascript" src="/javascripts/effects.js"></script>
    <script type="text/javascript" src="/javascripts/controls.js"></script>
    <script type="text/javascript" src="/javascripts/dragdrop.js"></script>
    <script type="text/javascript" src="/javascripts/application.js"></script>
    <link href="/stylesheets/screen.css" type="text/css" />
    <link href="/stylesheets/header.css" type="text/css" />

    А в режиме рабочего сайта будет:

    <script type="text/javascript" src="/javascripts/base_packaged.js?123456789"></script>
    <link href="/stylesheets/base_packaged.css?123456789" type="text/css" />

    Теперь, чтобы сделать нагрузку еще меньше, переносим все свои статические файлы на другой хост. В Ruby-on-Rails это очень просто сделать, достаточно добавить в config/environments/production.rb такую строку:

    config.action_controller.asset_host = "http://assets.example.ru"

    Теперь все image_tag, javascript_include_tag и т. д. будут указывать на этот хост.

    8.7. Разгоняем jQuery

    Материал для данного раздела получен при общении с Олегом Смирновым (aka CTAPbIu_MABP) — разработчиком пользовательских интерфейсов на Java и JavaScript. На данный момент он трудится над системой самообслуживания Украинского мобильного оператора "Киев-стар" (http://my.kyivstar.ua/). Олег занимается исследованиями в области производительности JavaScript-библиотек, в частности, jQuery, чему посветил много статей на своем сайте (http://mabp.kiev.ua/).

    jQuery, пожалуй, самая известная JavaScript-библиотека. Она позволяет быстро и просто производить манипуляции с DOM-деревом, навешивать события и делать AJAX-запросы на сервер. Ею пользуются очень много компаний, в том числе GoogLe и Microsoft. Под нее написано огромное количество плагинов, позволяющих расширить стандартный функционал и добавить на страницу виджет любой красоты.

    К сожалению, большая часть всех плагинов имеет код низкого качества, поэтому при установке нескольких таких плагинов страница начинает существенно подтормаживать. О том, как поправить код чужого плагина, чтобы избежать лишнего расхода памяти при сложных манипуляциях с DOM-деревом, как сделать анимацию плавной даже при анимировании нескольких элементов, а AJAX — быстрым, а также о том, как избежать некоторых скрытых багов, и будет этот раздел.

    8.7.1. Selectors

    Стоит начать с функции $, она принимает два параметра: первый — селектор, второй — контекст. Хотя контекст обычно опускают, впоследствии будет показано, как им грамотно пользоваться.

    Простой селект

    Самый простой вариант — это выбор по id, имени тега и имени класса.

    $("#id")
    $("tag")
    $(".class")

    Не случайно они расположены именно в такой последовательности: они идут по сложности алгоритма выборки. В первом случае вызов функции эквивалентен вызову

    document.getElementById("id");

    Поскольку предполагается, что id уникальный, поиск проходит очень быстро, и если на странице есть два элемента с таким id, то найден будет только первый. Хотя в IE и тут сделали ошибку и до 7-й версии включительно в случае отсутствия элемента с таким id он вернет элемент, у которого совпадает атрибут name.

    Во втором случае тоже все относительно просто:

    document.getElementsByTagName("tag");

    Получили все ноды с таким именем из документа, и все готово. И на удивление никаких ошибок, если не учитывать, что при запросе getElementsByTagName("*") IE вернет и комментарии тоже.

    В третьем случае, если есть возможность, работу перехватывает

    document.getElementsByClassName("class");

    (Таблицу поддержки этой функции в браузерах можно посмотреть на quirksmode.org)

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

    var nodes = document.getElementsByTagName("*"), result = [];
    for (var i=0; i<nodes.length ; i=""
      if="" ( " " + (nodes=""[i=""].className=""
    nodes=""[i=""].getAttribute=""
     .indexOf=""("class") >-1)
      result.push(nodes[i]);
            }

    Какой метод применять, определяется в самом начале при подключении библиотеки.

    Селект через querySelectorAll

    приходится обычно использовать намного более сложные конструкции. И для них в современных браузерах FireFox 3.0, Safari 3.2, Opera 9.5, а также в IE8, появились функции querySelector и querySelectorAll. Они, соответственно, предназначены для поиска одной или нескольких нод по CSS3-селекторам. Если браузер клиента поддерживает эту функцию, то все, о чем написано в прошлом пункте, — отпадает, и поиск происходит через querySelectorAll.

    $("#id .class tag")

    В лучшем случае селектор будет обработан именно querySelectorAll, потому что он написан по правилам CSS3. Но такое возможно не со всеми селекторами: jQuery поддерживает ряд селекторов, которые не входят в CSS3, такие, например, как : visible.

    $("#id .class tag:visible")

    Такой селектор выдаст ошибку в функции querySelectorAll, и селектор будет перенаправлен в поисковый движок Sizzle, где строка будет разбита на простые селекторы и превратится, по сути, в несколько разных поисков, в котором каждым следующим контекстом является предыдущий селектор.

    $(document).find("#id").find(".class").find("tag").filter(":visible")

    Скорость этого метода поиска напрямую зависит от величины DOM дерева: чем оно больше, тем медленнее, — но ее можно значительно увеличить, написав селектор раздельно.

    $("#id .class tag").filter(":visible")

    При этом querySelectorAll выберет все ноды, а Sizzle разберется с ":visible".

    По поводу псевдо-селекторов возникает также очень интересный вопрос: CSS3 поддерживает несколько видов псевдо-классов, такие как :nth-of-type/:nth-child/:parent/:not/:checked, jQuery имеет свою реализацию этих селекторов для браузеров, не поддерживающих querySelectorAll, или для браузеров, в которых querySelectorAll не поддерживает данный селектор, но эта реализация иногда отличается.

    Для примера возьмем псевдо-класс : nth-of-type и выберем все четные дивы, а из них все нечетные.

    document.querySelectorAll("div:nth-of-type(even):
    nth-of-type(odd)")
    // Safari/FireFox:0 IE/Opera:N/A
    $("div:nth-of-type(even):nth-of-type(odd)");
    // Safari/FireFox:0 IE/Opera:All
    $("div:even:odd"); // All: вернут 1,5,9 дивы

    Первых два примера работают одинаково и вернут либо 0, если отра- ботала функция querySelectorAll (это касается первого примера), либо все элементы, потому что их обработал Sizzle (это особенность реализа- ции выражения ":"). Третий же вернет 1, 5, 9 и т. д. элементы, а значит, селекторы отрабатывали в три прохода: сначала из всего DOM-дерева бы- ли выбраны все дивы, потом из них были выбраны все нечетные, а потом из оставшихся были выбраны все четные.

    jQuery также имеет набор псевдо-селекторов, которые не входят в CSS3 и обслуживаются только Sizzle’ом : visible/:animated/:input/:header. Их лучше выделять отдельно, поскольку они могут сильно замедлить выборку. Так, например, было с селекторами :visible/:hidden в версии 1.2.6: для то- го чтобы узнать, видимый это элемент или нет, надо было подняться до са- мого верха по DOM-дереву, проверяя атрибуты display и visible каждого ро- дителя (http://mabp.kiev.ua/2009/02/07/accelerates-selectors-in-jquery/).

    $("div").filter(":visible")

    Псевдо-классы, используемые для поиска элементов формы, такие, как :radio, тоже имеют некоторое преимущество, если не применяется querySelectorAll; в противном случае CSS3-селектор input[type=radio] работает быстрее.

    Сложенный селект

    Сложенный селект — это когда нам надо выбрать группу из двух или более разных селекторов, например, все дивы, у которых класс равен A, B и C.

    Это можно сделать двумя способами:

    $(".a,.b,.c")

    выбрать все сразу

    $(".a").add(".b").add(".c")

    или по одному.

    При этом, если задействована функция querySelectorAll, то первый способ быстрее второго в четыре раза, а если нет, то второй в два раза быстрее первого.

    Если уже заговорили про классы, их можно искать как любые другие атрибуты — например, если надо найти все классы, имена которых начинаются на "my", можно сделать так:

    $("[class^=my]")

    а не городить логику с использованием add, тем более что такой способ поддерживается querySelectorAll (http://mabp.kiev.ua/2009/02/21/testingproductivity-jquery-selectors/).

    Неправильный селект в контексте

    На сайте tvidesign.co.uk в одной очень популярной статье "Improve your jQuery — 25 excellent tips" написано, что селект лучше делать в контексте, и приведен вот такой пример:

    $(‘#listItem’ + i, $(‘.myList’))

    Рассмотрим подробнее: контекст — это то, где ищут селектор, значит, пример можно переписать в более наглядную, но менее читаемую форму:

    $($(".myList")).find("#listItem")

    При этом контекст от первого поиска будет являться document.

    $($(".myList",document)).find("#listItem")

    Еще раз перепишем согласно формуле

    $($(document).find(".myList")).find("#listItem")

    И наконец, раскроем скобки

    $(document).find(".myList").find("#listItem")

    Что же получается: мы выполняем дорогостоящую операцию поиска по имени класса (по всему DOM-дереву в худшем случае) для того, чтобы упростить и без того самую простую операцию поиска по id?

    Правильный селект в контексте

    Правильно делать "с точностью до наоборот". В контексте надо ука- зывать id элемента.

    $(".class",$("#id"))

    Однако можно не передавать в контекст jQuery объект, вполне достаточно

    $(".class","#id")

    Это можно переписать как

    $("#id").find(".class")

    Можно еще больше ускорить работу, если искать вот таким способом:

    $(document.getElementById("id")).find(".class")

    Но это, скорее всего, будет уже дурным тоном. Хотя поэкспериментировать интересно: что, если вместо getElementById взять querySelectorAll?

    $("div",document.querySelectorAll("#id"))

    Это примерно то же самое, что и

    $("div",[document.getElementById("id")])

    Ни прироста производительности, ни красоты кода из этого не получить, поэтому советую в контекст передавать что-то простое вроде id или при использовании псевдо-селекторов, обрабатываемых Sizzle’ом, пере давать их в селектор а все остальное в контекст:

    $(":visible","input[type=checkbox]")

    Раз уже пошла речь о псевдо-селекторах, то

    $(":checkbox")

    быстрее чем

    $("input[type=checkbox]")

    без использования querySelectorAll и наоборот.

    Cложный селект

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

    $("#id > div")

    Но если выборка уже есть, то будем использовать ее как контекст. Как мы уже выяснили, поиск в контексте происходит при помощи функции find:

    $("#id").find("> div")

    Но find — очень дорогая функция, она просматривает абсолютно всех потомков контекста, поэтому лучше применить функцию children, она просматривает только непосредственных потомков.

    $("#id").children("div")

    Есть еще ряд функций поиска и манипуляций, которых стоит избегать без крайней необходимости, — это find, closest, wrap, wrapInner, replaceWith, clone. Стоит заметить, что wrapAll сюда не входит (http://mabp.kiev.ua/2009/03/29/jquery-profiling/).

    8.7.2. Кэш

    Внутреннее кэширование

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

    Ситуация следующая: вы работаете со списком, у вас есть один из элементов li. Для того, чтобы получить все элементы, включая текущий, надо выбрать всех братьев (все, у кого родитель — это родитель текущего) этого элемента и добавить его самого:

    $("#id").siblings().add("#id")

    Так как он — прошлый элемент, с которым работали в цепочке вызовов, мы можем взять его из КЭШа:

    $("#id").siblings().andSelf()

    Конечно, в данном конкретном случаи быстрее было бы сделать

    $("#id").parent().children()

    Потому что siblings — это и есть выбор всех детей родителя. Но принцип использования этот пример иллюстрирует нормально.

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

    var elt = $("#id");
    elt.children().css({/**/})
    elt.click();

    Можно после работы с детьми вернуться обратно к родителю и работать с ним дальше:

    $("#id").children().css({/**/}).end().click()

    Кэширование селекторов

    Поскольку кэш так слабо развит, селекторы нужно кэшировать вручную. Давайте рассмотрим, например, вот такой код:

    for(var i=0;i<1000;i
    $("ul").append=""("<li>"+i""/

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

    var elts = $("ul");
    for(var i=0;i<1000;i
      elts.append=""("<li>"+i+"</li>")

    Буферизация

    Но этот код можно заставить работать еще быстрее! Каждый раз, делая append, мы заставляем обновиться DOM-дерево и заставляем браузер перерисовать страницу. Этого можно избежать, придерживая вставку в DOM-дерево.

    var str = "";
    for(var i=0;i<1000;i
      str += ""<li>"+i+"</li>"
    $("ul").html(str);

    Дело в том, что функции для работы с DOM-деревом у jQuery самые "тяжелые" (http://mabp.kiev.ua/2009/03/29/jquery-profiling/). Это объясняется просто. Все html-ноды, на которые повешены события через jQuery, имеют в себе атрибут с объектом jQuery. При удалении этих нод нужно следить, чтобы не было утечек памяти, и удалять эти атрибуты перед удалением ноды. В результате функции html и text вызывают функции полной очистки и только потом вставки нового содержимого:

    jQuery(DOMElement).empty().append(text)

    Функция empty выбирает все ноды и по очереди удаляет:

    jQuery(DOMElement).children().remove()

    А функция remove уже заботится, чтобы из элементов были удалены все дополнительные данные и события.

    Джон Ресиг утверждал, что знает способ быстро удалить все это и что улучшит эти методы, но что-то воз и ныне там. Поэтому будем ждать улуч- шенных функций уже в jQuery 1.4.

    Создание "на лету"

    Прошлый пример, на самом деле, был нужен для того, чтобы подобраться поближе к интересному факту. Часто приходится создавать какие-то вспомогательные дивы, и, естественно, нас интересует самый эргономичный способ это сделать. Казалось бы, в чем проблема: кинул кусок html-кода, и jQuery сама все сделала. Возьмем самый простой и банальный пример: надо создать пустой див:

    $("<div></div>")

    или

    $("<div/>")

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

    $("<div>text</div>")

    И не создавать, а потом добавлять текст:

    $("<div/>").text("text")

    Но это не каcается создания атрибутов, для них используются намно- го более "легкие" функции attr/css/addClass (http://mabp.kiev.ua/2009/03/29/jquery-profiling/), вот тут-то и имеет смысл вместо

    $("<div style='background:red;'/>")

    писать

    $("<div/>").css({background:'red'});

    — это даст небольшой, но выигрыш.

    8.7.3. События

    Множественные события

    $(window).bind("resize load",null,function(){
    $("#id").css({width:document.clientWidth})
    });

    Только при этом не забываем, что поведение IE8 не соответствует стандартам, и при загрузке страницы сначала происходит событие resize, а только потом load.

    То же самое корректно и в обратную сторону:

    $(window).unbind("resize load");

    Но это не работает в версии 1.2.6, точнее, работает только с именованными функциями, а с анонимными не годится, их надо удалять по одной.

    Одно событие на много элементов

    Если случается повесить события на длинный список:

    var ul = $("<ul/>");
    for(var i=0,j=1000;i<j;i++)
      $("<li>"+i+"</li>").click(function(e){
        alert(this.innerHTML);
      }).appendTo(ul);
    ul.appendTo("body");

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

    var str = "";
    for(var i=0,j=1000;i<j;i
      str += ""<li>"+i+"</li>";
    $("<ul/>")
      .append(str)
      .click(function(e){
        alert(e.target.innerHTML);
      })
      .appendTo("body");

    8.8. Клиентская оптимизация для произвольного сайта

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

    В общем, все вышеописанное было справедливо для сайта PERSPEK-TIVA IMPEREAL (http://www.vacLavak.ru/). Перед началом оптимизации главная страница "весила" около 500 Кб, загружала большое количество внешних скриптов (порядка 150 Кб) и ее невозможно было нормально загрузить по модему. Ниже рассказывается, какие методы были использованы для улучшения скорости загрузки, — может быть, общий ход проведения оптимизации поможет и вам точнее проанализировать ситуацию и применить требуемые действия.

    8.8.1. Этап первый: анализ ситуации

    Для анализа скорости загрузки в качестве стандарта стоит использо- вать как webo.in, так и Firebug NET Panel (http://www.getfirebug.com/). Почему два инструмента? С помощью Firebug можно достаточно точно отследить все запросы на странице из реального браузера. Однако Firebug временами не выдает всех запросов к файлам стилей и скриптов. Так же тяжело бывает с кэшированием. Для полного аналитического разбора можно адекватно использовать только анализатор скорости загрузки. После проведения проверки хорошо видно, какие файлы кэшируются (выставлено время кэша), у каких есть ETag или Last-Modified, а также, что более существенно, сразу виден потенциальный выигрыш при минимизации файлов.

    С помощью визуальной оптимизации (http://webo.in/my/action/load/) удобно посмотреть, как изменится диаграмма загрузки сайта, если применить все оптимизационные меры. И поскольку расчет производится аналитически, он обеспечивает достаточно большую точность (не нужно замерять по два-три раза, чтобы избежать случайных сетевых задержек).

    Разобрав основные проблемные места сайта с помощью указанных инструментов, намечаем путь действий — и вперед. Дополнительно мож- но оценить время ответа с сервера (http://webo.in/my/action/timings/) для динамических файлов и понять, нужно ли что-то придумывать для оптимизации серверной части.

    8.8.2. Этап второй: базовые действия

    Базовые действия по оптимизации чрезвычайно просты: нам нужно объединить все текстовые файлы и применить для них gzip-сжатие. А также включить кэширование на достаточно длительный срок (это позволит значительно ускорить по крайней мере открытие последующих страниц на этом сайте). Все это делается несколькими строками в конфигурационном файле Apache ( httpd.conf или .htaccess ):

    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE image/x-icon
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE application/x-javascript
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch Konqueror no-gzip
    BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
    Header append Vary User-Agent
    <FilesMatch .*\.(css|js|php|phtml|shtml|html|xml="")$>
      Header append Cache-Control private
    </FilesMatch>
    ExpiresActive On
    ExpiresDefault "access plus 1 month"
    <FilesMatch .*\.(shtml|html|phtml|php="")$>
      ExpiresActive Off
    </FilesMatch>

    Данный фрагмент кода включает сжатие для тех типов файлов, для которых это разумно сделать, затем отключает сжатие для старых и неподдерживаемых браузеров. Заголовки Vary User-Agent и Cache-Control private нужны для корректной обработки сжатия на этапе локальных прокси-серверов (чтобы они пропускали название браузера, не кэшировали сжатую версию и не отдавали ее тем пользовательским агентам, которые это не поддерживают). Далее на все файлы накидывается кэширование на один месяц, а затем для динамических файлов (HTML) оно отключается.

    Объединение и минимизация файлов может быть достаточно проблематичным занятием, но для этой цели можно использовать пакетную оптимизацию (http://webo.in/my/action/packet/), загрузить один архив со всеми файлами, которые требовалось оптимизировать, — и на выходе получить уже готовые к применению версии.

    8.8.3. Этап третий: шаманим с изображениями

    Следующим шагом по соотношению "эффективность/сложность" будет работа с изображениями. Начать лучше всего с создания CSS Sprites. Стоит руководствоваться уже описанными в лекции 4 принципами и соответствующей частью из книги "Разгони свой сайт". Естественно, что при создании CSS Sprites нужно будет видоизменять стили для страницы, но обычно это достаточно несложно при должной сноровке (либо можно использовать автоматическое решение — http://sprites.in/). В любом случае рекомендуется всегда делать резервные копии изменяемых файлов — это позволит быстро понять, где допущена ошибка, и не менее быстро ее исправить.

    Попутно все GIF-изображения переводятся в PNG-формат. Для этого архив с изображениями прогоняется через всю пакетную оптимизацию. Отдельным пунктом для сайта www.vacLavak.ru стоит упомянуть работу с ани-мированными GIF. В исходной точке все банеры занимали порядка 180 Кб. При оптимизации повезло (удалось удалить ненужные кадры и немного уменьшить палитру), в итоге размер рекламного блока сократился вдвое.

    В результате проведенных действий размер страницы уменьшился до 350 Кб, что было бы совсем неплохо, учитывая изначально "тяжелый" дизайн сайта.

    Однако этого оказалось недостаточно.

    8.8.4. Этап четвертый: счетчик времени загрузки

    Для более точного анализа реальной картины можно воспользоваться счетчиком времени загрузки (более подробно он описан в практическом приложении в книге "Разгони свой сайт"), который замеряет полное время загрузки у всех посетителей сайта для произвольной страницы. Установка его чрезвычайно проста: нужно получить код, установить его максимально близко к началу страницы в шаблоне (идеально — сразу после title) и пару дней собирать статистику (зависит от числа хитов; если на сайт заходит несколько десятков тысяч ежедневно, то хватит и дня или пары часов).

    (рис 8.6) Скорость загрузки страниц на четвертом этапе клиентской оптимизации для www.vaclavak.ru, источник: webo.in

    После установки счетчика обнаружилась следующая картина: среднее время загрузки (уже оптимизированного) сайта составило 16 секунд, за 4 секунды страницы загружались у 58% пользователей.

    8.8.5. Этап пятый: "ненавязчивая" реклама

    Было решено существенно переработать логику загрузки страницы: вынести рекламные блоки в пост-загрузку, а загрузку самого JavaScript "отложить". Все это было сделано в лучших традициях "ненавязчивого" JavaScript, которые описаны в седьмой главе книги "Разгони свой сайт". GoogLe AnaLytics был вынесен на событие OnDOMReady (для точности измерения посещений), остальные — на window.onload.

    В результате на странице сразу загружалось только основное содержание. Сразу после него — Google Analytics и дополнительные библиотеки для галереи изображений (но только на тех страницах, где это было нужно: в частности, это не требовалось для главной страницы). Затем шла загрузка рекламных банеров, информера с курсом валют и погоды. В самом конце на страницу (в самый низ) вставлялся блок с остальными счетчиками. Загрузка почти всего (99% кода) JavaScript была переделана на "ненавязчивый" манер.

    (рис 8.7) Скорость загрузки страниц на пятом этапе клиентской оптимизации для www.vaclavak.ru, источник: webo.in

    Единственная сложность возникла с блоком валют — он вызывался через внешний JavaScript-файл, который в свою очередь содержал docu-ment.write. Его пришлось вставлять через динамический iframe, который получал данные, а потом отправлял родительской странице. Поскольку информер этот загружается достаточно медленно, получился значительный выигрыш во времени загрузки страницы после его вынесения в постзагрузку.

    После очередных замеров с помощью счетчика времени загрузки получилась следующая картина: среднее время загрузки — 7,3 секунды (ускорение более 100% от уже оптимизированного варианта), за 4 секунды сайт загрузился у 82% посетителей.

    8.8.6. Заключение

    Благодаря существованию большого количества удобных инструментов для клиентской оптимизации (на http://webo.in/) удалось очень оперативно проработать клиентскую составляющую обычного в общем-то сайта. Только для широкополосного подключения скорость загрузки увеличилась в 7 раз (для коммутируемого доступа это число еще более значительно в связи с существенным уменьшением числа запросов, необходимых для первичного отображения страницы).

    (рис 8.8) История применения клиентской оптимизации для www.vaclavak.ru

    Сам процесс оптимизации относительно оценки самого webo.in мож- но представить следующим образом (в этом уже может помочь история проверок сайта, http://webo.in/my/action/history/):

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

    Страницы:

    8.1. Разгоняем ASP .NET: 100 баллов и оценка "А" в YSlow

    Этот раздел написан под впечатлением статьи Viktar Karpach YSlow and ASP.NET: 100 points "A" grade is possible (http://www.karpach.com/ yslow-and-asp-net-100-points-a-grade.htm). Здесь мы разберем по шагам, как максимально ускорить работу вашего сайта на ASP .NET. Далее приводятся пункты рекомендаций советов от Yahoo!, с которыми могут возникнуть сложности.

    (рис 8.1) Измерения скорости загрузки сайта при помощи YSlow. Источник: www.karpach.com

    8.1.1. Меньше HTTP-запросов

    Во-первых, нужно объединить в один файл таблицы стилей и файлы скриптов. Это стоит сделать только для рабочего (не тестового) выпуска сайта при помощи MS BuiLd:

    <ItemGroup>
    <TextFiles Include="*.css" Exclude="global.css"/>
    </ItemGroup>
    <Exec Command="echo y| type %(TextFiles.Identity) >> global.css"/>

    Таким образом мы можем объединить все CSS- и JS-файлы. Некоторые разработчики зададут резонный вопрос: а что можно сказать по поводу WebResource.axd? В новом AJAX ControL TooLkit есть TooLkitScriptManager, который позволяет объединить большинство ваших файлов WebResource.axd. Также он может применять к ним gzip-сжатие (это будет важно для следующих разделов).

    Затем нам нужно создать CSS Sprites. Более подробно они уже были описаны в четвертой лекции. Идея заключается в том, чтобы группировать такие изображения по оси повторения. Например, все изображения, повторяющиеся по вертикали, сложить в vbackground.png, а все повторяющиеся по горизонтали — в hbackground.png. Затем можно использовать background-position: -OffsetPixeLs 0 для позиционирования внутри вертикального файла и background-position: 0 -OffsetPixeLs — для горизонтального.

    8.1.2. Используем CDN (Content Delivery Networks)

    Сети доставки содержания (англ. CDN, Content Delivery Network) стоят довольно дорого. Но можно поступить проще и использовать для этого GoogLe (подробнее рассказывается в шестой лекции). Естественно, придется немного попотеть, прежде чем собрать из GoogLe App Engine свою идеальную CDN.

    Можно также применить msbuild для изменения файлов стилей при использовании CDN. Ниже приведен рабочий пример (в нем пути с ../images/ заменяются на http://karpach.appspot.com/cdn/images/):

    <Import Project=".\References\MSBuild.Community.Tasks.targets" />
    <Target Name="Release">
    
      <FileUpdate Files="$(OutputPath)styles\basic.css" Regex="\.\.\/images/(["\)]*)" ReplacementText= 
      "http:=""//karpach.appspot.com/cdn/images/$1" />
    </Target>

    Теперь нужно положить в CDN все ваши изображения, файлы стилей и скриптов. Здесь как раз и всплывает проблема сброса кэша. Что будет, если пользователь зашел к вам на сайт как раз перед тем, как вы выложили новую версию, в которой изменился файл стилей и некоторые изобра- жения? По-видимому, этот пользователь будет видеть сайт со старыми стилями и картинками еще 7 дней, и он просто не обнаружит изменений, пока не истечет срок действия кэша в его браузере. Это не есть хорошо. Если озвученный вопрос становится актуальным, то стоит посмотреть, что Yahoo! сделали на своем веб-сайте. В название картинки они "зашивают" метку даты и версию. Например, trough_2.0_062308.gif. Это выглядит разумно. Если вы изменяете какой-либо из ваших CSS Sprites, то его нужно вручную переименовать при помощи новой даты и версии (на самом деле процесс переименования файлов должен быть заложен в сам инструмент создания CSS Sprites; подробнее о сбросе кэша и объединении файлов было рассказано в лекции 4).

    Однако, по всей видимости, этот подход может быть неприменим для файлов стилей. Для начала стоит обеспечить контроль изменений версий таблиц стилей (в данном случае это SVN). Для картинок это не играет существенной роли, потому что они меняются не так часто. Основная идея заключается в том, что у нас может быть один физический файл (например, basic.css), а на странице используется адрес другого файла (например, basic_14102262.css), где 14102262 — это текущая версия приложения. Затем мы можем применить технологию url rewrite, так что basic_14102262.css будет указывать на basic.css. Пользователь с закэшированной версией basic_14102261.css будет вынужден загрузить новую версию basic_14102262.css, поскольку изменилось имя файла.

    Движок Google Apps позволяет легко реализовывать rewrite для URL. Для этого можно просто изменить ваш app.yaml следующим образом:

    - url: /cdn/styles/basic_\d*\.css
    static_files: cdn/styles/basic.css
    upload: cdn/styles/basic\.css

    Более подробно о настройке CDN от Google рассказывалось в предыдущей лекции.

    8.1.3. Добавляем заголовок Expires

    У всех файлов, расположенных в CDN, уже выставлен этот заголовок. Для всех остальных можно создать следующий HttpModule:

    private readonly static string[] CACHED_FILE_TYPES = new
    string[] { ".jpg", ".gif", ".png",".css" };
    public void Init(HttpApplication context)
    {
      context.AcquireRequestState += new
    EventHandler(context_AcquireRequestState);
    }
    void context_AcquireRequestState(object sender, EventArgs e)
    {
        HttpContext context = HttpContext.Current;
        if (context != null  context.Response != null)
        {
        string fileExtension = Path.GetExtension
        (context.Request.PhysicalPath).ToLower();
        if (context.Response.Cache != null 
        Array.BinarySearch<string>(CACHED_FILE_TYPES, fileExtension)
        >= 0)
        {
          HttpCachePolicy cache = context.Response.Cache;
          TimeSpan duration = TimeSpan.FromDays(365);
          cache.SetCacheability(HttpCacheability.Public);
          cache.SetExpires(DateTime.Now.Add(duration));
          cache.SetValidUntilExpires(true);
          cache.SetNoServerCaching();
          cache.SetMaxAge(duration);
        }
     }
    }

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

    8.1.4. Располагаем CSS-файлы в начале страницы

    Это делается очень просто. Просто надо ввести эти действия в привычку. Помните об этом при разработке модулей на стороне сервера и используйте коллекцию Header.Controls для добавления таблиц стилей на страницу.

    8.1.5. Располагаем JavaScript-файлы в конце страницы

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

    Library.js

    может содержать

    function DoSomething()
    {
    }
    А после этого прямо в коде страницы:
    <script type='text/javascript'>
      if (typeof (DoSomething) == 'undefined')
      {
      alert('Library is not loaded yet');
      }
    </script>

    Приведенный пример будет недостаточно верным, потому что загружаемая функция может быть доступна на странице после некоторого времени. Лучше всего создавать дополнительную клиентскую логику, использующую определенный модуль, в самом этом модуле либо проверять через равные промежутки времени, доступен ли необходимый функционал. Более подробно данные техники были освещены в лекции "Ненавязчивый JavaScript" книги "Разгони свой сайт".

    8.1.6. Уменьшаем число DNS-запросов

    Для этого стоит скопировать внешние ресурсы (картинки, JavaScript файлы) к себе на сайт.

    Например, на блоге располагается иконка валидной страницы от W3.Изначально эта иконка находилась на сайте W3 Schools, но для ускорения загрузки сайта стоит скопировать ее в свой проект. Тогда она будет располагаться локально, а при загрузке сайта понадобится на 1 DNS-запрос меньше.

    8.1.7. Уменьшаем JavaScript

    Минимизацию JS-кода можно осуществлять при публикации очередного выпуска сайта при помощи MS Build (это наиболее разумная позиция — осуществлять все оптимизационные процедуры при превращении сайта из тестового в рабочий). Для этой цели можно использовать YUI compressor. Ниже приведен вариант скрипта для MS Build.

    <Target Name="Compress">
      <Message Text="Create temp files ..." />
      <Copy SourceFiles=".\$(ProjectName)\Javascript\ColorPicker.js"
      DestinationFiles=".\$(ProjectName)\Javascript\ColorPicker.js.full"/>
      <Copy SourceFiles=".\$(ProjectName)\Styles\ColorPicker.css"
      DestinationFiles=".\$(ProjectName)\Styles\ColorPicker.css.full"/>
      <Exec Command="java -jar yuicompressor-2.4.2.jar —type js
      .\$(ProjectName)\Javascript\ColorPicker.js.full
      >.\$(ProjectName)\Javascript\ColorPicker.js"/>
      <Exec Command="java -jar yuicompressor-2.4.2.jar —type css
      .\$(ProjectName)\Styles\ColorPicker.css.full
    >.\$(ProjectName)\Styles\ColorPicker.css"/>
    </Target>

    8.1.8. Удаляем дублирующиеся скрипты

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

    8.1.9. Настройте ETag

    Кэширующий модуль и CDN должны решить эту проблему. Но стоит также иметь в виду, что в ASP .NET есть специальный метод для ETag:

    Response.Cache.SetETag

    8.2. Разгоняем Drupal

    Данный раздел подготовлен при помощи Елены Цаплиной (aka Касихиной) — программиста-разработчика и руководителя нескольких Интернет-проектов: студии дизайна и разработки Интернет-сайтов Aquanther (http://www.aquanther.ru/)), ежедневного женского журнала "Мои подружки" (http://www.moipodruzhki.ru/), программного обеспечения (под управлением ОС Windows) под единым названием — HomAff (сокращение от home affairs).

    Елена известна своей публикацией о CMS DrupaL DrupaL ( http://drupaL.ru/node/26290 ), получившей широкое распространение в Рунете.

    В данный момент под ее руководством (на базе студии Aquanther) разрабатывается мультимедийный курс по созданию и управлению сайтом с использованием CMS DrupaL. В дальнейшем вместе с мультимедийным курсом планируется распространение модифицированных версий CMS DrupaL, специализированных для определенных типов сайтов, и оказание платной поддержки данной CMS.

    8.2.1. Вступление

    DrupaL — довольная распространенная CMS, и это наложило на нее свой отпечаток: базовая поставка DrupaL является не готовым решением для определенного вида сайта, а фундаментом для его создания. Существуют "сборки" на базе DrupaL, специализированные под определенные виды сайтов, например, под новостные сайты. Но подобные сборки в данный момент мало распространены и плохо поддерживаются. В связи с этим при создании Интернет-сайта на основе стандартной поставки DrupaL используется большое количество готовых дополнительных модулей и тем оформления для DrupaL либо разрабатываются новые модули и темы специально для данного проекта. Последним этапом работ по созданию сайта является его оптимизация, которую условно можно разбить на 4 шага:

  • встроенная оптимизация Drupal;
  • оптимизация Drupal с помощью модулей;
  • оптимизация конфигурации и обслуживания Drupal;
  • оптимизация сервера.
  • 8.2.2. Встроенная оптимизация Drupal

  • Отключим все неиспользуемые модули, так как при генерации страницы перед отправкой ее браузеру пользователя код определенных модулей может выполняться, даже если функционал данного модуля не используется на сайте. На выполнение кода будет тратиться процессорное время сервера, что приведет к более долгой генерации страницы. Пример такого модуля — Statistics. Вместо статистики, выдаваемой данным модулем, можно взять статистику сервиса — GoogLe AnaLytics.

    При создании сайта используем DrupaL версии 6, так как в нем лучше реализованы внутренние средства кэширования. Также в дополнительных модулях (Views, PaneL и т. д.) для DrupaL версии 6 внедрены эффективные методы кэширования. К сожалению не все модули DrupaL версии 5 реализованы для DrupaL версии 6 (например, модуль Sphinx), о чем не следует забывать при планировании разработки Интернет-сайта. Далее будем рассматривать только DrupaL версии 6.

    Хорошо обдумаем варианты использования модулей наподобие CCK (Content Construction Kit) перед реализацией запланированного. Например, простая задача на хранение в базе сайта тысячи типов продуктов, их названий и описаний, решается с помощью: I создания тысячи терминов таксономии и привязки их к определенному типу материала; I добавления определенному материалу дополнительного поля CCK, в котором будет храниться тип продуктов. При усложнении задачи с помощью условия, что все типы продуктов должны быть разбиты на 10 групп, задача решается двумя вариантами. Вариант первый, с использованием таксономии.

  • Таксономия позволяет создавать иерархию терминов в словаре, т.е. в словаре с терминами может быть 10 терминов первого уровня, а остальные термины будут потомками одного из этих 10 терминов. Создадим нужную иерархию терминов в словаре.
  • Установим дополнительный модуль — HierarchicaL SeLect (http://drupal.org/project/hierarchical_select ), который позволяет в зависимости от вложенности уровней словаря таксономии отображать определенное количество выпадающих списков. Иначе говоря, пользователю при добавлении новой статьи о продукте на сайт будет выведен один выпадающий список, в котором можно выбрать один из 10 терминов первого уровня (группы типов продуктов). После осуществления выбора будет отображен еще один выпадающий список, в котором будут приведены потомки данного термина в иерархическом дереве терминов данного словаря таксономии (типы продуктов).

    Вариант второй, с использованием CCK.

  • Необходимо определенному материалу добавить 2 дополнительных поля CCK: одно — для хранения групп продуктов, второе — для хранения типов продуктов.
  • Нужно настроить взаимодействие данных полей в зависимости от выбора значений в них.
  • Главные ошибки при решении подобных задач, создающие дополнительную нагрузку на базу данных:
  • создание у определенного материала 10 полей CCK, по полю на каждую группу;
  • при создании у определенного материала поля CCK не указана его длина (поэтому по умолчанию считается, что она максимально возможная).
  • Используем встроенное кэширование Drupal. Оно позволяет кэшировать информацию, извлеченную из базы данных, а также информацию, полученную при обработке извлеченной информации из PHP.
  • Кэширование системы меню, фильтров форматов ввода, переменных администрирования (например, название сайта) и настроек модуля производится автоматически. Остальные параметры кэширования можно настроить на странице "Управление — Производительность" ( http://www.exampLe.ru/admin/settings/performance )

    На данной странице можно настроить:

  • компрессию страниц;
  • кэширование блоков;
  • оптимизацию CSS-файлов;
  • оптимизацию JavaScript-файлов.
  • Включим кэширование страниц в режим "нормальный". В данном режиме кэширования страниц при просмотре страницы в первый раз (анонимным, незарегистрированным в системе пользователем) производится сохранение сгенерированной страницы в кэш. В дальнейшем при просмотре данной страницы (анонимным пользователем) она не генерируется заново, а берется из кэша, что значительно ускоряет работу Drupal.

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

    (рис 8.2) Настройки производительности для Drupal

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

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

    Включим кэширование блоков. Принцип работы кэширования блоков аналогичен принципу кэширования страниц. Для супер-пользователя (первого зарегистрированного пользователя при установке DrupaL, его id равен 1) блоки никогда не кэшируются.

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

    8.2.3. Оптимизация Drupal с помощью модулей

  • Установим модуль Authenticated User Page Caching (Authcache), скачать его можно по адресу http://drupaL.org/project/authcache . Данный модуль позволяет кэшировать страницы, как для анонимных пользователей, так и для аутентифицированных ("залогинившихся") пользователей, более качественно, чем встроенное кэширование DrupaL. При установке данного модуля необходимо перенастроить динамический контент на страницах (например, вывод имени аутентифицированного пользователя).

    Authcache сохраняет сжатый кэш страниц отдельно для каждого пользователя или роли. Кэш сохраняется в базе данных или в стороннем средстве кэширования (memcahed, APC, и т. д.). Кэшированные версии страниц для аутентифицированных пользователей (кроме супер-пользователя) передаются с помощью AJAX, поэтому достигается очень быстрое отображение страницы в браузере. Если у аутентифицированного пользователя в браузере отключены JavaScript, то он получает страницы не из кэша. На некоторых серверах скорость загрузки страницы уменьшается до 1 миллисекунды.

    Для установки модуля:

  • скачаем его;
  • распакуем модуль в папку /sites/all/modules ;
  • скопируем файл ajaxauthcache.php из папки модуля в корневую директорию сайта (там же находится файл index.php )
  • откроем файл settings.php (в папке /sites/default ) и добавим следующий код (без примечаний за // ...) в начало файла после тега <?php:
    $conf['cache_inc'] =
    './sites/all/modules/authcache/api/authcache.inc';
    $conf['authcache'] = array(
      'default' => array(
      // технология кэширования - apc, memcache, db, file,
      // eacc or xcache
      'engine' => 'db',
      // если используем memcached (host:port, например,
      // 'localhost:11211')
      'server' => array(),
      // если используем процесс memcached, shared или single
      'shared' => TRUE,
      // кэш ключа префикса (для нескольких сайтов)
      'prefix' => '',
      // если используем кэширование на файлах — указываем
       // путь их сохранения
      'path' => 'files/filecache',
      // статический массив кэша (расширенный)
      'static' => FALSE,
       ),
    );
  • В данном коде устанавливаются настройки модуля Authcache. Указываем, что будем хранить кэш страниц в базе данных ('engine' => 'db'), поэтому все остальные установки не имеют значения, и мы оставляем их без изменений. Более подробно о параметрах данного кода можно прочитать на странице http://drupaL.org/project/cacherouter (на английском языке).

    Включим модуль Authcache на странице "Управление — Модули" ( http://www.exampLe.ru/admin/buiLd/moduLes ). После чего настроим его работу на странице "Управление — Производительность — Authcache" ( http://www.exampLe.ru/admin/settings/performance/authcache ):

  • укажем роли, для которых необходимо кэшировать контент;
  • аннулируем все пользовательские сессии (переключатель — Invalidate all user sessions ) при первом запуске;
  • выставим время хранения кэша (в часах);
  • нажмем кнопку "сохранить и очистить кэш" ( Save clear cached pages ) для сохранения изменений.
  • (рис 8.3) Настройка модуля Authcache

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

    В шаблонах тем оформления используем переменные:

  • $user_name - для отображения имени аутентифицированного пользователя;
  • $user_link - для отображения ссылок, связанных с профилем пользователя;
  • $is_page_authcache — если установлен в TRUE, то все хуки данного шаблона темы оформления будут сохранены в кэш.
  • Можно также ознакомиться с примером /sites/all/modules/authcache/modules/authcache_example, который показывает, как настроить блоки с пользовательским содержанием (с контентом пользователя).

  • Если необходимо кэшировать страницы только для анонимных пользователей (без аутентифицированных), можно установить модуль Cache Router. Данный модуль лежит в основе модуля Authcache и кэширует страницы лучше встроенного кэширования Drupal. Скачаем модуль по адресу http://drupal.org/project/authcache . После скачивания распакуем модуль в папку /sites/all/modules. Включим модуль Cache Router на странице "Управление — Модули" (http://www.example.ru/admin/build/modules ). Откроем файл settings.php (в папке /sites/default ) и добавим следующий код в начало файла перед тегом <?php:
    $conf['cache_inc'] = './sites/all/modules/cacherouter/cacherouter.inc';
    $conf['cacherouter'] = array(
      'default' => array(
      'engine' => 'db',
      'server' => array(),
      'shared' => TRUE,
      'prefix' => '',
      'path' => 'sites/default/files/filecache',
      'static' => FALSE,
      'fast_cache' => TRUE,
      ),
     );

    После осуществления действий, приведенных выше, страницы создаваемого сайта будут отдаваться сервером браузеру пользователя в сжатом виде, а вот CSS и JavaScript — нет. Исправим это:

  • cкачаем модуль CSS Gzip со страницы http://drupal.org/project/css_gzip ;
  • cкачаем модуль JavaScript Aggregator со страницы http://drupal.org/project/javascript_aggregator ;
  • распакуем модули в папку /sites/all/modules ;
  • включим модули на странице ldquo Управление - Модули rdquo ( http://www.example.ru/admin/build/modules );
  • активируем сжатие CSS и JavaScript на странице "Управление - Производительность" ( http://www.example.ru/admin/settings/performance ), отметив чекбоксы "GZip CSS" и "GZip JavaScript" ;
  • внесем изменение в файл .htaccess, расположенный в корневой директории сайта (на основании данных из README.txt, входящего в состав модуля CSS Gzip), пропишем меду тегами <IfModule mod_rewrite.c> и </IfModule> следующий код:
    ### START CSS GZIP ###
    # Requires mod_mime to be enabled.
    <IfModule mod_mime.c="">
      # Send any files ending in .gz with x-gzip encoding
      # in the header.
      AddEncoding x-gzip .gz
    </IfModule>
    # Gzip compressed css files are of the type 'text/css'.
    <FilesMatch "\.css\.gz$">
      ForceType text/css
    </FilesMatch>
    <IfModule mod_rewrite.c="">
      RewriteEngine on
      # Serve gzip compressed css files
      RewriteCond %{HTTP:Accept-encoding} gzip
      RewriteCond %{REQUEST_FILENAME}\.gz -s
      RewriteRule ^(.*)\.css $1\.css\.gz [L,QSA,T=text/css]
    </IfModule>
    ### End CSS GZIP ###
  • сохраним настройки и очистим кэш.

    Если в шаблонах темы оформления необходимо использовать дополнительные CSS и JavaScript, то желательно подключать их с помощью следующих команд:

  • drupal_add_css(‘путь к CSS относительно корневой директории сайта’);
  • drupal_add_js(‘путь к JavaScript относительно корневой директории сайта’);
  • для того, чтобы они оптимизировались (включались в один исходный CSS или JavaScript-файл) и сжимались совместно со всеми остальными CSS- или JavaScript-файлами, используемыми на сайте.

    8.2.4. Оптимизация конфигурации и обслуживания Drupal

    От оптимизации Drupal с помощью модулей, перейдем к более сложной оптимизации — оптимизации конфигурации и обслуживания Drupal.

  • Уменьшим время хранения пользовательских сеансов. Так как DrupaL хранит их в своей базе данных, то сокращение времени их хранения разгрузит базу данных, особенно, если на сайт приходят тысячи пользователей в день. По умолчанию сеансы хранятся 55 часов, уменьшим время их хранения до 24 часов. Для этого на сервере в папке /sites/default в файле settings.php изменим строку
    ini_set('session.gc_maxlifetime', 200000);

    на

    ini_set('session.gc_maxlifetime', 86400); // 24 часа (в секундах)

    Также в этом файле можно сократить время жизни кэшированных страниц сеансов до 24 часов, изменив строку

    ini_set('session.cache_expire', 200000);

    на

    ini_set('session.cache_expire', 1440); // 24 часа (в минутах)

    Напоследок в этом же файле изменим время хранения cookie в браузере пользователя, сократив его до 24 часов:

    ini_set('session.cookie_lifetime', 86400); // 24 часа (в секундах)

    Если установить время хранения cookie в браузере пользователя равным 0, то cookie будет удаляться сразу после закрытия Интернет-браузера пользователем.

  • Сократим количество сообщений протоколирования работы сайта, сохраняемых в базе данных. На странице "Управление - Отчеты и сообщения - Отчеты в базе данных" ( http://www.example.ru/admin/settings/logging/dblog ), выставим необходимый максимум отчетов, хранимых в базе данных. Данные отчеты полезны для просмотра попыток взлома сайта, поэтому минимум, который можно выбрать, — это 100 записей. Просмотреть данные отчеты можно, перейдя на страницу "Управление $$\Rightarrow$$ Недавние записи в системном журнале" (http://www.example.ru/admin/reports/dblog ).
  • Настроим выполнение регулярных процедур (задачи cron), так как при их выполнении очищаются журналы записей сообщений протоколирования работы сайта, устаревшие записи кэша и другие статистические данные. Самым простым способом настройки автоматического запуска регулярных процедур является установка модуля — Poormanscron. Скачаем данный модуль по адресу http://drupal.org/project/poormanscron . Распакуем его в папку /sites/all/modules, активируем модуль на странице "Управление - Модули" (http://www.example.ru/admin/build/modules ). Установим интервал запусков Cron на странице "Управление - Poormanscron" (http://www.example.ru/admin/settings/poormanscron) равным 360 минут (один раз в 6 часов).
  • В составе Drupal имеется модуль Throttle, который производит оценку количества посетителей сайта и отключает некоторые функцио- нальные возможности, если достигнут порог, установленный администратором. После активации модуля на странице "Управление - Модули" (http://www.example.ru/admin/build/modules) можно увидеть, что у не которых модулей на данной странице кроме флажков включения появились флажки, отмечающие, должен ли данный модуль регулироваться Throttle или нет. Также некоторые блоки могут регулироваться Throttle("Управление -Блоки" (http://www.example.ru/admin/build/block). Настройка Throttle производится на странице "Управление - Регулятор" (http://www.example.ru/admin/settings/throttle), где указывается минимальное количество анонимных посетителей и минимальное количество зарегистрированных пользователей для включения ограничения функционала сайта для них. На этой странице установим вероятностный ограничитель авторегулятора на 20%, чтобы для одного из каждых 5 запросов на выдачу страницы для браузера пользователя производился 1 запрос к базе данных для определения нагрузки на сайт."
  • 8.2.5. Оптимизация сервера

    Так как сервер сайта может работать под управлением разных операционных систем:

  • Windows
  • Linux
  • FeeBSD
  • то в каждом случае настройки оптимизации сервера будут отличаться (т. е. установка eAccelerator в Windows и Lunux сильно различается). Ниже приведены только основные рекомендации по оптимизации сервера. Подробно из рекомендаций рассмотрена лишь установка PHP-акселератора на сервер Ubuntu 8.04, так как PHP-акселератор значительно ускоряет работу сайта.

  • Установим eAccelerator. Он является PHP-акселератором, основное назначение которого состоит в кэшировании бинарного представления кода.
  • Соединимся с сервером по SSH и авторизуемся с правами root. Выполним команды для установки дополнительного пакета php5-dev:
    sudo apt-get install php5-dev
    sudo apt-get install make
  • Выполним команды для установки eAccelerator:
    sudo cd /tmp/
    sudo wget http://bart.eaccelerator.net/source/0.9.5.3/eaccelerator-
    0.9.5.3.tar.bz2
    sudo tar xvjf eaccelerator-0.9.5.3.tar.bz2
    sudo cd eaccelerator-0.9.5.3
    sudo phpize
    sudo ./configure —enable-eaccelerator=shared
    sudo make
    sudo make install
  • Отредактируем файл php.ini в папке /etc/php5/apache2, вставим в начале файла после тега [PHP] следующий код:
    ; eAccelerator configuration
    ; Note that eAccelerator may also be installed as a PHP extension
    or as a zend_extension
    ; If you are using a thread safe build of PHP you must use
    ; zend_extension_ts instead of zend_extension
    ;extension = "/usr/lib/php5/20060613+lfs/eaccelerator.so"
    zend_extension = "/usr/lib/php5/20060613+lfs/eaccelerator.so"
    eaccelerator.shm_size = "16"
    eaccelerator.cache_dir = "/var/cache/eaccelerator"
    eaccelerator.enable = "1"
    eaccelerator.optimizer = "1"
    eaccelerator.check_mtime = "1"
    eaccelerator.debug = "0"
    eaccelerator.filter = ""
    eaccelerator.shm_max = "0"
    eaccelerator.shm_ttl = "0"
    eaccelerator.shm_prune_period = "0"
    eaccelerator.shm_only = "0"
    eaccelerator.compress = "1"
    eaccelerator.compress_level = "9"
    eaccelerator.allowed_admin_path = "/var/www/eaccelerator"
  • При использовании Zend Optimizer и/или ionCube Loader приведенный выше код будет выглядеть так:
    ; eAccelerator configuration
    ; Note that eAccelerator may also be installed as a PHP extension
    or as a zend_extension
    ; If you are using a thread safe build of PHP you must use
    ; zend_extension_ts instead of zend_extension
    ;extension = "/usr/lib/php5/20060613+lfs/eaccelerator.so"
    zend_extension = "/usr/lib/php5/20060613+lfs/eaccelerator.so"
    eaccelerator.shm_size = "16"
    eaccelerator.cache_dir = "/var/cache/eaccelerator"
    eaccelerator.enable = "1"
    eaccelerator.optimizer = "1"
    eaccelerator.check_mtime = "1"
    eaccelerator.debug = "0"
    eaccelerator.filter = ""
    eaccelerator.shm_max = "0"
    eaccelerator.shm_ttl = "0"
    eaccelerator.shm_prune_period = "0"
    eaccelerator.shm_only = "0"
    eaccelerator.compress = "1"
    eaccelerator.compress_level = "9"
    eaccelerator.allowed_admin_path = "/var/www/eaccelerator"
    ; ionCube Loader configuration
    zend_extension=/usr/local/lib/ioncube/ioncube_loader_lin_5.2.so
    ; Zend Optimizer configuration
    zend_extension=/usr/local/lib/Zend/ZendOptimizer.so
    zend_optimizer.optimization_level=15
  • Создадим кэш-каталог для eAccelerator, выполнив команды
    sudo mkdir -p /var/cache/eaccelerator
    sudo chmod 0777 /var/cache/eaccelerator
  • Перезапустим Apache:
    sudo /etc/init.d/apache2 restart
  • Рекомендуем установить Web-сервер nginx и настроить его работу с веб-сервером Apache так, чтобы страницы он отдавал браузеру пользователя Apache, а статический контент (CSS, JavaScript, фото и т. д.) —nginx. Либо полностью замените веб-сервер Apache веб-сервером nginx.
  • Установим в Apache модуль mod_expires , который позволяет Drupal посылать HTTP-заголовки Expires , кэшируя все статические файлы (изображения, CSS, JavaScript и т. п.) в Интернет-браузере пользователя на определенный срок или до момента появления новых версий файлов. Настройки взаимодействия Drupal и модуля mod_expires веб-сервера Apache находятся в файле .htaccess в корневой директории сайта:
    # Включить mod_expires.
    <IfModule mod_expires.c="">
      # Разрешить истечение срока.
      ExpiresActive On
      # Кэшировать все файлы на две недели после доступа (A).
      ExpiresDefault A1209600
      # Не кэшировать динамически генерируемые страницы.
      ExpiresByType text/html A1
    </IfModule>
  • Для ускорения обработки .htaccess файлов веб-сервером их содержание можно перенести в главный файл конфигурации Apache — httpd.conf. После чего необходимо запретить поиск файлов .htaccess в пределах корневого каталога веб-сервера, установив AllowOverride в None:
    <Directory/>
    AllowOverride
    …
    </Directory>
  • Ввиду того, что некоторые модули внутри своих каталогов могут соержать файлы .htaccess, следует аккуратно работать с данным видом оптимизации, чтобы при переносе содержимого всех файлов .htaccess в httpd.conf не пропустить не один файл .htaccess.
  • Установим на сервере:
  • систему анализа лог-файлов (например, AWstats);
  • систему мониторинга производительности сервера (например,Munin);
  • систему для учета сетевого трафика (например, Vnstat).
  • Включим кэш MySQL и установим его размер равным 64 мегабайтам. Для этого отредактируем файл my.cnf в папке /etc/mysql (при использовании Ubuntu 8.04). Изменим значение
    # query_cache_limit = 1M
    # query_cache_size = 16M

    на

    query_cache_limit = 1M
    query_cache_size = 64M

    После чего перезапустим MySQL командой

    /etc/init.d/mysql restart

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

  • Проверим: загрузку центрального процессора, нехватку оперативой памяти или места на диске и возможную перегрузку линии связи сервера с Интернетом. Если необходимо разместить базу данных на отдельном сервере, то изменим настройки соединения с базой данных, — они находятся в файле settings.php (в папке /sites/default ) — либо разместим сайт на кластере из серверов (например, воспользовавшись услугами сервиса Amazon C2).
  • 8.2.6. Заключение

    Для просмотра информации о сервере из Drupal существует удобный модуль — System information (http://drupal.org/project/systeminfo ). После его установки и активации информацию о вашем сервере можно посмотреть на странице http://www.example.ru/admin/reports/systeminfo.

    8.3. Разгоняем Wordpress

    Wordpress (http://www.wordpress.org/) является сейчас наиболее популярной платформой для одиночного хостинга блогов. Ряд хостинг-провайдеров уже даже предлагают площадки с предварительно установленным Wordpress, а в большом количестве изданий рассуждают, как лучше заработать на новом блоге или правильно его использовать. Ниже будет освещен ответ на один из основных вопросов, встающих перед администраторами блогов: как сделать так, чтобы сайт быстро работал. Нижеизложенный материал рассчитан на максимально широкую аудиторию пользователей.

    8.3.1. Основные положения

    Ускорение работы любой системы возможно, в основном, за счет кэширования некоторых (тут стоит подчеркнуть, что именно некоторых, а не всех подряд) часто используемых операций. Все кэширующие мероприятия, в том числе и для Wordpress, можно разбить на несколько основных частей:

  • база данных;
  • компиляция серверных скриптов (PHP);
  • статические страницы;
  • клиентская составляющая.
  • Данную проблему можно проиллюстрировать при помощи следующего рисунка:

    8.3.2. База данных

    Так уж сложилось, что основное узкое место практически любой системы заключается в базе данных, поэтому ее стараются ускорить всеми возможными способами. Стоит отметить, что проблема многочисленных вызовов к базе данных не решается просто уменьшением их количества (для предоставления той же самой информации), тут надо подходить более комплексно и настраивать многоуровневый кэш для запросов. Относительно MySQL это сделать довольно просто: достаточно прописать в конфигурационном файле my.cnf (или my.ini ) следующие параметры (в случае большого количества оперативной памяти 20 Мб может быть увеличено до любого приемлемого количества, но не стоит здесь увлекаться: скорость поиска данных в кэше напрямую зависит от размера самого кэша):

    (рис 8.4) Кэширующие звенья для Wordpress, источник: www.arnebrachhoLd.de
    query-cache-type=1
    query-cache-size=20M

    Для оптимизации таблиц (что позволит уменьшить время запросов на 20—50%) можно воспользоваться дополнением Optimize DB (http://yoast.com/wordpress/optimize-db/ ), которое позволит существенно уменьшить размер таблиц MySQL и улучшить их структуру. Для кэширования запросов к базе данных также существует специальное дополнение, DB Cache Reloaded (http://wordpress.org/extend/plugins/db-cachereloaded/ ).

    8.3.3. Компиляция серверных скриптов

    Каждый раз, когда исполняется PHP-скрипт, он заново компилируется в памяти в исполняемый код, что требует значительного времени. Что бы избежать повторной компиляции одних и тех же скриптов, используются такие приложения, как APC ( http://pecl.php.net/package/APC) или eAccelerator (http://eaccelerator.net/), которые сохраняют уже скомпилированный код в памяти и позволяют выполнять его значительно (до нескольких десятков раз) быстрее. Также данные решения хорошо справляются с большим количеством маленьких файлов, которые подключаются при обработке запроса к странице, снижая издержки при обращении к файловой системе. PHP-движок не загружает каждый раз файлы с диска (или из дискового кэша) — он получает сразу исполняемый код, что наного увеличивает скорость выполнения. После оптимизации базы данных (настройки кэширования) это одно из наиболее узких мест (за исключением создания статических страниц вместо динамической их генерации)."

    8.3.4. Статические страницы

    Следующим шагом для борьбы с большим временем подготовки страницы на сервере будет полное кэширование создаваемой страницы в один файл или одну запись в оперативной памяти. Для включения внутреннего кэширования на уровне самого Wordpress достаточно раскомментировать (или добавить) в файл wp-config.php следующие строки (предварительно проверив, что директория wp-content/cache доступна для записи, иначе ничего не получится):

    define('ENABLE_CACHE', true );
    define('CACHE_EXPIRATION_TIME', 900);

    Более серьезных результатов кэширования можно добиться при помощи дополнения WP-Super-Cache (http://ocaoimh.ie/wp-supercache/ , базирующегося на WP-Cache, http://mnm.uib.es/gallir/wpcache-2/) или Hyper Cache (http://www.satollo.com/english/wordpress/hyper-cache), которое вообще не будет осуществлять никаких запросов к базе данных для отображения внешних веб-страниц. Однако при этом станет невозможно учитывать статистику посещений через встроенные в Wordpress методы (только через внешние счетчики или по логам сервера). Для Wordpress, установленного на IIS, также лучше всего будет использовать именно WP-Super-Cache вместо IIS Output Caching. Это подробно рассматривается в соответствующей заметке; ниже приведено число запросов в секунду при том или ином методе серверного кэширования.

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

    (рис 8.5) Рис. 8.5. Производительность кэширования Wordpress для IIS, источник: bLogs.iis.net

    8.3.5. Клиентская часть

    Основной минус богатства тем для Wordpress — то, что они в основном разрабатываются любителями. Как следствие, такие темы могут состоять из большого количества картинок и файлов стилей, которые в совокупности загружаются очень медленно. Однако ситуация поправимая. Для ускорения загрузки сайта в самом браузере (а это, по мнению экспертов Yahoo!, занимает 95% времени общей загрузки страницы) можно воспользоваться несколькими решениями:

  • Включить сжатие страниц в самом Wordpress. Делается это через "Настройки - чтение" ("WordPress должен упаковывать статьи (gzip), если браузер запросит это").
  • CSS Compress (http://dev.wp-plugins.org/wiki/css-compress) — дополнение к Wordpress, которое автоматически минимизирует и сжимает CSS-файлы.
  • PHP Speedy (http://aciddrop.com/php-speedy/) позволяет объединить все CSS- и JS-файлы, настроить клиентское кэширование и сжатие текстовых файлов. Это позволяет значительно ускорить за грузку страниц для конечных пользователей (особенно если это ваши постоянные посетители). Устанавливается и как плагин, и как отдельное приложение."
  • Web Optimizer (http://code.google.com/p/web-optimizator/) устанавливается и как отдельное веб-приложение, и как встроенный плагин. Обладает более широкими возможностями, в частности, автоматическим созданием CSS Sprites (что ускоряет загрузку страниц для пользователей IE) и добавлением всех серверных правил в .htaccess-файл (что обеспечивает более широкую совместимость и снимает нагрузку с PHP-скриптов на анализ кэширования и осуществление сжатия). Также Web Optimizer позволяет настроить "ненавязчивую" загрузку JavaScript и распределить изображения по нескольким статическим хостам.
  • В общем, даже самый обычный блог может быть ускорен в несколько (десятков) раз за считанные минуты. Скорее всего, в ближайшем будущем уже появятся отдельные сборки Wordpress, настроенные на максимальную производительность при задействовании любой темы и произвольной посещаемости блога. Сейчас же можно просто воспользоваться вышеприве- денными советами и порадоваться за значительное увеличение числа посещений и постоянных читателей.

    8.4. Разгоняем Joomla! 1.5

    Скорее всего, многие уже слышали о медленной работе JoomLa! (http://www.joomLa.org/), одной из самых популярных бесплатных CMS, равно как и о ее сильной уязвимости для атак хакеров. Благодаря простоте создания расширений для JoomLa! сейчас доступно несколько (десятков) тысяч разнообразных модулей, компонентов и расширений, позволяющих установить на сайт практически произвольный функционал: от полноценной социальной сети до Интернет-магазина.

    Естественно, что у такой простоты есть и обратная сторона: большинство расширений создаются без учета требований высокой производительности и какой-либо оглядки на мощности конечных серверов, очень часто даже не выделенных или виртуальных, а расположенных на общем хостинге. Разработка бесплатных решений оборачивается 100-500% замедлением в скорости загрузки сайта. Давайте разбираться, как с этим можно бороться.

    8.4.1. Серверная часть

    Для начала посмотрим, что можно сделать с серверной производительностью. Мы исследовали стандартную сборку JoomLa!, но даже в такой комплектации на отдельном сервере время создания страницы занимало 0,312 с (замер времени ответа производился с помощью curl, интерфейс к которому выложен на webo.in,http://webo.in/my/action/timings/). Это не очень много, но в условиях виртуального хостинга может возрасти многократно, даже на изначально хорошо оптимизированных окружениях.

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

    Кэширование на стороне сервера

    Включение встроенного кэширования в Joomla! 1.5 сократило время ответа тестируемого сервера примерно на 30% (на 0,107 с). Прекрасно понятно, что в большинстве случаев оно будет практически бесполезно: если необходимо сократить время создания страниц на порядок, то нужны более кардинальные методы.

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

    Если просто включить Web Optimizer ( http://code.google.com/p/weboptimizator/ ) в процесс создания страниц, то время обработки документа возрастет незначительно (после создания всех кэширующих файлов на 0,006 с или 3% на тестовом сервере). Дополнительно включив HTML-кэши- рование в Web Optimizer, можно сократить время отдачи документа до 0,08 с (почти в 4 раза по сравнению с исходным временем создания страницы). Сразу стоит отметить, что установка вроде бы аналогичного по функциональ- ности дополнения Content Static ( http://extensions.joomla.org/extensionssite-management/cache/5104/details/ ) визуально на производительности никак не отразилась.

    Очевидно, что более грамотным будет кэшировать отдельные модули на странице, оставляя нужные места (или, как их любят называть в шаблонных движках, — заглушки) динамическими. Однако данное решение требует существенного вмешательства в алгоритм работы самой CMS. Дополнение System-Cache (включено в сборку по умолчанию) работает именно по такому принципу и на данный момент обеспечивает практически наилучшую производительность (при большой стабильности алгоритма).

    Заканчивая речь про кэширование создаваемых страниц на стороне сервера, стоит упомянуть, что дополнение JoomLa Performance Booster (http://www.joomLatwork.com/products/components/joomLa-performance.htmL) в ходе тестирования показало результаты примерно на 50% (пятикратный прирост производительности по сравнению с "обычной" версией) лучше, чем System-Cache, однако может работать не очень стабильно.

    Кэширование запросов к базе данных

    К серверному кэшированию можно подойти и с другой стороны: ограничить число запросов к базе данных, — обычно именно эта часть вызывает наиболее серьезную "утечку" производительности. Для JoomLa! существует расширение, позволяющее закэшировать все (или почти все) запросы к базе. Тут стоит понимать, что база данных сама по себе может работать достаточно быстро, и подобное решение будет эффективно только в том случае, если восстановление закэшированного значения выборки на порядок (или хотя бы в разы) быстрее, чем осуществление самой выборки (например, 1 мс против 10 мс). В противном случае прироста производительности не произойдет.

    Дополнение Query Cache (http://extensions.joomLa.org/extensions/site-management/cache/3180/detaiLs) позволяет задействовать как файловую систему, так и популярные кэширующие подсистемы (APC, Memcache и др.) для сохранения выполненных запросов.

    8.4.2. Клиентская часть

    Для оценки эффективности решений для клиентской оптимизации использовалось хорошо зарекомендовавшее себя (и относительно беспристрастное) дополнение к Firefox — YSLow.

    "Чистая" система

    "Чистая" установка Joomla! 1.5 набрала 65 баллов из 100. Вполне приемлемо. Стоит понимать, что если на систему дополнительно поставить десяток модулей и компонентов, то оценка резко ухудшится до 30-40.

    Следующий этап: архивирование

    В Joomla! есть встроенный gzip. Однако, во-первых, он работает через PHP, во-вторых, только для HTML-файлов. Грустно, что и видно по оценке: она поднялась только до 67.

    CssJsCompress

    Довольно известное дополнение (http://extensions.joomla.org/extensions/site-management/cache/7350/details), позволяющее объединять CSS- и JS-файлы. Однако оно не добавляет к ним всех кэширующих заголовков и сжатия, что и отразилось на результате: всего 72 балла по YSlow. В самой Joomla! gzip при этом был включен. Дополнение CSS/JS Cache ( http://extensions.joomla.org/extensions/site-management/cache/7801/details) не удалось заставить корректно работать."

    Joomla Perfomance Booster

    Joomla Performance Booster (http://www.joomlatwork.com/products/components/joomla-performance.html) является платным дополнением (39 евро) и представляет собой наиболее мощное "встроенное" решение для Joomla! 1.5. После его установки и настройки (объединение JavaScript работало "со скрипом" и его пришлось выключить) был достигнут результат в 73 балла (вполне вероятно, что при правильной работе с JavaScript оценка YSlow поднялась бы и до 75). В целом достаточно мощное дополнение, поскольку обеспечивает кроме самого кэширования еще и очень гибкое управление созданным кэшем."

    Однако данное дополнение возможно подключить вместе с приложением Web Optimizer (которое возьмет на себя всю логику преобразования клиентской части), что позволит существенно ускорить работу сайта на Joomla! практически любой сложности.

    Smart Optimizer

    Далее был протестирован Smart Optimizer (http://farhadi.ir/works/smartoptimizer, как отдельное PHP-приложение) — по характеру работы полностью аналогичный известному Minify (http://code.google.com/p/minify/, дополнение Minify4Joomla, http://extensions.joomla.org/extensions/site-management/cache/7183/details , "завести" не удалось). Установка у него достаточно сложная для непрофессионала, к тому же приходится править шаблоны вручную, нет возможности объединять файлы из разных директорий. Однако все остальное на высоте: оценка поднялась до 85. В самой Joomla! gzip при этом был включен."

    Web Optimizer

    Web Optimizer (www.web-optimizer.ru, как отдельное PHP-приложение или как плагин), естественно, устанавливается в "два клика" и обладает более мощным клиентским арсеналом: при отключенном сжатии в самой Joomla! оценка поднялась до 94 (с 65 изначально). Наверное, тут уже дополнительных комментариев не нужно.

    8.4.3. Заключение

    На данный момент для Joomla! 1.5 не существует более мощного бесплатного решения для оптимизации производительности, чем Web Optimizer. PHP Speedy (http://code.google.com/p/phpspeedy/), к сожалению, доступен только для Joomla! 1.0.

    8.5. Разгоняем Joostina

    Материал для данного раздела и консультирование по вопросам производительности Joostina предоставил Николай Кирш — веб-разработчик, основатель и технический лидер проекта Joostina CMS (http://www.joostina.ru/). Профессиональные приоритеты: качественный код, оптимизация под нагрузки, клиентская оптимизация.

    Joostina родилась и развивается с изначальной целью: быть максимально быстрой и эффективно использовать ресурсы сервера, не уменьшая при этом удобств как для пользователя, так и для администратора сайта. В основе системы лежит CMS Joomla! 1.0.x, считающаяся уже классикой. За время развития проекта было учтено максимум пожеланий пользователей по насыщению системы необходимым функционалом, изначально отсутствующим в Joomla!. Но кроме новых возможностей также добавились новые настройки, позволяющие оптимизировать сайт под более конкретные задачи и типовые Интернет-решения.

    Оптимизацию CMS Joostina можно разделить на 3 ступени:

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

    8.5.1. Оптимизация через базовые настройки системы

    Самый быстрый и безопасный способ настроить свой сайт на более высокую скорость и выжать из него максимум возможностей — основательно ознакомиться с настройками, располагающимися в "Глобальной конфигурации". Для доступа к настройкам необходимо авторизоваться с правами Супер-администратора в административной части, называемой так же панелью управления: http://www.exampLe.ru/administrator. Далее надо выбрать пункт меню "Сайт — Глобальная конфигурация", или прямо на главной страницы панели управления, через кнопку быстрого доступа "Глобальная конфигурация".

    Отключить генерацию RSS (syndicate)

    Joostina, как и большинство современных CMS, умеет формировать RSS-ленты из материалов, размещенных на сайте. Чтобы браузеры при отображении страниц сайта автоматически подхватывали RSS-ленты, ссылки на них прописываются в HTML-код страниц через теги примерно такого содержания:

    <link rel="alternate" type="application/rss+xml" title="Joostina v 1.3.0 b"
    href="http://www.example.ru/index2.php?option=com_rssfeed=0amp;no_html=1" />

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

    Использовать шаблон

    Для каждого пункта меню в панели управления можно выбрать уникальное отображение и состав модулей. Но также для каждого из пунктов меню можно назначить уникальный шаблон. Если такая возможность на сайте не используется, то ее следует отключить. Для этого и создана данная настройка. Параметр позволяет выбрать единый шаблон для всего сайта, что исключит один запрос и его обработку для выбора конкретного шаблона. Аналогичная настройка существует для панели управления.

    Отключить мамботы группы system

    Мамботы — это чаще всего небольшие PHP-сценарии, срабатываю щие на определенном этапе работы системы. Мамботы группы system сра- батывают в момент инициализации системы. Обычно в группу входят расширенные обработчики SEF и библиотеки подключения Javascript. Все мамботы этой группы можно посмотреть в панели управления "Меню - Мамботы -Мамботы сайта". Если справа в списке выбора типа нет группы system, то настройку рекомендуется отключить, это сделает ненужным один запрос в базу и инициализацию механизма.

    Отключить мамботы группы content,

    Отключить мамботы группы mainbody

    Действие данного пункта аналогично группе system, но поступать тут надо внимательнее. Группа content — основная, за счет нее выводятся изображения, вставленные в текст через тег {mosimage}, разбивка на страницы внутри текста и т. д. Безопаснее всего поочередно снимать мамботы с публикации и смотреть, что изменилось на сайте. Если все мамботы не опубликованы, а сайт отображается верно, — можно отключить всю группу.

    Использовать неопубликованные мамботы

    Мамботы группы content часто работают, заменяя определенные те- ги в тексте, например, {mosimage}. Но если мамбот не опубликован, то система его все равно использует — чтобы убрать из текста этот самый тег {mosimage}. Если на сайте такие мамботы не используются, то лучше активировать данную настройку, исключив неиспользуемые обращения к базе данных и подключение лишних файлов.

    Авторизация на сайте

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

    Время существования сессии на фронте

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

    Отключить сессии на фронте

    При посещении сайта авторизованным пользователем или даже гостем для него запускается механизм инициализации сессий, что влечет за собой запись данных в базу, создание cookie у пользователя и постоянную проверку авторизации. Если авторизация на сайте не важна, то параметр рекомендуется отключить. Но учтите, что модуль отображающих посетителей будет выдавать не точную информацию, так как он основывается на данных записанных в таблице сессий. Параметр рекомендуется использовать совместно с пунктом "Авторизация на сайте".

    Отключить контроль доступа к содержимому

    Хотя в Joostina имеется не очень много возможностей для полноценного создания и управления правами пользователей, доступ к содержимому всегда ведется с учетом прав текущего пользователя. Это добавляет в SQL-запрос дополнительное условие. На сайтах, где доступ не разграничен на зарегистрированных и гостей, параметр лучше активировать.

    Считать число прочтений содержимого

    При прочтении каждого содержимого увеличивается значение поля счетчика в таблице содержимого. Постоянные изменения даже одного поля таблицы содержимого сводят на нет встроенный в mysql механизм кэширования, да и дополнительный запрос в базу тоже лучше исключить. Настройку рекомендуется выключить, а ведение статистики доверить специализированным сервисам, типа li.ru или Google Analytics.

    Отключить проверки публикации по датам

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

    GZIP-сжатие страниц

    Позволяет передавать пользователю более компактные страницы. Содержимое пакуется через gzip-алгоритм на сервере, а распаковывается автоматически в браузере пользователя. Получается экономия на трафике, но немного больший расход ресурсов сервера; более подробно расчет оптимальности применения gzip приведен во второй главе книги "Разгони свой сайт".

    Блокировка компонентов

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

    Рейтинг/Голосование

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

    Ежедневная оптимизация таблиц базы данных

    Добавляет в работу сайта ежедневную оптимизацию всех таблиц базы данных через выполнение OPTIMIZE TABLE для каждой. Данная процедура уменьшает фрагментацию и производит общую оптимизацию таблиц встроенными средствами mysql.

    Сжатие CSS- и JS-файлов

    Позволяет выдавать вместо обычных JS- и CSS-файлов их упакованные аналоги. Экономит трафик и позволяет указать более длительное время кэширования. Работает только для встроенных файлов.

    Значение тега revisit:

    Позволяет указать параметр тега:

    <meta name="revisit" content="10 days" />

    который указывает, как часто сайт должен посещать поисковый робот. Правильно установленное значение позволит исключить повышенную нагрузку на сервер, вызванную слишком частым посещением поискового робота.

    8.5.2. Встроенное кэширование

    Включить кэширование

    В Joostina встроен механизм, позволяющий кэшировать результаты выполнения ресурсоемких функций или инициализаций объектов. Использование кэширования позволяет уменьшить число запросов в базу, уменьшить число подключаемых файлов и увеличить общую скорость работы системы. Кэширование лучше активировать после полного построения и отладки сайта.

    Тип кэширующей системы

    Кэшировать можно как в файлы, так и в специальные акселераторы кэширования. Joostina поддерживает работу кэширования с использованием apc, eacceLerator, xcache и memcache. Первые 3 — это не только кэш-акселераторы, но и общие оптимизаторы работы php. Наличие любого из них — очень большой плюс в работе сайта.

    Оптимизация кэширования

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

    Автоматическая очистка каталога кэша

    При кэшировании в файлы каталог кэша может содержать множество просроченных объектов. Система следит, чтобы таких случаев не было, но для большей уверенности рекомендуется активировать и этот параметр. На оптимизацию работы сайта число файлов в каталоге кэша имеет прямое влияние: чем больше файлов в каталоге одного уровня — тем дольше файловая система будет их отдавать.

    Кэширование меню панели управления

    При работе в панели управления в верхней части отображается меню, которое содержит пункты для доступа к основным операциям. Меню частично формируется из базы данных. Например, список установленных компонентов или список разделов и категорий. Такие данные изменяются не очень часто, и лучше произвести кэширование этого участка. Активация параметра также сделает вывод меню через внешний JavaScript- файл, код которого исключится из тела страниц и будет кэшироваться еще и на стороне пользователя — в браузере.

    Каталог кэша (/dev/shm)

    По умолчанию файлы кэша складываются в каталог /cache в корне сайта. В зависимости от настроек сервера можно попытаться перенести этот каталог в более быстрое место, например, на диск с другой файловой системой или /dev/shm. Не забудьте убедиться, что PHP-интерпретатор имеет полный доступ к указанному каталогу

    Время жизни кэша

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

    8.5.3. Отключение встроенной статистики

    Включить сбор статистики

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

    Вести статистику просмотра содержимого по дате

    Аналогично предыдущему параметру — лучше отключить и вести все учеты на серверах специальных сервисов.

    Статистика поисковых запросов

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

    8.5.4. Отключение неиспользуемых расширений

    Один из основных принципов оптимизации — использование только необходимого функционала. Joostina по своей сути является универсальной системой, и это влечет за собой некоторую ограниченность и сложность. Базовый дистрибутив системы имеет набор встроенных расширений, часть которых может не использоваться на сайте. Всего в Joostina можно отключить расширения всех 3-х типов:

  • компоненты,
  • модули,
  • мамботы.
  • Отключение компонентов лишь косвенно влияет на оптимизацию сайта. Но блокировка доступа — это отличный способ уменьшить число страниц для индексации или попыток спама. Чем меньше страниц, тем быстрее их проиндексирует поисковый робот и тем меньше времени эти страницы надо будет генерировать серверу для поисковика.

    Отключить неиспользуемые компоненты можно в панели управления на странице управления компонентами: "Меню - Компоненты - Управление компонентами". Для работы данного механизма необходимо, чтобы в глобальной конфигурации была активирована настройка "Блокировка компонентов".

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

    Заходим в меню управления модулями: "Меню - Модули - Модули сайта".

    Выбираем все модули и нажимаем кнопку "Скрыть", находящуюся в верхней части страницы — на тулбаре.

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

    Отключение мамботов позволяет существенно сократить непосредственное время генерации страницы. Мамботы разделены на группы, про это уже сообщалось ранее, и каждая группа отвечает за отдельные участки. Наиболее часто используемые — мамботы группы content, они позволяют обрабатывать содержимое, выдаваемое компонентом. Чаще всего такие мам-боты отвечают за замену в тексте специально оформленных тегов на необходимый функционал или оформление. Например, мамбот bot_mosimage отвечает за замену тега {mosimage} на необходимую картинку. Но для такой работы производится обработка текста регулярными выражениями, что не очень хорошо сказывается на производительности. Отключить неиспользуемые мамботы можно по той же схеме, что и модули, только на другой странице панели управления: Меню — Мамботы — Мамботы сайта.

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

    8.6. Пара советов для Ruby on Rails

    Уже много людей писали руководства, помогающие вашему веб-приложению работать быстрее. В этом разделе будут освещены самые простые, но наиболее эффективные методы, которые дадут вам возможность существенно ускорить ваше приложение без потери какого-либо функционала из Ruby on Rails.

    Часто бывает так, что одно веб-приложение подгружает сразу несколько JavaScript-файлов и CSS-стилей. Это существенно замедляет загрузку страницы, так как веб-браузер каждый раз заново запрашивает новый файл.

    Решение заключается в том, чтобы уменьшить количество внешних ресурсов на вашей странице, объединив их все в один файл. Поможет нам в этом плагин AssetPackager (http://synthesis.sbecker.net/pages/asset packager). Ставим

    script/plugin install git://github.com/sbecker/asset_packager.git

    Пример config/asset_packages.yml:

    javascripts:
    - base:
    - prototype
    - effects
    - controls
    - dragdrop
    - application
    - secondary:
    - foo
    - bar
    stylesheets:
    - base:
    - screen
    - header
    - secondary:
    - foo
    - bar

    И запускаем rake-задачу:

    rake asset:packager:build_all

    Дальше для JavaScript пишем

    <%= javascript_include_merged :base %>

    или

    <%= javascript_include_merged 'prototype', 'effects', 'controls',
    'dragdrop', 'application' % >

    Для стилей пишем:

    <%= stylesheet_link_merged :base %>

    или

    <%= stylesheet_link_merged 'screen', 'header' %>

    В итоге получаем для режима разработки наш старый код, например:

    <script type="text/javascript" src="/javascripts/prototype.js"></script>
    <script type="text/javascript" src="/javascripts/effects.js"></script>
    <script type="text/javascript" src="/javascripts/controls.js"></script>
    <script type="text/javascript" src="/javascripts/dragdrop.js"></script>
    <script type="text/javascript" src="/javascripts/application.js"></script>
    <link href="/stylesheets/screen.css" type="text/css" />
    <link href="/stylesheets/header.css" type="text/css" />

    А в режиме рабочего сайта будет:

    <script type="text/javascript" src="/javascripts/base_packaged.js?123456789"></script>
    <link href="/stylesheets/base_packaged.css?123456789" type="text/css" />

    Теперь, чтобы сделать нагрузку еще меньше, переносим все свои статические файлы на другой хост. В Ruby-on-Rails это очень просто сделать, достаточно добавить в config/environments/production.rb такую строку:

    config.action_controller.asset_host = "http://assets.example.ru"

    Теперь все image_tag, javascript_include_tag и т. д. будут указывать на этот хост.

    8.7. Разгоняем jQuery

    Материал для данного раздела получен при общении с Олегом Смирновым (aka CTAPbIu_MABP) — разработчиком пользовательских интерфейсов на Java и JavaScript. На данный момент он трудится над системой самообслуживания Украинского мобильного оператора "Киев-стар" (http://my.kyivstar.ua/). Олег занимается исследованиями в области производительности JavaScript-библиотек, в частности, jQuery, чему посветил много статей на своем сайте (http://mabp.kiev.ua/).

    jQuery, пожалуй, самая известная JavaScript-библиотека. Она позволяет быстро и просто производить манипуляции с DOM-деревом, навешивать события и делать AJAX-запросы на сервер. Ею пользуются очень много компаний, в том числе GoogLe и Microsoft. Под нее написано огромное количество плагинов, позволяющих расширить стандартный функционал и добавить на страницу виджет любой красоты.

    К сожалению, большая часть всех плагинов имеет код низкого качества, поэтому при установке нескольких таких плагинов страница начинает существенно подтормаживать. О том, как поправить код чужого плагина, чтобы избежать лишнего расхода памяти при сложных манипуляциях с DOM-деревом, как сделать анимацию плавной даже при анимировании нескольких элементов, а AJAX — быстрым, а также о том, как избежать некоторых скрытых багов, и будет этот раздел.

    8.7.1. Selectors

    Стоит начать с функции $, она принимает два параметра: первый — селектор, второй — контекст. Хотя контекст обычно опускают, впоследствии будет показано, как им грамотно пользоваться.

    Простой селект

    Самый простой вариант — это выбор по id, имени тега и имени класса.

    $("#id")
    $("tag")
    $(".class")

    Не случайно они расположены именно в такой последовательности: они идут по сложности алгоритма выборки. В первом случае вызов функции эквивалентен вызову

    document.getElementById("id");

    Поскольку предполагается, что id уникальный, поиск проходит очень быстро, и если на странице есть два элемента с таким id, то найден будет только первый. Хотя в IE и тут сделали ошибку и до 7-й версии включительно в случае отсутствия элемента с таким id он вернет элемент, у которого совпадает атрибут name.

    Во втором случае тоже все относительно просто:

    document.getElementsByTagName("tag");

    Получили все ноды с таким именем из документа, и все готово. И на удивление никаких ошибок, если не учитывать, что при запросе getElementsByTagName("*") IE вернет и комментарии тоже.

    В третьем случае, если есть возможность, работу перехватывает

    document.getElementsByClassName("class");

    (Таблицу поддержки этой функции в браузерах можно посмотреть на quirksmode.org)

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

    var nodes = document.getElementsByTagName("*"), result = [];
    for (var i=0; i<nodes.length ; i=""
      if="" ( " " + (nodes=""[i=""].className=""
    nodes=""[i=""].getAttribute=""
     .indexOf=""("class") >-1)
      result.push(nodes[i]);
            }

    Какой метод применять, определяется в самом начале при подключении библиотеки.

    Селект через querySelectorAll

    приходится обычно использовать намного более сложные конструкции. И для них в современных браузерах FireFox 3.0, Safari 3.2, Opera 9.5, а также в IE8, появились функции querySelector и querySelectorAll. Они, соответственно, предназначены для поиска одной или нескольких нод по CSS3-селекторам. Если браузер клиента поддерживает эту функцию, то все, о чем написано в прошлом пункте, — отпадает, и поиск происходит через querySelectorAll.

    $("#id .class tag")

    В лучшем случае селектор будет обработан именно querySelectorAll, потому что он написан по правилам CSS3. Но такое возможно не со всеми селекторами: jQuery поддерживает ряд селекторов, которые не входят в CSS3, такие, например, как : visible.

    $("#id .class tag:visible")

    Такой селектор выдаст ошибку в функции querySelectorAll, и селектор будет перенаправлен в поисковый движок Sizzle, где строка будет разбита на простые селекторы и превратится, по сути, в несколько разных поисков, в котором каждым следующим контекстом является предыдущий селектор.

    $(document).find("#id").find(".class").find("tag").filter(":visible")

    Скорость этого метода поиска напрямую зависит от величины DOM дерева: чем оно больше, тем медленнее, — но ее можно значительно увеличить, написав селектор раздельно.

    $("#id .class tag").filter(":visible")

    При этом querySelectorAll выберет все ноды, а Sizzle разберется с ":visible".

    По поводу псевдо-селекторов возникает также очень интересный вопрос: CSS3 поддерживает несколько видов псевдо-классов, такие как :nth-of-type/:nth-child/:parent/:not/:checked, jQuery имеет свою реализацию этих селекторов для браузеров, не поддерживающих querySelectorAll, или для браузеров, в которых querySelectorAll не поддерживает данный селектор, но эта реализация иногда отличается.

    Для примера возьмем псевдо-класс : nth-of-type и выберем все четные дивы, а из них все нечетные.

    document.querySelectorAll("div:nth-of-type(even):
    nth-of-type(odd)")
    // Safari/FireFox:0 IE/Opera:N/A
    $("div:nth-of-type(even):nth-of-type(odd)");
    // Safari/FireFox:0 IE/Opera:All
    $("div:even:odd"); // All: вернут 1,5,9 дивы

    Первых два примера работают одинаково и вернут либо 0, если отра- ботала функция querySelectorAll (это касается первого примера), либо все элементы, потому что их обработал Sizzle (это особенность реализа- ции выражения ":"). Третий же вернет 1, 5, 9 и т. д. элементы, а значит, селекторы отрабатывали в три прохода: сначала из всего DOM-дерева бы- ли выбраны все дивы, потом из них были выбраны все нечетные, а потом из оставшихся были выбраны все четные.

    jQuery также имеет набор псевдо-селекторов, которые не входят в CSS3 и обслуживаются только Sizzle’ом : visible/:animated/:input/:header. Их лучше выделять отдельно, поскольку они могут сильно замедлить выборку. Так, например, было с селекторами :visible/:hidden в версии 1.2.6: для то- го чтобы узнать, видимый это элемент или нет, надо было подняться до са- мого верха по DOM-дереву, проверяя атрибуты display и visible каждого ро- дителя (http://mabp.kiev.ua/2009/02/07/accelerates-selectors-in-jquery/).

    $("div").filter(":visible")

    Псевдо-классы, используемые для поиска элементов формы, такие, как :radio, тоже имеют некоторое преимущество, если не применяется querySelectorAll; в противном случае CSS3-селектор input[type=radio] работает быстрее.

    Сложенный селект

    Сложенный селект — это когда нам надо выбрать группу из двух или более разных селекторов, например, все дивы, у которых класс равен A, B и C.

    Это можно сделать двумя способами:

    $(".a,.b,.c")

    выбрать все сразу

    $(".a").add(".b").add(".c")

    или по одному.

    При этом, если задействована функция querySelectorAll, то первый способ быстрее второго в четыре раза, а если нет, то второй в два раза быстрее первого.

    Если уже заговорили про классы, их можно искать как любые другие атрибуты — например, если надо найти все классы, имена которых начинаются на "my", можно сделать так:

    $("[class^=my]")

    а не городить логику с использованием add, тем более что такой способ поддерживается querySelectorAll (http://mabp.kiev.ua/2009/02/21/testingproductivity-jquery-selectors/).

    Неправильный селект в контексте

    На сайте tvidesign.co.uk в одной очень популярной статье "Improve your jQuery — 25 excellent tips" написано, что селект лучше делать в контексте, и приведен вот такой пример:

    $(‘#listItem’ + i, $(‘.myList’))

    Рассмотрим подробнее: контекст — это то, где ищут селектор, значит, пример можно переписать в более наглядную, но менее читаемую форму:

    $($(".myList")).find("#listItem")

    При этом контекст от первого поиска будет являться document.

    $($(".myList",document)).find("#listItem")

    Еще раз перепишем согласно формуле

    $($(document).find(".myList")).find("#listItem")

    И наконец, раскроем скобки

    $(document).find(".myList").find("#listItem")

    Что же получается: мы выполняем дорогостоящую операцию поиска по имени класса (по всему DOM-дереву в худшем случае) для того, чтобы упростить и без того самую простую операцию поиска по id?

    Правильный селект в контексте

    Правильно делать "с точностью до наоборот". В контексте надо ука- зывать id элемента.

    $(".class",$("#id"))

    Однако можно не передавать в контекст jQuery объект, вполне достаточно

    $(".class","#id")

    Это можно переписать как

    $("#id").find(".class")

    Можно еще больше ускорить работу, если искать вот таким способом:

    $(document.getElementById("id")).find(".class")

    Но это, скорее всего, будет уже дурным тоном. Хотя поэкспериментировать интересно: что, если вместо getElementById взять querySelectorAll?

    $("div",document.querySelectorAll("#id"))

    Это примерно то же самое, что и

    $("div",[document.getElementById("id")])

    Ни прироста производительности, ни красоты кода из этого не получить, поэтому советую в контекст передавать что-то простое вроде id или при использовании псевдо-селекторов, обрабатываемых Sizzle’ом, пере давать их в селектор а все остальное в контекст:

    $(":visible","input[type=checkbox]")

    Раз уже пошла речь о псевдо-селекторах, то

    $(":checkbox")

    быстрее чем

    $("input[type=checkbox]")

    без использования querySelectorAll и наоборот.

    Cложный селект

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

    $("#id > div")

    Но если выборка уже есть, то будем использовать ее как контекст. Как мы уже выяснили, поиск в контексте происходит при помощи функции find:

    $("#id").find("> div")

    Но find — очень дорогая функция, она просматривает абсолютно всех потомков контекста, поэтому лучше применить функцию children, она просматривает только непосредственных потомков.

    $("#id").children("div")

    Есть еще ряд функций поиска и манипуляций, которых стоит избегать без крайней необходимости, — это find, closest, wrap, wrapInner, replaceWith, clone. Стоит заметить, что wrapAll сюда не входит (http://mabp.kiev.ua/2009/03/29/jquery-profiling/).

    8.7.2. Кэш

    Внутреннее кэширование

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

    Ситуация следующая: вы работаете со списком, у вас есть один из элементов li. Для того, чтобы получить все элементы, включая текущий, надо выбрать всех братьев (все, у кого родитель — это родитель текущего) этого элемента и добавить его самого:

    $("#id").siblings().add("#id")

    Так как он — прошлый элемент, с которым работали в цепочке вызовов, мы можем взять его из КЭШа:

    $("#id").siblings().andSelf()

    Конечно, в данном конкретном случаи быстрее было бы сделать

    $("#id").parent().children()

    Потому что siblings — это и есть выбор всех детей родителя. Но принцип использования этот пример иллюстрирует нормально.

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

    var elt = $("#id");
    elt.children().css({/**/})
    elt.click();

    Можно после работы с детьми вернуться обратно к родителю и работать с ним дальше:

    $("#id").children().css({/**/}).end().click()

    Кэширование селекторов

    Поскольку кэш так слабо развит, селекторы нужно кэшировать вручную. Давайте рассмотрим, например, вот такой код:

    for(var i=0;i<1000;i
    $("ul").append=""("<li>"+i""/

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

    var elts = $("ul");
    for(var i=0;i<1000;i
      elts.append=""("<li>"+i+"</li>")

    Буферизация

    Но этот код можно заставить работать еще быстрее! Каждый раз, делая append, мы заставляем обновиться DOM-дерево и заставляем браузер перерисовать страницу. Этого можно избежать, придерживая вставку в DOM-дерево.

    var str = "";
    for(var i=0;i<1000;i
      str += ""<li>"+i+"</li>"
    $("ul").html(str);

    Дело в том, что функции для работы с DOM-деревом у jQuery самые "тяжелые" (http://mabp.kiev.ua/2009/03/29/jquery-profiling/). Это объясняется просто. Все html-ноды, на которые повешены события через jQuery, имеют в себе атрибут с объектом jQuery. При удалении этих нод нужно следить, чтобы не было утечек памяти, и удалять эти атрибуты перед удалением ноды. В результате функции html и text вызывают функции полной очистки и только потом вставки нового содержимого:

    jQuery(DOMElement).empty().append(text)

    Функция empty выбирает все ноды и по очереди удаляет:

    jQuery(DOMElement).children().remove()

    А функция remove уже заботится, чтобы из элементов были удалены все дополнительные данные и события.

    Джон Ресиг утверждал, что знает способ быстро удалить все это и что улучшит эти методы, но что-то воз и ныне там. Поэтому будем ждать улуч- шенных функций уже в jQuery 1.4.

    Создание "на лету"

    Прошлый пример, на самом деле, был нужен для того, чтобы подобраться поближе к интересному факту. Часто приходится создавать какие-то вспомогательные дивы, и, естественно, нас интересует самый эргономичный способ это сделать. Казалось бы, в чем проблема: кинул кусок html-кода, и jQuery сама все сделала. Возьмем самый простой и банальный пример: надо создать пустой див:

    $("<div></div>")

    или

    $("<div/>")

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

    $("<div>text</div>")

    И не создавать, а потом добавлять текст:

    $("<div/>").text("text")

    Но это не каcается создания атрибутов, для них используются намно- го более "легкие" функции attr/css/addClass (http://mabp.kiev.ua/2009/03/29/jquery-profiling/), вот тут-то и имеет смысл вместо

    $("<div style='background:red;'/>")

    писать

    $("<div/>").css({background:'red'});

    — это даст небольшой, но выигрыш.

    8.7.3. События

    Множественные события

    $(window).bind("resize load",null,function(){
    $("#id").css({width:document.clientWidth})
    });

    Только при этом не забываем, что поведение IE8 не соответствует стандартам, и при загрузке страницы сначала происходит событие resize, а только потом load.

    То же самое корректно и в обратную сторону:

    $(window).unbind("resize load");

    Но это не работает в версии 1.2.6, точнее, работает только с именованными функциями, а с анонимными не годится, их надо удалять по одной.

    Одно событие на много элементов

    Если случается повесить события на длинный список:

    var ul = $("<ul/>");
    for(var i=0,j=1000;i<j;i++)
      $("<li>"+i+"</li>").click(function(e){
        alert(this.innerHTML);
      }).appendTo(ul);
    ul.appendTo("body");

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

    var str = "";
    for(var i=0,j=1000;i<j;i
      str += ""<li>"+i+"</li>";
    $("<ul/>")
      .append(str)
      .click(function(e){
        alert(e.target.innerHTML);
      })
      .appendTo("body");

    8.8. Клиентская оптимизация для произвольного сайта

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

    В общем, все вышеописанное было справедливо для сайта PERSPEK-TIVA IMPEREAL (http://www.vacLavak.ru/). Перед началом оптимизации главная страница "весила" около 500 Кб, загружала большое количество внешних скриптов (порядка 150 Кб) и ее невозможно было нормально загрузить по модему. Ниже рассказывается, какие методы были использованы для улучшения скорости загрузки, — может быть, общий ход проведения оптимизации поможет и вам точнее проанализировать ситуацию и применить требуемые действия.

    8.8.1. Этап первый: анализ ситуации

    Для анализа скорости загрузки в качестве стандарта стоит использо- вать как webo.in, так и Firebug NET Panel (http://www.getfirebug.com/). Почему два инструмента? С помощью Firebug можно достаточно точно отследить все запросы на странице из реального браузера. Однако Firebug временами не выдает всех запросов к файлам стилей и скриптов. Так же тяжело бывает с кэшированием. Для полного аналитического разбора можно адекватно использовать только анализатор скорости загрузки. После проведения проверки хорошо видно, какие файлы кэшируются (выставлено время кэша), у каких есть ETag или Last-Modified, а также, что более существенно, сразу виден потенциальный выигрыш при минимизации файлов.

    С помощью визуальной оптимизации (http://webo.in/my/action/load/) удобно посмотреть, как изменится диаграмма загрузки сайта, если применить все оптимизационные меры. И поскольку расчет производится аналитически, он обеспечивает достаточно большую точность (не нужно замерять по два-три раза, чтобы избежать случайных сетевых задержек).

    Разобрав основные проблемные места сайта с помощью указанных инструментов, намечаем путь действий — и вперед. Дополнительно мож- но оценить время ответа с сервера (http://webo.in/my/action/timings/) для динамических файлов и понять, нужно ли что-то придумывать для оптимизации серверной части.

    8.8.2. Этап второй: базовые действия

    Базовые действия по оптимизации чрезвычайно просты: нам нужно объединить все текстовые файлы и применить для них gzip-сжатие. А также включить кэширование на достаточно длительный срок (это позволит значительно ускорить по крайней мере открытие последующих страниц на этом сайте). Все это делается несколькими строками в конфигурационном файле Apache ( httpd.conf или .htaccess ):

    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE image/x-icon
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE application/x-javascript
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch Konqueror no-gzip
    BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
    Header append Vary User-Agent
    <FilesMatch .*\.(css|js|php|phtml|shtml|html|xml="")$>
      Header append Cache-Control private
    </FilesMatch>
    ExpiresActive On
    ExpiresDefault "access plus 1 month"
    <FilesMatch .*\.(shtml|html|phtml|php="")$>
      ExpiresActive Off
    </FilesMatch>

    Данный фрагмент кода включает сжатие для тех типов файлов, для которых это разумно сделать, затем отключает сжатие для старых и неподдерживаемых браузеров. Заголовки Vary User-Agent и Cache-Control private нужны для корректной обработки сжатия на этапе локальных прокси-серверов (чтобы они пропускали название браузера, не кэшировали сжатую версию и не отдавали ее тем пользовательским агентам, которые это не поддерживают). Далее на все файлы накидывается кэширование на один месяц, а затем для динамических файлов (HTML) оно отключается.

    Объединение и минимизация файлов может быть достаточно проблематичным занятием, но для этой цели можно использовать пакетную оптимизацию (http://webo.in/my/action/packet/), загрузить один архив со всеми файлами, которые требовалось оптимизировать, — и на выходе получить уже готовые к применению версии.

    8.8.3. Этап третий: шаманим с изображениями

    Следующим шагом по соотношению "эффективность/сложность" будет работа с изображениями. Начать лучше всего с создания CSS Sprites. Стоит руководствоваться уже описанными в лекции 4 принципами и соответствующей частью из книги "Разгони свой сайт". Естественно, что при создании CSS Sprites нужно будет видоизменять стили для страницы, но обычно это достаточно несложно при должной сноровке (либо можно использовать автоматическое решение — http://sprites.in/). В любом случае рекомендуется всегда делать резервные копии изменяемых файлов — это позволит быстро понять, где допущена ошибка, и не менее быстро ее исправить.

    Попутно все GIF-изображения переводятся в PNG-формат. Для этого архив с изображениями прогоняется через всю пакетную оптимизацию. Отдельным пунктом для сайта www.vacLavak.ru стоит упомянуть работу с ани-мированными GIF. В исходной точке все банеры занимали порядка 180 Кб. При оптимизации повезло (удалось удалить ненужные кадры и немного уменьшить палитру), в итоге размер рекламного блока сократился вдвое.

    В результате проведенных действий размер страницы уменьшился до 350 Кб, что было бы совсем неплохо, учитывая изначально "тяжелый" дизайн сайта.

    Однако этого оказалось недостаточно.

    8.8.4. Этап четвертый: счетчик времени загрузки

    Для более точного анализа реальной картины можно воспользоваться счетчиком времени загрузки (более подробно он описан в практическом приложении в книге "Разгони свой сайт"), который замеряет полное время загрузки у всех посетителей сайта для произвольной страницы. Установка его чрезвычайно проста: нужно получить код, установить его максимально близко к началу страницы в шаблоне (идеально — сразу после title) и пару дней собирать статистику (зависит от числа хитов; если на сайт заходит несколько десятков тысяч ежедневно, то хватит и дня или пары часов).

    (рис 8.6) Скорость загрузки страниц на четвертом этапе клиентской оптимизации для www.vaclavak.ru, источник: webo.in

    После установки счетчика обнаружилась следующая картина: среднее время загрузки (уже оптимизированного) сайта составило 16 секунд, за 4 секунды страницы загружались у 58% пользователей.

    8.8.5. Этап пятый: "ненавязчивая" реклама

    Было решено существенно переработать логику загрузки страницы: вынести рекламные блоки в пост-загрузку, а загрузку самого JavaScript "отложить". Все это было сделано в лучших традициях "ненавязчивого" JavaScript, которые описаны в седьмой главе книги "Разгони свой сайт". GoogLe AnaLytics был вынесен на событие OnDOMReady (для точности измерения посещений), остальные — на window.onload.

    В результате на странице сразу загружалось только основное содержание. Сразу после него — Google Analytics и дополнительные библиотеки для галереи изображений (но только на тех страницах, где это было нужно: в частности, это не требовалось для главной страницы). Затем шла загрузка рекламных банеров, информера с курсом валют и погоды. В самом конце на страницу (в самый низ) вставлялся блок с остальными счетчиками. Загрузка почти всего (99% кода) JavaScript была переделана на "ненавязчивый" манер.

    (рис 8.7) Скорость загрузки страниц на пятом этапе клиентской оптимизации для www.vaclavak.ru, источник: webo.in

    Единственная сложность возникла с блоком валют — он вызывался через внешний JavaScript-файл, который в свою очередь содержал docu-ment.write. Его пришлось вставлять через динамический iframe, который получал данные, а потом отправлял родительской странице. Поскольку информер этот загружается достаточно медленно, получился значительный выигрыш во времени загрузки страницы после его вынесения в постзагрузку.

    После очередных замеров с помощью счетчика времени загрузки получилась следующая картина: среднее время загрузки — 7,3 секунды (ускорение более 100% от уже оптимизированного варианта), за 4 секунды сайт загрузился у 82% посетителей.

    8.8.6. Заключение

    Благодаря существованию большого количества удобных инструментов для клиентской оптимизации (на http://webo.in/) удалось очень оперативно проработать клиентскую составляющую обычного в общем-то сайта. Только для широкополосного подключения скорость загрузки увеличилась в 7 раз (для коммутируемого доступа это число еще более значительно в связи с существенным уменьшением числа запросов, необходимых для первичного отображения страницы).

    (рис 8.8) История применения клиентской оптимизации для www.vaclavak.ru

    Сам процесс оптимизации относительно оценки самого webo.in мож- но представить следующим образом (в этом уже может помочь история проверок сайта, http://webo.in/my/action/history/):

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

    Вернуться к учебному плану