Как стать автором
Обновить

Комментарии 44

Сначала статья про шизофрению. Теперь про 9летний монолит на ноде…
Несмотря ни на что, совпадение случайно!
Сверхчеловек существует!
Правильно я понимаю, что slowlint это некий аналог lint-staged? lint-staged позволяет запускать линтер только для изменённых файлов.

Я его внедрял на одном из проектов вместе с husky, зашло очень даже хорошо.

Да, lint-staged это прекрасная вещь, мы его тоже используем, и тоже с хаски на прекоммите — потому что линтинг всех корректных файлов монолита занимает около двух минут.


Про slowlint у меня, видимо, плохо получается пояснять. Попробую ещё раз здесь.


Предпосылки такие:


  • У нас было большое количество файлов, которые не проходят линтинг. Почти все, на самом деле.
  • Хотелось, чтобы линтинг был в CI, но при этом не мешал разработчикам.
  • Хотелось, чтобы линтинг шёл постепенно. Прогонять --fix по всему репозиторию — чревато ошибками, грозит потерей верхнего слоя истории, мердж конфликтами, да и не поправит автомат и половины ошибок.
  • Хотелось, чтобы новые файлы обязательно соответствовали нашим стандартам.

Можно было бы вогнать всё в .eslintignore, но тут проблема — если это сделать, то любая IDE сразу применит его и перестанет использовать линтер — так что ничего лучше не станет. Хотелось таки мозолить глаза разработчикам. Поэтому slowlint делает очень простую вещь — он позволяет добавить дополнительный файл а-ля .eslignignore, который не воспринимается IDE, но используется при проверках. К сожалению, из коробки в eslint нет возможности использовать два игнор файла, иначе надстройка была бы не нужна.


В результате, файлы у нас делаятся на три категории:
1) Игонорируется в .eslintignore — те вещи, которые не надо трогать никогда — например, легаси библиотеки. CI, IDE и линтер их игнорирует. В целом, у нас уже нет такого кода в репозитории.
2) Игнорируется в slowlint — те вещи, которые надо исправить. IDE не воспринимает этот игнор, ошибки мозолят глаза разработчикам, код постепенно переписывается и удаляется из игнор листа. А ещё этот список можно автоматически перегенерировать при помощи slowlint, если внезапно куча некорректного кода исправилась или куда-то уехала.
3) Нигде не игнорируются.


Звучит несколько сумбурно, но отлично у нас отработало.


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

Во-вторых, всю жизнь он провёл под хайлоадом (сейчас это 23 млн запросов в час).


23М / 60 мин = 383к рпс (ну пусть 400к будет)

400к рпс на одну инстанцию (раз это монолит) как-то жирно. Если монолит, то это 1 инстанс? или это все же монорепо?
При монолитной архитектуре никто не говорит, что должен быть единственный инстанс приложения.
Тем более, в случае Node.JS — будь это так — мы бы на одном потоке не пережили бы и открытие сервиса.
Инстансов у нас было большое количество, ещё и с распределением по ролям, окружениям, балансировкой — но это уже отдельная большая история.
Можно добавить продолжение с подробностями.
А сколько примерно экземпляров приложения запускалось для отработки загрузки 6383 rps?
Чуть ниже поправили, что всё же около 6к. И обработкой запросов занимаются сотни инстансов приложения — запросы у нас тяжёлые, это не выдача какого-нибудь куска статики, а подзапросы в большое количество внешних сервисов, их агрегация и обработка.

Циферки RPS на самом деле мало чего значат без понимания, что именно за запросы идут, и с чем можно их сравнивать, поэтому я их не очень люблю приводить — слишком многие начинают сравнивать тёплое и мягкое.
Все таки 383к рпм или 6383 рпс. Что тем не менее тоже весьма дохрена, учитывая, что это среднее значение.
23кк/60/60 = 6.3к, если меня не обманывает математика. Про 383 только комментатор выше писал, забыв ещё раз на 60 разделить, а я внимания не обратил. И да — это среднее — у нас по большей части пользователи из РФ, так что ночью нагрузки почти нет.
>Тот самый случай, когда синергия — это зло. Потому что любой компонент был переиспользован по несколько сотен раз, и если была возможность использовать его криво — то она не была упущена. Любое действие может вызвать абсолютно непредсказуемые эффекты, и не все из них отслеживаются юнитами и интеграционными тестами.

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

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

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

"1. Боль в монолите", кажется, имеет мало отношения к монолитности.


и если была возможность использовать его криво — то она не была упущена.

Это проблема культуры разработки: "использовать криво" — это про людей, а не про монолиты.


Любое действие может вызвать абсолютно непредсказуемые эффекты, и не все из них отслеживаются

Это проблема дизайна: как сделать так, чтобы глядя на код было очевидно какие эффекты он вызывает. Микросервисы эту проблему вообще никак не адресуют.


Хочешь Express? Линтер? Другой фреймворк для тестов или моков? Обновить валидатор или хотя бы lodash? Обновить Node.js? Извини. Для этого придётся править тысячи строк кода.

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


Что приводит к тому, что в релизе может быть по 60 задач и больше. такое количество вызывает мердж конфликты, внезапные синергетические эффекты, полную загруженность QA на разборе логов, и прочие печали.

Насколько я понимаю, процесс продиктован непредсказуемостью ("Любое действие может вызвать абсолютно непредсказуемые эффекты"). Т.к. микросервисы эту проблему даже не пытаются решить, не понятно в чём здесь виноват монолит.


Сложный контекстный поиск (не забывайте, у нас нет статической типизации)

Это проблема JS и к монолитам отношения не имеет. Тут даже наоборот — будь у вас статическая типизация, монолит делал бы любую работу с кодом проще, чем микросервисы.


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

С технической точки зрения у микросервисов ещё хуже: если 80% задачи сводится к правкам в одном сервисе, возникает искушение остальные 20% засунуть в тот же сервис, даже если этому коду там совсем не место. Искушение, конечно, вызвано тем, что зарелизить один сервис "проще", чем два.


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


Потекла память? Выросло потребление CPU? Захотелось построить флейм графы? Извини. В монолите одновременно происходит столько всего, что локализовать какую-то проблему становится безумно сложно. Например, понять, какая из 60 задач при выкатке в продакшн вызывает повышенное потребление ресурсов (хотя локально, на тестовых и стейджинг средах это не воспроизводится) — почти нереально.

С микросервисами ещё интереснее — если сервис X внезапно стал потреблять в 10 раз больше ресурсов, из этого совершенно не следует, что проблема — в сервисе X. Часто бывает, что проблема в сервисе Z, который вызывает сервис Y, который в цикле дёргает X по 20 раз на каждую транзакцию :-)

"использовать криво" — это про людей, а не про монолиты.

Повторюсь. Проблема — не в факте кривизны использования, а в количестве переиспользований, которые надо проверить за раз.


Это проблема дизайна: как сделать так, чтобы глядя на код было очевидно какие эффекты он вызывает. Микросервисы эту проблему вообще никак не адресуют.

Повторюсь. Меньше кода — проще понять.


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

Повторюсь. Перейти на мажорную версию, поправив 10 строчек — легко. Перейти на мажорную версию, поправив 1000 строчек — кошмар.


Т.к. микросервисы эту проблему даже не пытаются решить, не понятно в чём здесь виноват монолит.

С микросервисами нам стало проще локализовать проблему в конкретном сервисе.


Это проблема JS и к монолитам отношения не имеет. Тут даже наоборот — будь у вас статическая типизация, монолит делал бы любую работу с кодом проще, чем микросервисы.

Это проблема JS+монолита. Я и имел в виду, что при статической типизации было бы порядком проще.


Искушение, конечно, вызвано тем, что зарелизить один сервис "проще", чем два.

В нашем флоу, разработчик делает задачи, и ничего не знает о релизах — так что ему всё равно, сделать правку в одном сервисе, или в двух.


С микросервисами фичи превращаются в "вот этот код, вон тот код, дождаться вон того релиза, и вот потом-то уже сможем зарелизить нашу фичу".

Да нет, просто релизим, но не включаем. Проверить её работоспособность можно и без зависимых микросервисов.


Часто бывает, что проблема в сервисе Z, который вызывает сервис Y, который в цикле дёргает X по 20 раз на каждую транзакцию :-)

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

Проблема — не в факте кривизны использования, а в количестве переиспользований, которые надо проверить за раз.

Не получается понять что всё-таки вы имеете в виду под этой необходимостью "проверять много переиспользований". Это какая-то ручная работа?


Меньше кода — проще понять.

Так ведь его не меньше, его как минимум столько же. Только теперь вместо "Ctrl+click, Ctrl+click, Ctrl+click, о, всё понятно" это превращается в: "а, тут мы дёргаем другой сервис, окей, пошёл смотреть его код".


Перейти на мажорную версию, поправив 10 строчек — легко. Перейти на мажорную версию, поправив 1000 строчек — кошмар.

Я понял, вы имеете в виду: "с микросервисами моя задача, как разработчика, вероятно, будет ограничина одним микросервисом".


В нашем флоу, разработчик делает задачи, и ничего не знает о релизах — так что ему всё равно, сделать правку в одном сервисе, или в двух.

Вы в статье написали: "часто возникают задачи с непонятной сферой ответственности", а теперь "ему всё равно, сделать правку в одном сервисе, или в двух". Как монолитность-микросервисность влияет на владение кодом?


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

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

Не получается понять что всё-таки вы имеете в виду под этой необходимостью "проверять много переиспользований". Это какая-то ручная работа?

Ага. Например, замена кода при breaking changes, или проверка использования каких-то deprecated методов.


Так ведь его не меньше, его как минимум столько же. Только теперь вместо "Ctrl+click, Ctrl+click, Ctrl+click, о, всё понятно" это превращается в: "а, тут мы дёргаем другой сервис, окей, пошёл смотреть его код".

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


Я понял, вы имеете в виду: "с микросервисами моя задача, как разработчика, вероятно, будет ограничина одним микросервисом".

Нет, я имею в виду, что много маленьких итеративные правки проще одной огроменной.


Как монолитность-микросервисность влияет на владение кодом?

Снова повторюсь. Обычно известно, какая команда отвечает за тот или иной микросервис целиком.


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

Разделить использование RAM или CPU по исполняемым функциям на продакшне — не такая простая задача. Она тоже решаема, но распределить по микросервисам проще.

«Это проблема JS и к монолитам отношения не имеет. Тут даже наоборот — будь у вас статическая типизация, монолит делал бы любую работу с кодом проще, чем микросервисы.»


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

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

Расскажите, пожалуйста, как получилось что юнит тесты в препуш хуке выполняются 10 секунд.

Это время выполнения всех или только тех, с которыми связаны изменения в коммите?
Что было самым результативным при правке тестов?
Понять, с какими именно тестами связаны изменения в коммите — задача весьма нетривиальная. Разве что сначала проверить покрытие текущее покрытие тестов. Смайлик.

Так что это время прогона всех юнитов. Наиболее результативным оказался поиск самых долгих тестов и их профилирование. Например, отрыли древний тест, в котором разработчик решил проверить, что несколько тысяч случайно сгенерированных величин будут действительно случайны… И мы проверяли это несколько лет!
>Сложный контекстный поиск (не забывайте, у нас нет статической типизации)

Не, ну тут Вы просто себя не любите. Понятно что 9 лет назад typescript еще не было, но уже лет пять как им уверенно можно пользоваться.
Опять же — проблема большого количества людей со своими вкусами. Не все команды были за внедрение TS. Насильно это делать не хотелось. А вот в микросервисах в некоторых командах он отлично зашёл.
Интересная статья, спасибо
Тоже шел подобными путями пару раз, лично у нас было еще +30 всяких разных интересных проблем. Понимаю что они просто не вместились в презентацию.
Хочу пару моментов уточнить
1. Удалось ли при такой поэтапной миграции полностью отказаться отказаться от старого кода в монолите? И была ли такая цель вообще?
2. В целом по моему опыту сложность продукта, доменной области в монолите приводила к вышеуказанным проблемам, и при миграции на микросервисы вы эту сложность перекладываете на плечи девопсов. На сколько больше у вас стало девопсов в итоге? Или как оценить примерно сколько людей девопсов понадобится например через год-два после миграции 30% функционала на микросервисы? Мс и монолит у вас on-premise?

Да, проблем было гораздо больше, но я был ограничен и временем доклада и размером статьи. Огромное мало кто прочитает. Судя по комментариям, и эту длину многие читали по диагонали.


  1. Удалось ли при такой поэтапной миграции полностью отказаться отказаться от старого кода в монолите? И была ли такая цель вообще?

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


На сколько больше у вас стало девопсов в итоге?

Ну, в самом начале у нас было ноль девопсов, так что в любом случае их стало бесконечно больше. Сейчас команда девопс насчитывает около пяти человек. Сложности и потребность в увеличении команды возникли не столько из-за микросервисов, сколько из-за сознательного применения различных технологий и подходов. Если бы мы остались только на ноде на микросервисах стандартной конфигурации — думаю, пары человек бы хватало.


Мс и монолит у вас on-premise?

Да, конечно. Свои облака, мощности, поднимаемые по запросу и в ответ на нагрузку, и прочие радости.

Я для себя вывел такое эмпирическое правило:
1. Если вас на беке пара человек — пишите монолит и не гонитесь за модой в девопсе. Уже на десятке микросервисов вы утонете.
2. Если вас тридцать человек — никаких монолитов, вы в нём запутаетесь.

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

Да, как-то так. Главное — вовремя поймать момент, когда первое состояние переходит во второе, и осилить перейти. Но до него ещё нужно дожить, что выходит не так часто, как хотелось бы.

TPC & UDP, Redis, NATS, kafka, итд.
Везде где есть потокавая передача данных можно использовать как транспорт. Главное формат общения был запрос ответа был под стандартом.


https://moleculer.services/docs/0.12/transporters.html

Извиняюсь не вам отправил, хотел ниже человеку отправить noize


С телефона не так удобно использовать комментарии.....

Расскажите пожалуйста про шину обмена данными между микоосервисами и что используется в качестве протокола для обмена сообщениями

Чуть выше писал — http, redis, rabbit. Обмен по большей части в JSON или ProtoBuf. Наверняка что-то ещё есть, о чём я не знаю. Какой-то единой шины нет — может быть, это принесло бы нам счастье, но навскидку не могу сказать проблемы, которую она бы решила. Но тут как — возможно, я просто не осознаю, что она есть.

на ноде подобного пережить, к счастью, не пришлось, но пережил на рнр… когда из 6-ти летнего монолита начал вычленять в микросервисы, правда на golang…

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

даже не знаю чья это проблема… бизнеса или разработчика, палка о двух концах… Задач постоянно много, фьючеров постоянно много и некоторые нужны на вчера и т.п., потому что фьючер продается будто бы он уже есть, чтобы не городить то что не будет восстребованным…

до сих пор борюсь в дев.подходом компании, и если в течении 6-7 лет разрабатывал все в одиночку, огромный монолит для 3х ветвлений компании (селл, телемаркетинг, клиенты), то с созданием полноценного офиса разработчиков, привлечением новых кадров и т.п. — работы ни у кого меньше не стало, видимо дело в манере ведения бизнеса компании относительно разработки, и конечно — прибыль компании и рост доходности возвращаються болью разработчиков…

Но вообще, микросервисы, в результате, дали возможность группе разработчиков или одному концентрироваться лишь на конкретной части, в результате: где-то нода, где-то го, гдето даже си… а обмен — http или голый tcp, хотя и grpc был на основе protobuf — но стал выстрелом в ногу, даже в две, а потом и в голову…

крч, сложно это все, и таки да — идеального решения нет,
есть потребность бизнеса и конкуренты не будут ждать идеальный код перфекциониста. Сделали чтобы работало — и поскакали далее, а там разберемся)
есть потребность бизнеса и конкуренты не будут ждать идеальный код перфекциониста.

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


но стал выстрелом в ногу, даже в две, а потом и в голову

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

Я радуюсь, когда делаю на микросервисах что-то.
Использую активно molecular, так как посчитал что базовые фишки я буду делать долго и не факт, что сделаю лучше с первого раза. Из плюсов
Нормальное логирование.
Настройка метрик на методы.
Отправка метрик во все возможные сервисы.
Единый формат общения между сервисами.
Масштабируемость сервисов из коробки.
Тестировать код, мне стало проще. Микроскопы могут сами подыматся в случаи падения.
Наверное все не перечислить из плюсов.


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


Текущий монолит работает 700-1000 запросов сек.

Да, часто про molecular слышу хорошее от адекватных людей, но пока ни разу не трогал, о чём жалею.
Порекомендуете по нему хорошей литературы?

4.1 Можно делать гетерогенную инфраструктуру

Теперь мы можем писать код на cтеке, который оптимален для решения конкретной задачи. И рационально использовать любых хороших разработчиков, которые к нам попали

К сожалению в подавляющем большинстве случаев это скорее минус нежели плюс. Не надо забывать что код надо больше поддерживать в будущем и адаптировать, нежели написать один раз и забыть о нем. Не надо забывать что разработчики не только приходят в проект но и уходят из проекта. Очень печально видеть проекты в которых в команде из 5 человек ипользуют 3-4 стека технологий на микросервисах, потому что каждый новый пришедший человек считал что стек X отстой, стек Y крут, стек Y отстой, стек Z крут и т.д.


А потом когда человек уходит из проекта (по самым разным причинам) иногда всю работу приходиться полностью переписывать с нуля, так как или разраба не могут найти на этот стек или там все очень плохо написано на самом деле: у кого то качество покрытия логов реализовано плохо, у кого то вообще реализовано с SQL инъекциями. А код ревью не сильно спасет так как надо разбираться в коде в другом стеке.


Не надо разводить зоопарк просто потому что разработчик хороший, это обходиться потом очень и очень дорого.

Если у вас пять разработчиков в компании — то безусловно да. И мотивация "стек — отстой" — крайне плохая.


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

Как-то писал 3,5 года похожий проект. .net, 200 метров кода, одних адаптерам к разным hotel-booking api — под 100 штук, таблиц в нескольких БД под 1000.


Там был довольно мудро структурирован код — монорепа, но побитая по проектам, которые могли и как библиотеки подключаться, так и как отдельные сервисы с http api. Т.е. а были границы, через которые можно кидать только сериализуемые dto-шки. Хочешь как монолит запусти, хочешь каждый сервис на своей ноде.


Подход очень неплохо работал. Весь код открывать в ide, билдить, и запускать — не обязательно. Как разбить по нодам при деплое — можно разные варианты пробовать. Можно было логировать весть вход-выход любого модуля, и это было просто бомбически удобно для дебага.


При этом все плюсы монорепы и монолита были на месте.


.net и кучу библиотек вокруг — без проблем обновляли: с. net 1.1 дошли до 4.5. Т.е. начали даже без дженериков, а под конец уже фигачили лямбды. И сбилдить все 200 метров кода — тогда. (10 лет назад) было быстрее, чем сейчас 20 метров тайпскрипта с вебпаком. Так что проблема обновить lodash и сбилдить монорепу — это чисто нодовские приколы.

спасибо за доклад
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории