К вопросу производительности старых и новых версий ноды
Периодически у разработчиков, поддерживающих старые нодовские приложения, возникают сомнения по поводу необходимости перевода приложения со старых версий ноды на новые. Основная аргументация в пользу того, чтобы оставаться на старых — это «новыми фичами я не пользуюсь, но их реализация наверняка замедляет обработку в целом». Плюс ситуация может сильно усложняться наличием зависимостей от библиотек, которые под новыми нодами перестали поддерживаться, и смена версии ноды автоматически выливается в значительную переработку архитектуры приложения. Моя статья, надеюсь, поможет им определиться в этом вопросе
Прежде чем начать, напомню основной принцип ИТ бизнеса: РАБОТАЕТ, НЕ ТРОГАЙ!
Если ваше приложение работает как надо, если полностью справляется с поставленными задачами и нет острой необходимости в его переработке, лучше оставить всё так, как есть. Переработка может оказаться весьма болезненным и долгим процессом. И в результате вы не получите никаких реально осязаемых выгод, кроме чувства эстетического удовлетворения, а в это время ваш бизнес будет страдать.
Моя статья для тех, у кого необходимость всё же есть. Я опишу свою недавнюю ситуацию, расскажу о трудностях, с которыми столкнулся при её разрешении, и приведу результаты, которые в конечном итоге получил.
Я занимаюсь разработкой системы по сбору и обработке логов других приложений. Штатные рабочие нагрузки — сотни тысяч логов в минуту на один компонент. Система неплохо масштабировалась горизонтально, имела модульную архитектуру, успешно проработала не один год и со своими функциями в целом справлялась. Использовавшаяся версия ноды — 0.10
С чем столкнулись?
Естественно, не недостаток функционала. Новые фичи es6 даже не рассматривались как аргумент. Конечно, они делают жизнь слегка приятней, но не более того. Для любых наших задач вполне хватало и функционала старой ноды. Проблемы возникли в самом неожиданном месте по мере усложнения функционала.
Проблема первая:
Один из компонентов при пиковых входящих нагрузках и расходе памяти под 5ГБ вдруг начинал адски тормозить. Проблема возникала не каждый раз, спонтанно, обычно ближе к концу пика. Если приложение не падало по таймаутам, быстродействие постепенно в течение получаса-часа восстанавливалось. Перезагрузка вылечивала сразу.
После процесса «глубокой отладки» выяснилось, что тормозить начинает весь процесс, даже синхронные операции, из-за чего сделали вывод, что «плохеет» сборщику мусора самой ноды. Что с этим делать, совершенно было не понятно. Единственным решением виделось поиск по истории изменений нашего кода за несколько месяцев и откат важного функционала. С другой стороны, проблема возникала не часто, не каждый день, и ручной перезапуск системы тоже выглядел вполне приемлемым решением (ручной — значит скриптом по сигналу детектора отставаний). Мы больше склонялись ко второму варианту. И если бы не вторая проблема, то скорее всего его бы и реализовали.
Проблема вторая:
Другой из наших компонентов ел ощутимо много памяти. Поскольку этот компонент был по факту кэшем, его «жадность» была в принципе объяснима. До момента, пока не потребовалось ограничить его сверху строго заданным объёмом. И тут выяснилось, что компонент набирает память и не спешит её отдавать. Причём даже работая на едва ли не холостых оборотах. То есть в момент пиковой нагрузки менеджер памяти ноды отбирал памяти по максимуму, да ещё с запасом, и после этого удерживал её до скончания веков (перезагрузки компонента, например). Сразу упомяну, что естественно вариант утечек рассматривался и проверялся. Увы, утечек не было, и мы опять оказались в тупике.
Я пытался спрашивать в различных местах интернета, как продебажить управление памятью ноды и как разрешить нашу ситуацию. Но в ответ получил только массу негатива по поводу использования ноды 0.10. В общем-то именно этот негатив сподвиг меня на задачу по переходу на последние версии ноды.
Что сдерживало?
1. Опасения в потере производительности.
Кто работал на питоне, тот помнит, что переход от линейки 2.х к 3.х сопровождался солидной потерей производительности. Не знаю, как обстоят дела в питоне сейчас, возможно ситуация улучшилась. Но вполне логично ожидать, что после добавления всех этих новых фич es6 нода тоже могла солидно просесть. Пытался нагуглить какие-нибудь бенчмарки для сравнения новых нод со старыми, ничего толкового не нашёл.
2. Производительность JSON.parse
Поскольку мы работаем с логами, JSON.parse занимает львиную долю процессора. В своё время мы сравнивали его на разных версиях ноды и получили падение производительности где-то на 30%. Сравнивалась нода 4.х с 0.10 и 0.12.
На самом деле эти причины не были определяющими, так как процессор не являлся узким местом. Кроме того, для подобных задач есть горизонтальное масштабирование.
3. Зависимости пакетов
А вот это и было камнем преткновения. Наш центральный самый сложный компонент использовал пакет libmysqlclient, который работает только под нодой 0.10. Хуже того, его синхронные вызовы. То есть нельзя было просто сменить драйвер mysql, заменив одни вызовы другими, без глубокой переработки архитектуры компонента с частично синхронной на полностью асинхронную обработку. Причём самое сложное в этой задаче было даже не столько сама переработка, сколько обосновать руководству её необходимость и показать, что конкретно она даст проекту :)
Что дало?
В результате мы всё таки переехали с ноды 0.10 на последнюю lts 8.11.х
1. Падения производительности не произошло. Наоборот, мы получили прирост порядка 10–15%
2. Значительно улучшился расход памяти, примерно на 30–50% в зависимости от компонента
3. Озвученные выше «неразрешимые» проблемы разрешились сами собой
4. Мы наконец получили возможность использовать новые фичи от es6! Хотя по привычке всё равно их пока не используем)))