9 лет в монолите на Node.JS

    монолит от https://reneaigner.deviantart.com


    Неделю назад я выступал на митапе по Node.JS, и многим обещал выложить запись выступления. Уже потом я понял, что мне не удалось вместить в регламентированные полчаса некоторые интересные факты. Да и сам я больше люблю читать, а не смотреть и слушать, поэтому решил выложить выступление в формате статьи. Впрочем, видео тоже будет в конце поста в разделе ссылок.


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


    • Во-первых, нашему монолиту уже 9 лет.
    • Во-вторых, всю жизнь он провёл под хайлоадом (сейчас это 23 млн запросов в час).
    • А в NaN-ых, мы пишем наш монолит на Node.JS, который за эти 9 лет изменился до неузнаваемости. Да, мы начинали писать на ноде в 2010, безумству храбрых поём мы песню!

    Так что всякой специфики и реального опыта у нас довольно много. Интересно? Поехали!


    Дисклеймер раз


    Данная презентация отражает лишь частное мнение ее автора. Оно может совпадать с позицией компании «OneTwoTrip», а может и не совпадать. Тут уж как повезет. Я работаю техлидом одной из команд компании и не претендую на объективность или выражение чьего-то мнения кроме своего.

    Дисклеймер два


    Данная статья описывает исторические события, и на текущий момент всё совсем не так, так что не пугайтесь.

    0. Как же так вышло


    Тренд запроса слова "microservice" в google:

    Всё очень просто — девять лет назад никто и не знал про микросервисы. Так что начали мы писать, как и все — в монолите.


    1. Боль в монолите


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


    1.1 Обновление связных компонентов



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


    1.2 Миграция на новые технологии


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


    Многие говорят про преимущество монолита, которое состоит в том, что любая правка — это атомарный коммит. Умалчивают эти люди об одном — эта правка никогда не будет сделана.


    Знаете старую шутку про семантическое версионирование?


    the real semantics of semantic versioning:

    major = a breaking change
    minor = a minor breaking change
    patch = a little-bitty breaking change

    А теперь представьте, что в вашем коде почти наверняка всплывёт любой little-bitty breaking change. Нет, жить с этим можно, и мы таки периодически собирались с силами и мигрировали, но это правда было очень тяжело. Очень.


    1.3 Релизы


    Здесь надо сказать о некоторой специфике нашего продукта. У нас есть огромное количество внешних интеграций, и различных веток бизнес логики, которые всплывают по отдельности довольно редко. Я очень завидую продуктам, которые фактически выполняют все ветви своего кода за 10 минут в продакшне, но у нас не тот случай. Путём проб и ошибок, мы нашли для себя оптимальный релизный цикл, который минимизировал количество ошибок, которые дойдут до конечных пользователей:


    1. релиз собирается и за полдня проходит интеграционные тесты
    2. дальше он день лежит под внимательным присмотром на стейдже (для 10% пользователей)
    3. затем лежит ещё день на продакшне под ещё более внимательным присмотром.
    4. И только после этого мы даём ему зелёный свет в мастер.

    Так как мы любим наших коллег и не релизим по пятницам, то в итоге это означает, что релиз уходит в мастер примерно 1.5-2 раза в неделю. Что приводит к тому, что в релизе может быть по 60 задач и больше. такое количество вызывает мердж конфликты, внезапные синергетические эффекты, полную загруженность QA на разборе логов, и прочие печали. В общем, очень тяжело нам было релизить монолит.


    1.4 Просто очень много кода


    Казалось бы, количество кода не должно иметь принципиального значения. Но… На самом деле нет. В реальном мире это:


    • Более высокий порог вхождения
    • Огромные артефакты сборки на каждую задачу
    • Долгие CI процессы, включая интеграционные тесты, юнит тесты, и даже линтинг кода
    • Медленная работа IDE (на заре развития Jetbrains мы не раз шокировали их своими логами)
    • Сложный контекстный поиск (не забывайте, у нас нет статической типизации)
    • Сложность поиска и удаления неиспользуемого кода

    1.5 Отсутствуют владельцы кода


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


    1.6 Сложность отладки


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


    1.7 Один стек


    С одной стороны, хорошо, когда все разработчики "говорят" на одном языке. В случае JS получается, что даже Backend с Frontend разработчиками понимают друг друга. Но...


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

    1.8 Много команд с разным представлением о счастье



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


    2. Плюсы монолита


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


    2.1 Простота развёртывания


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


    2.2 Нет оверхеда на передачу данных


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


    2.2 Одна сборка


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


    3. Мнимые плюсы монолита


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


    3.1 Код — это и есть документация


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


    3.2 Нет разных версий библиотек, сервисов и API. Нет разных репозиториев.


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


    3.3 Проще мониторинг


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


    3.4 Проще соблюдать единые стандарты


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


    3.5 Меньше вероятность дублирования кода


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


    4. Плюсы микросервисов


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


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


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


    4.2 Можно делать много частых релизов


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


    4.3 Проще делать независимые тесты


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


    4.4 Легче внедрять и тестировать новые фичи


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


    4.5 Можно обновлять что угодно


    Можно обновлять версию движка, библиотек, да чего угодно! В рамках небольшого сервиса, найти и поправить все breaking changes — дело минут. А не недель, как было раньше.


    4.6 А можно не обновлять


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


    5 Минусы микросервисов


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


    5.1 Нужна шина для обмена данными и внятное логирование.


    Взаимодействие сервисов по HTTP это классическая модель, и в целом даже рабочая, при условии, что между ними есть логирующие и балансирующие прослойки. Но лучше иметь более внятную шину. Кроме того, следует думать о том, как собирать и объединять между собой логи — иначе у вас на руках будет просто каша.


    5.2 Нужно следить за тем, что делают разработчики


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


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


    Почти любой разработчик может так или иначе развернуть монолит и выкладывать его релизы (например, по FTP или SSH, видел я такое). Но с микросервисами появляются всякие централизованные сервисы сбора логов, метрик, дашборды, шефы для управления конфигами, волты, дженкинс, и прочее добро, без которого в целом жить можно — но плохо и непонятно зачем. Так что для управления микросервисами нужно иметь хорошую DevOps команду.


    5.4 Можно попытаться словить хайп и выстрелить себе в ногу.


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


    6 Хаки в монолите


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


    6.1 Линтинг


    Внедрение линтера в монолите — не такая простая задача, как кажется на первый взгляд. Конечно, можно сделать строгие правила, добавить конфиг, и… Ничего не изменится, все просто выключат линтер, потому что половина кода станет красным.


    Для постепенного внедрения линтинга мы написали простую надстройку над eslint — slowlint, который позволяет сделать одну простую вещь — содержать список временно игнорируемых файлов. В результате:


    • Весь некорректный код подсвечивается в IDE
    • Новые файлы создаются по правилам линтинга, иначе CI их не пропустит
    • Старые постепенно правятся и уходят из исключений

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


    6.2 Доработки юнит тестов


    Когда-то юнит тесты выполнялись у нас по три минуты. Разработчики не хотели ждать столько времени, так что всё проверялось только в CI на сервере. Через некоторое время разработчик узнавал, что тесты упали, чертыхался, открывал ветку, возвращался к коду… В общем, страдал. Что мы с этим сделали:


    1. Для начала начали запускать тесты многопоточно. У яндекса есть вариант многопоточной mocha, но у нас он не взлетел, так что сами писали простую обёртку. Тесты стали выполняться в полтора раза быстрее.
    2. Затем мы переехали с 0.12 ноды на 8ую (да, процесс сам по себе тянет на отдельный доклад). Принципиального выигрыша в производительности на продакшне это, как ни странно, не дало, но тесты стали выполняться на 20% быстрее.
    3. А дальше мы таки сели отлаживать тесты и оптимизировать их по отдельности. Что дало наибольший прирост в скорости.

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


    6.3 Облегчение веса артефакта


    Артефакт монолита со временем стал занимать 400 мегабайт. С учётом того, что он создаётся на каждый коммит, суммарно объёмы получались довольно большие. С этим нам помог модуль diarrhea, форк модуля modclean. Мы удаляли из артефакта юнит тесты и чистили его от различного мусора вроде ридми файлов, тестов внутри пакетов, и так далее. Выигрыш составил порядка 30% от веса!


    6.4 Кэширование зависимостей


    Когда-то установка зависимостей при помощи npm занимала столько времени, что можно было не только попить кофе, но и, например, испечь пиццу. Поэтому сначала мы пользовались модулем npm-cache, который форкали и немного допиливали. Он позволял сохранять зависимости на общем сетевом диске, с которого его потом брали все остальные билды.


    Потом мы задумались о воспроизводимости сборок. Когда у тебя монолит, то смена транзитивных зависимостей — бич божий. Учитывая то, что мы тогда сильно отставали по версии движка, смена какой-нибудь зависимости пятого уровня запросто ломала нам всю сборку. Поэтому мы начали использовать npm-shrinkwrap. С ним было уже проще, хотя мерджить его изменения — удовольствие для сильных духом.


    А дальше наконец появился package-lock и прекрасная команда npm ci — которая выполнялась с лишь чуть меньшей скоростью, чем установка зависимостей с файлового кеша. Поэтому мы начали пользоваться только ей, и перестали хранить сборки зависимостей. В этот день я принёс на работу несколько коробок пончиков.


    6.5 Распределение очерёдности релизов.


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


    6.6 Удаление мёртвого кода


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


    7 Распил монолита


    Почему-то многие считают, что для того, чтобы перейти на микросервисы, надо забросить свой монолит, написать рядом с нуля кучу микросервисов, разом всё это запустить — и будет счастье. Но эта модель… Хмм… Чревата тем, что вы ничего не сделаете, и только потратите кучу времени и денег на написание кода, который придётся выкинуть.


    Предлагаю другой вариант, который мне кажется более рабочим, и который был реализован у нас:


    1. Начинаем писать новые сервисы в микросервисах. Обкатываем технологию, прыгаем по граблям, понимаем, хотим ли вообще это делать.
    2. Выделяем код в модули, библиотеки, или что у вас там используется.
    3. Выделяем из монолита сервисы.
    4. Выделяем из сервисов микросервисы. Без спешки и по одному.

    8 И напоследок


    картинка взята у https://fvl1-01.livejournal.com/


    Под конец я решил оставить самое главное.


    Помните:


    • Вы не Google
    • Вы не Microsoft
    • Вы не Facebook
    • Вы не Yandex
    • Вы не Netflix
    • Вы не OneTwoTrip

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


    Полезные ссылки:


    Поддержать автора
    Поделиться публикацией

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

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

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

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


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


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


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

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


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


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


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

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


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

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

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

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

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

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

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


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

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


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

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


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

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


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

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


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

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


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

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


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


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

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

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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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

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

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


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

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


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

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


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

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


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

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

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

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


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

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


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

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


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

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


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

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

                        +1

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


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

                          +1

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

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

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

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

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

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


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

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


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

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


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

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

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

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

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

                                  +1

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


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

                                    0

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


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

                                  0

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

                                    0

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

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

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

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

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

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

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

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


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

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

                                      0

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


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


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

                                        0

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

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

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

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


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


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

                                          +1

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


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

                                          0

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


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


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


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


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

                                            0
                                            спасибо за доклад

                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                            Самое читаемое