Здравствуйте! Меня зовут Ренат Саматов, я возглавляю фронтенд-направление в линейке продуктов Витрина. Большая часть наших проектов рендерится на сервере. Сегодня мы побеседуем об одном из важнейших аспектов SSR-приложения: о мониторинге.
Даже в 2020-м мониторинг Node.js-приложения является непростой задачей. Node.js — среда выполнения JavaScript, однопоточного языка программирования с динамической типизацией. Интерпретатору и среде исполнения предоставлены широкие полномочия, что очень легко может привести к утечке памяти или высокой нагрузке на процессор. Node.js работает асинхронно (псевдопараллельно), однако если одна функция блокирует поток или очередь событий, производительность приложения начинает сильно проседать.
В этой статье я расскажу о том, как контролировать ваше Node.js-приложение и быть уверенным в том, что проблем с утечкой памяти, ресурсов процессора и блокировкой потока не возникает. Мы побеседуем об управлении производительностью приложения (Application Performance Management, APM) и метриках, на которые следует обращать внимание. Погнали!
Мониторинг — это систематическое наблюдение и анализ прогресса и качества. Мониторинг приложения в первую очередь необходим для того, чтобы убедиться, как ни странно, в корректной работе приложения. Вы ведь хотите узнавать о багах раньше, чем они всплывут у конечного пользователя? Или быть готовым к горизонтальному масштабированию приложения до того, как оно перестало отвечать?
Мониторинг помогает не потерять клиентов и, что особенно важно, не тратить время зря. Время — деньги, а предотвращение простоев, потери ресурсов и избавление вашего продукта от проблем с производительностью сэкономит вам деньги в долгосрочной перспективе; что и должно быть в центре внимания каждого бизнеса — зарабатывать деньги.
APM — стандартизированный способ мониторинга производительности и доступности вашего приложения, демонстрирующий диаграммы с количеством запросов, временем ответа, загрузкой CPU и использованием памяти. На этих диаграммах отображены средние значения, процентили и любые настраиваемые группы.
Давайте подробнее рассмотрим метрики, которые необходимо отслеживать.
Метрики использования ЦП для Node.js
Выше я упоминал, что среда выполнения Node.js имеет ограничения в использовании потоков ЦП. Причиной этого является единственный поток среды выполнения, то есть он привязан к одному ядру процессора. Один экземпляр приложения Node.js может использовать только одно ядро ЦП.
Поэтому Node.js-приложения редко используют большое количество процессорного времени. Вместо этого они полагаются на неблокирующий ввод-вывод. ЦП не должен ждать запросы ввода-вывода, вместо этого он обрабатывает их асинхронно. Если вы столкнулись с высокой загрузкой ЦП, это может означать, что большая синхронная работа перегружает его и блокирует поток. Это плохо! При блокировке потока блокируются и асинхронные процессы.
В большинстве случаев вам не нужно беспокоиться о загрузке процессора. Чтобы снизить его использование, вы можете создать дочерние процессы, или форки, для обработки задач, интенсивно использующих ЦП. Например, у вас есть веб-сервер, который обрабатывает входящие запросы. Чтобы избежать блокировки этого потока, вы можете создать дочерний процесс для обработки задачи, интенсивно использующей процессор. Довольно круто.
Исправление кода с интенсивным использованием ЦП — это первый шаг к повышению производительности и стабильности вашего Node.js-сервера. Метрики, на которые следует обратить внимание:
- Использование процессора
- Загрузка процессора
Метрики использования памяти и утечек для Node.js
Чтобы разобраться в использовании памяти и возможных утечках, вам сначала нужно понять, что такое куча и стек. Значения могут храниться либо в стеке, либо в куче. Стек можно визуально представить как стопку книг, где книги на самом деле являются функциями, а их контекст сохраняется в памяти. Куча — это большая область, в которой хранится всё, что распределяется динамически.
С этим связан важный факт о памяти процесса Node.js, про который вы должны знать. Максимальный размер кучи для одного процесса может составлять 1,5 ГБ. Вы угадали! Утечки памяти — распространенная проблема в Node.js. Они случаются, когда на объекты ссылаются слишком долго, то есть значения сохраняются, даже если они не нужны. Поскольку Node.js основан на движке V8, он использует сборку мусора для освобождения памяти, используемой переменными, которые больше не нужны. Этот процесс останавливает выполнение программы. Мы поговорим о сборке мусора более подробно в следующем разделе.
Заметить утечки памяти проще, чем вы думаете. Если память вашего процесса постоянно растет, не уменьшаясь периодически за счет сборки мусора, скорее всего, у вас есть утечка памяти. Вам следует обратить внимание на следующие показатели:
- Освобожденная память между циклами сборки мусора.
- Объем выделенной под процесс памяти.
- Использование выделенной памяти процессом.
Метрики сборки мусора для Node.js
В среде выполнения V8 сборка мусора останавливает выполнение программы. Есть два типа сборки мусора. Один из них называется очисткой и использует циклы инкрементной сборки мусора для обработки только части кучи за раз. Это очень быстро по сравнению с полными циклами сборки мусора, которые освобождают память от объектов и переменных, переживших несколько циклов добавочной сборки мусора. Поскольку полные циклы сборки мусора приостанавливают выполнение программы, они выполняются реже.
Измеряя частоту выполнения полного или инкрементного цикла сборки мусора, вы можете увидеть, как он влияет на время, необходимое для освобождения памяти, и на то, сколько памяти было освобождено. Сравнение освобожденной памяти с размером кучи может показать вам, есть ли растущая тенденция, которая заставит вас выяснить, есть ли у вас утечка памяти.
Из-за всего упомянутого выше вам следует отслеживать следующие показатели сборки мусора Node.js:
- Время, затраченное на сборку мусора.
- Счетчики полных циклов сборки мусора.
- Счетчики дополнительных циклов сборки мусора.
- Освобожденная память после сборки мусора.
Показатели цикла событий Node.js
Node.js быстр, потому что он может обрабатывать события асинхронно. Это реализовано благодаря циклу событий: специальному месту, зарезервированному для обработки асинхронных функций, которые вызываются как ответы на определенные события и выполняются вне основного потока. Такие функции также называются функциями обратного вызова.
Node.js может быть привязан к ЦП и использовать асинхронные операции, чтобы не тратить циклы процессора во время ожидания операций ввода-вывода. Сервер может обрабатывать огромное количество подключений и не блокироваться для операций ввода-вывода. Это так и называется: неблокирующий ввод-вывод. Однако цикл событий может замедлиться, и в конечном итоге приведет к тому, что каждое последующее событие будет обрабатываться дольше, то есть возникнет так называемая задержка цикла событий.
Распространенными причинами запаздывания цикла событий являются длительные синхронные процессы и постепенное увеличение количества задач на цикл.
Длительные синхронные процессы
Помните, как вы обрабатываете синхронное выполнение в своем приложении? Все остальные операции должны дождаться выполнения. Отсюда знаменитое правило производительности Node.js: не блокируйте цикл событий! Вы не можете избежать работы вашего сервера, связанной с процессором, но вы можете понять, как выполнять асинхронные и синхронные задачи. Как упоминалось выше, для синхронных задач используйте форки (дочерние процессы).
Постепенное увеличение количества задач на цикл
По мере масштабирования приложения вы увидите увеличение нагрузки и количества задач на цикл. Node.js отслеживает все асинхронные функции, которые должны обрабатываться циклом событий. Задержка, возникающая при увеличении количества задач, приведет к увеличению времени отклика, когда количество станет слишком большим.
Хорошая новость заключается в том, что вы можете облегчить ситуацию, увеличив количество процессов, выполняющих ваше приложение. Используя модуль кластера, вы можете задействовать все ядра ЦП сервера.
Поэтому необходимо отслеживать показатели:
- Самая медленная обработка событий (максимальная задержка).
- Самая быстрая обработка событий (минимальная задержка).
- Средняя задержка цикла событий.
Node.js в кластерном режиме и разветвление рабочих процессов
Выше я несколько раз упоминал об однопоточном характере Node.js, об ограничении памяти для одного процесса и о том, что блокировки потока следует избегать всеми способами. Помимо этого масштабирование Node.js выполняется с помощью кластерного модуля.
Используя модуль кластера, вы можете создать главный процесс, который разделяет сокеты с разветвленными рабочими процессами. Эти процессы могут обмениваться сообщениями. Все разветвленные рабочие процессы имеют собственный идентификатор процесса и могут работать на выделенном ядре ЦП. Типичным вариантом использования веб-серверов является создание рабочих процессов, которые действуют в общем сокете сервера и обрабатывают запросы циклически.
Количество рабочих процессов — в том числе порожденные модулем кластера, и дочерние процессы, порожденные запуском синхронных задач вне основного потока, — может быть важной метрикой, которую необходимо знать. Если процессы по какой-то причине были прекращены, важно, чтобы вы снова запустили их. Наличие этой функции в инструменте мониторинга может быть большим преимуществом.
Здесь необходимо следить за показателями:
- Количество воркеров.
- Задержка цикла событий на одного воркера.
Node.js HTTP-запрос / задержка ответа
Следить за задержками, с которыми сталкивается пользователь, — самый важный шаг в мониторинге любого API. HTTP-запросы, попадающие на ваш сервер, и своевременные ответы пользователей — вот что заставит ваших клиентов возвращаться.
При мониторинге показателей HTTP-запросов и ответов необходимо учитывать 4 ключевых значения:
- Время отклика.
- Частоту запросов.
- Частоту ошибок.
- Размер содержимого запроса / ответа.
Резюме
Мониторинг работоспособности и производительности ваших Node.js-приложений может быть непростым. Ключевые метрики Node.js тесно связаны. Циклы сборки мусора вызывают изменения в памяти процесса и использовании ЦП. Отслеживание этих показателей Node.js имеет решающее значение для поддержания работоспособности вашего приложения и обслуживания пользователей с минимальной задержкой.
Выше приведены показатели, на которые я обращаю внимание в первую очередь. Буду рад любым вопросам и обратной связи в комментариях. В своих следующих публикациях я буду подробно освещать сценарии, связанные с мониторингом, и рассказывать о том, как мы их решаем в ДомКлике.
Спасибо за внимание.