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

Прежде чем начать, напомню основной принцип ИТ бизнеса: РАБОТАЕТ, НЕ ТРОГАЙ!

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

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

Я занимаюсь разработкой системы по сбору и обработке логов других приложений. Штатные рабочие нагрузки — сотни тысяч логов в минуту на один компонент. Система неплохо масштабировалась горизонтально, имела модульную архитектуру, успешно проработала не один год и со своими функциями в целом справлялась. Использовавшаяся версия ноды — 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! Хотя по привычке всё равно их пока не используем )))

Комментарии (22)


  1. rodial
    08.06.2018 22:45

    Мрачноватое начало, всю статью держали в напряжении, я уже начал переживать что в конце кого-то накажут.

    Думаю не хватает подробностей, особенно хочется почитать про «обосновать руководству её необходимость».


    1. TheRoSS Автор
      09.06.2018 12:45

      Ну это же не художественный роман) Цель статьи — дать какую-то обратную связь тем людям, которые ещё стоят перед выбором и сомневаются


  1. artalar
    08.06.2018 23:48

    Интересно сколько человеко-часов ушло и на какой объем кода.


    1. vlreshet
      08.06.2018 23:55

      А обновления ноды сильно ломают обратную совместимость? Никогда не видел backward-impompatible изменений в JS, реально интересно


      1. artalar
        08.06.2018 23:59

        Ну вопервых автор указал на libmysqlclient и необходимость изменения всей архитектуры, что уже интересно. Во вторых что-то вроде nodejs.org/dist/latest-v8.x/docs/api/all.html#deprecations_deprecated_apis


      1. evkochurov
        09.06.2018 13:58

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

        А вот API для подключения расширений к Ноде на других языках несколько раз кардинально менялось. И если использовать модуль, написанный, скажем, на C++, который уже давно никем не сопровождается, то, чтобы его оживить, понадобится квалификация, скажем так, сильно отличающаяся от квалификации javascript-программиста.


    1. TheRoSS Автор
      09.06.2018 13:01

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


  1. k12th
    09.06.2018 00:14

    Пытался нагуглить какие-нибудь бенчмарки для сравнения новых нод со старыми, ничего толкового не нашёл.

    А бенчмарки v8 не искали?


    1. TheRoSS Автор
      09.06.2018 13:05

      Искал. Но информации, достаточной для принятия решения не нашёл. Впрочем, это критерий слишком субъективный. Окончательное решение о переходе было скорее интуитивным, чем основывалось на реальных фактах. Плюс желание иметь нормальные возможности отладки, которые появились в ноде только с версии 0.12


  1. Zet_Roy
    09.06.2018 00:59
    +1

    Прежде чем начать, напомню основной принцип ИТ бизнеса: РАБОТАЕТ, НЕ ТРОГАЙ!

    Проблема первая:
    Один из компонентов при пиковых входящих нагрузках и расходе памяти под 5ГБ вдруг начинал адски тормозить.

    Проблема вторая:
    Другой из наших компонентов ел ощутимо много памяти.

    А где оно работало?
    Из за жадности бизнеса, потеряли много человеко часов на костыли, вместо того чтобы использовать новую версию где все баги исправлены.
    Есть еще один принцип, СКУПОЙ ПЛАТИТ ДВАЖДЫ!..


    1. Voiddancer
      09.06.2018 06:54

      Так он для читателей предупредил, дабы не сподвигнуть кого на неприятности своим примером. А ему то надо было. Иначе бы и статьи не получилось. Довольно куцей, кстати.


    1. denisromanenko
      09.06.2018 09:00

      Вы уверены, что это именно злой директор зажимал перевод приложения на обновленные версии ноды?

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

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

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


      1. TheRoSS Автор
        09.06.2018 13:13

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


  1. Moxa
    09.06.2018 01:07

    А не думали перейти на go или java? у меня нет деталей задачи, как-то странно на парсинге логов потреблять 5гб оперативы


    1. TheRoSS Автор
      09.06.2018 13:25

      Задача сложнее, чем просто парсинг логов. Например, подшивка к логу юзера, обработку различных хуков и построение реалтаймовых агрегаций. И нода вполне с задачей справляется. Но вы совершенно правы, что расход в 5ГБ оперативы — это не нормально. Обычный рабочий расход памяти у данного компонента — менее одного гига. Перерасход происходит только в определённых исключительных ситуациях. И дело здесь не в языке, а в архитектурной недоработке. Мы знаем об этой недоработке, но более простым решением оказалось установить параметр --max-old-space-size в более высокое значение.


  1. tamtakoe
    09.06.2018 01:59
    +2

    Напомню основной принцип ИТ бизнеса: ОБНОВЛЯЙ ПОСТОЯННО. Если периодически обновлять на стабильную версию с задержкой в пару месяцев (чтобы первые баги успели пофиксить), то проблем придется решать значительно меньше. И уж точно не придется сталкиваться с тем, что разработчики уже давно пофиксили.


    1. immaculate
      09.06.2018 08:00

      Да, тоже хотел сказать, что «работает — не трогай» — устаревший принцип. На практике, у каждого приложения 100500 зависимостей, если не обновлять платформу и зависимости, то очень быстро, за год-два, окажешься в вакууме на:
      1) неподдерживаемой платформе/версии языка
      2) без обновлений безопасности
      3) без исправлений ошибок
      4) без возможности воспользоваться современными инструментами и библиотеками


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


      1. TheRoSS Автор
        09.06.2018 13:32

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


  1. Ostrouschcko
    09.06.2018 08:24

    С чем столкнулись?

    Естественно, не недостаток функционала. Новые фичи es6 даже не рассматривались как аргумент.


    image


  1. wormen
    09.06.2018 13:38
    +1

    еще хотел добавить про ноду…
    что начиная с версии 9.0.0, с производительностью стало еще лучше, чем 8 версии, в частности с памятью, которая в 8 ветке все еще имеет утечки, гораздо меньше, чем предыдущие версии

    примерно с 9.3, утечек памяти уже не наблюдалось

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


    1. TheRoSS Автор
      09.06.2018 13:43

      Спасибо за информацию, взял на заметку. Брал ноду 8 только потому, что она LTS. Скорее всего 10-ка тоже вот-вот перейдёт в LTS статус, не вижу причин не перейти на неё.


      По поводу утечек памяти скажу, что при наших объёмах (террабайты в сутки) утечки обязательно проявили бы себя. Значит либо в 8-й ноде утечки пофиксили, либо вы делаете что-то очень специфическое


      1. wormen
        09.06.2018 13:47

        да, у нас специфические задачи
        на текущий момент используем 10ю версию, показатели даже лучше чем у 9 версии