Измерение производительности с помощью синхронизации сервера
Опубликовано: 2022-03-10Приступая к любой работе по оптимизации производительности, одна из самых первых вещей, которую мы узнаём, заключается в том, что прежде чем вы сможете улучшить производительность, вы должны сначала измерить её. Не имея возможности измерить скорость, с которой что-то работает, мы не можем сказать, улучшают ли внесенные изменения производительность, не оказывают никакого эффекта или даже ухудшают ситуацию.
Многие из нас знакомы с работой над проблемой производительности на каком-то уровне. Это может быть что-то такое же простое, как попытка выяснить, почему JavaScript на вашей странице не запускается достаточно быстро или почему изображения слишком долго появляются при плохом Wi-Fi в отеле. Ответы на такого рода вопросы часто можно найти в очень знакомом месте: в инструментах разработчика вашего браузера.
За прошедшие годы инструменты разработчика были улучшены, чтобы помочь нам устранять подобные проблемы с производительностью во внешнем интерфейсе наших приложений. Браузеры теперь даже имеют встроенные аудиты производительности. Это может помочь отследить проблемы с внешним интерфейсом, но эти аудиты могут выявить еще один источник медлительности, который мы не можем исправить в браузере. Эта проблема заключается в медленном времени отклика сервера.
«Время до первого байта»
Оптимизация браузера мало что может сделать для улучшения страницы, которая просто медленно создается на сервере. Эта стоимость возникает между тем, как браузер делает запрос на файл и получает ответ. Изучение вашей каскадной диаграммы сети в инструментах разработчика покажет эту задержку в категории «Ожидание (TTFB)». Это время ожидания браузера между выполнением запроса и получением ответа.
С точки зрения производительности это известно как время до первого байта — количество времени, которое требуется, прежде чем сервер начнет отправлять что-то, с чем браузер сможет начать работать. В это время ожидания включено все, что нужно серверу для создания страницы. Для типичного сайта это может включать маршрутизацию запроса в правильную часть приложения, аутентификацию запроса, выполнение нескольких вызовов серверных систем, таких как базы данных, и т. д. Это может включать запуск контента через системы шаблонов, выполнение вызовов API к сторонним службам и, возможно, даже такие вещи, как отправка электронных писем или изменение размера изображений. Любая работа, которую сервер выполняет для выполнения запроса, сжимается в это ожидание TTFB, которое пользователь выполняет в своем браузере.
Так как же нам сократить это время и начать быстрее доставлять страницу пользователю? Ну, это большой вопрос, и ответ зависит от вашего приложения. Это работа самой оптимизации производительности. Что нам нужно сделать в первую очередь, так это измерить производительность, чтобы можно было оценить преимущества любых изменений.
Заголовок синхронизации сервера
Работа Server Timing заключается не в том, чтобы помочь вам определить время активности на вашем сервере. Вам нужно будет рассчитать время самостоятельно, используя любой набор инструментов, который вам предоставляет ваша серверная платформа. Скорее, цель Server Timing — указать, как эти измерения могут быть переданы в браузер.
Это делается очень просто, прозрачно для пользователя и оказывает минимальное влияние на вес вашей страницы. Информация отправляется в виде простого набора заголовков ответа HTTP.
Server-Timing: db;dur=123, tmpl;dur=56
В этом примере сообщается о двух разных временных точках с именами db
и tmpl
. Это не часть спецификации — это имена, которые мы выбрали, в данном случае для представления некоторых временных параметров базы данных и шаблона соответственно.
Свойство dur
указывает количество миллисекунд, которое потребовалось для завершения операции. Если мы посмотрим на запрос в разделе «Сеть» инструментов разработчика, то увидим, что тайминги добавились на график.
Заголовок Server-Timing
может принимать несколько метрик, разделенных запятыми:
Server-Timing: metric, metric, metric
Каждая метрика может указывать три возможных свойства
- Краткое имя метрики (например,
db
в нашем примере) - Продолжительность в миллисекундах (выражается как
dur=123
) - Описание (выраженное как
desc="My Description"
)
Каждое свойство отделяется точкой с запятой в качестве разделителя. Мы могли бы добавить описания к нашему примеру следующим образом:
Server-Timing: db;dur=123;desc="Database", tmpl;dur=56;desc="Template processing"
Единственное свойство, которое требуется, это name
. И dur
, и desc
являются необязательными и могут использоваться при необходимости. Например, если вам нужно отладить проблему синхронизации, которая возникает на одном сервере или в центре обработки данных, а не на другом, может быть полезно добавить эту информацию в ответ без связанной синхронизации.
Server-Timing: datacenter;desc="East coast data center", db;dur=123;desc="Database", tmpl;dur=56;desc="Template processing”
Затем это будет отображаться вместе с таймингами.
Одна вещь, которую вы можете заметить, это то, что временные полосы не отображаются в виде водопада. Это просто потому, что Server Timing не пытается передать последовательность таймингов, а только сами необработанные метрики.
Реализация серверного времени
Точная реализация в вашем собственном приложении будет зависеть от ваших конкретных обстоятельств, но принципы остаются теми же. Шаги всегда будут такими:
- Время некоторых операций
- Соберите вместе результаты тайминга
- Вывести HTTP-заголовок
В псевдокоде генерация ответа может выглядеть так:
startTimer('db') getInfoFromDatabase() stopTimer('db') startTimer('geo') geolocatePostalAddressWithAPI('10 Downing Street, London, UK') endTimer('geo') outputHeader('Server-Timing', getTimerOutput())
Основы реализации чего-либо в этом направлении должны быть простыми на любом языке. Очень простая реализация PHP может использовать microtime()
для операций синхронизации и может выглядеть примерно так:
class Timers { private $timers = []; public function startTimer($name, $description = null) { $this->timers[$name] = [ 'start' => microtime(true), 'desc' => $description, ]; } public function endTimer($name) { $this->timers[$name]['end'] = microtime(true); } public function getTimers() { $metrics = []; if (count($this->timers)) { foreach($this->timers as $name => $timer) { $timeTaken = ($timer['end'] - $timer['start']) * 1000; $output = sprintf('%s;dur=%f', $name, $timeTaken); if ($timer['desc'] != null) { $output .= sprintf(';desc="%s"', addslashes($timer['desc'])); } $metrics[] = $output; } } return implode($metrics, ', '); } }
Тестовый сценарий будет использовать его, как показано ниже, здесь используется функция usleep()
для искусственного создания задержки в выполнении сценария для имитации процесса, для завершения которого требуется время.
$Timers = new Timers(); $Timers->startTimer('db'); usleep('200000'); $Timers->endTimer('db'); $Timers->startTimer('tpl', 'Templating'); usleep('300000'); $Timers->endTimer('tpl'); $Timers->startTimer('geo', 'Geocoding'); usleep('400000'); $Timers->endTimer('geo'); header('Server-Timing: '.$Timers->getTimers());
Запуск этого кода сгенерировал заголовок, который выглядел так:
Server-Timing: db;dur=201.098919, tpl;dur=301.271915;desc="Templating", geo;dur=404.520988;desc="Geocoding"
Существующие реализации
Учитывая, насколько удобен Server Timing, мне удалось найти относительно немного реализаций. Пакет server-timing NPM предлагает удобный способ использования Server Timing из проектов Node.
Если вы используете PHP-фреймворк на основе промежуточного программного обеспечения, tuupola/server-timing-middleware также предоставляет удобный вариант. Я использую это в производстве на Notist в течение нескольких месяцев, и я всегда оставляю несколько основных таймингов включенными, если вы хотите увидеть пример в дикой природе.
Что касается поддержки браузера, лучшее, что я видел, это Chrome DevTools, и это то, что я использовал для скриншотов в этой статье.
Соображения
Серверная синхронизация сама по себе добавляет очень минимальные накладные расходы к ответу HTTP, отправляемому обратно по сети. Заголовок очень минимален и, как правило, его можно безопасно отправлять, не беспокоясь о таргетинге только на внутренних пользователей. Тем не менее, стоит делать имена и описания короткими, чтобы не добавлять лишние накладные расходы.
Больше беспокойства вызывает дополнительная работа, которую вы можете выполнять на сервере, чтобы синхронизировать страницу или приложение. Добавление дополнительного времени и ведения журнала само по себе может повлиять на производительность, поэтому стоит реализовать способ включения и выключения этого при необходимости.
Использование заголовка Server Timing — отличный способ убедиться, что вся информация о времени как от внешнего, так и от внутреннего интерфейса вашего приложения доступна в одном месте. Если ваше приложение не слишком сложное, его можно легко реализовать, и вы сможете запустить его в работу за очень короткий промежуток времени.
Если вы хотите узнать больше о синхронизации сервера, вы можете попробовать следующее:
- Спецификация синхронизации сервера W3C
- На странице MDN, посвященной синхронизации сервера, есть примеры и актуальная информация о поддержке браузера.
- Интересный репортаж от команды BBC iPlayer об их использовании Server Timing.