Pull to refresh

Производительность фронтэнда. Часть 2 — кешируем динамический контент с помощью etagify

Reading time 5 min
Views 5.6K
Original author: Jared Hirsch
От переводчика: Это шестая статья из цикла о Node.js от команды Mozilla Identity, которая занимается проектом Persona.





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

Эта статья посвящена etagify — модулю middleware для Connect, который генерирует ETag-и на лету на основе MD5-хешей ответов, и хранит эти хеши в памяти. Etagify избавляет от лишней рутины при сборке проекта, предельно прост в использовании и увеличивает производительность больше, чем можно было бы ожидать (в своих тестах мы получили ускорение загрузки страниц на 9%):

myapp = require('express').createServer();
myapp.use(require('etagify')());
...
app.get('/about', function(req, res) {
  res.etagify(); 
  var body = ejs.render(template, options);
  res.send(body);
});


Рассмотрим подробнее, как работает etagify, когда его стоит и когда не стоит использовать и как измерить результат его применения. На случай, если вы не совсем хорошо представляете, что такое Etag-и, и как с ними работать, я написал небольшую шпаргалку.

Как работает etagify


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

Etagify делает две вещи — считает хеши для отдаваемых клиенту ресурсов и хранит их в памяти, чтобы проверять версию ресурса при обработке условных GET-запросов.

Вот как формируется массив хешей:

// Принцип работы etagify.js 
// В начале массив хешей пуст.
// вот как выглядит типичная запись: 
//   '/about': { md5: 'fa88257b77...' }
var etags = {};
 
var _end = res.end;
res.end = function(body) {
  var hash = crypto.createHash('md5');
 
  // если есть тело ответа, хешируем его
  if (body) { hash.update(body); }
 
  // и добавляем в наш массив
  etags[req.path] = { md5: hash.digest('hex') };
 
  // передаём управление дальше
  _end.apply(res, arguments);
}

А вот так проверяется версия ресурса:

return function(req, res, next) {
  var cached = etags[req.path]['md5'];
 
  // Если у нас уже есть ETag, добавляем его в заголовок 
  if (cached) { res.setHeader('ETag', '"' + cached + '"' }
 
  // если браузер отправил условный GET,
  if (connect.utils.conditionalGET(req)) {
 
    // Проверим, не совпадают ли If-None-Match и ETag
    if (!connect.utils.modified(req, res)) {
 
      // Закешированная браузером версия ресурса совпадает с актуальной.
      // вырезаем из заголовка ETag и возвращаем клиенту статус 304 Not modified
      res.removeHeader('ETag');
      return connect.utils.notModified(res);        
    }
  }
}

Когда можно и когда нельзя применять etagify


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

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

Измеряем улучшение производительности


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

image

Мы запустили экземпляр Persona на awsbox (об awsbox речь пойдёт подробнее в последней статье цикла), открыли firebug и сделали по 50 замеров скорости загрузки страницы «about» с etagify и без (Время загрузки страницы имело большое значение для нас. В других проектах важнее могут быть другие метрики — время до начала отображения контента, или время до показа первого рекламного сообщения и т.п.).

Мы проанализировали полученные данные — вычислили среднее значение и стандартное отклонение для обоих наборов данных, предположив, что они распределены нормально.

К своему удивлению, мы обнаружили, что etagify уменьшает время загрузки страницы на 9% c 1,65 (с отклонением 0,19) до 1,50 (с отклонением 0,13) секунд. Неплохой результат, если учесть как мало усилий он потребовал.

Затем мы применили t-критерий Стьюдента, чтобы узнать, с какой вероятностью подобный результат мог быть достигнут без etagify. P-значение оказалось меньше 0,01, то есть вероятность случайного появления такого же распределения меньше 1%. Таким образом измеренное улучшение статистически значимо.

Вот как это выглядит на графике:

image

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




Tags:
Hubs:
+22
Comments 0
Comments Leave a comment

Articles

Information

Website
nordavind.ru
Registered
Employees
31–50 employees
Location
Россия