Привет! Я тимлид команды «Добро» в «Сравни.ру», мы занимаемся разработкой сервиса по подбору кредитных продуктов. 

Сервис, над которым мы работаем, помогает нашим клиентам подобрать кредитные продукты с высокой степенью одобрения. Для этого мы придумали алгоритм, который аккумулирует необходимый объем данных, обрабатывает их и подбирает кредитные продукты от банков, которые с высокой долей вероятности одобрят заявку конкретного пользователя. Нетипичное название команды («Добро») произошло от слова «одобрение». 

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

С чего всё начиналось

На момент принятия решения о переезде проект «Сравни.ру» существовал уже около семи лет. Сайт был написан на .NET и представлял собой один большой монолит. Как можно догадаться, чтобы выкатить очередное обновление, приходилось немного пошаманить. У нас было несколько админок, в которых нужно было время от времени перезагружать данные. Все данные собирались из одной базы, выполнялся экспорт, и они обновлялись пачкой. Это занимало кучу времени. Вместо этого мы хотели получить реал-таймовые админки (когда поправил раздел — и он моментально обновился на сайте).

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

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

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

Из технологий выбрали Node.js для серверной части, React — для фронтенда и MongoDB — в качестве базы данных, а для хостинга — Azure и Яндекс.Облако. Перевести хотели в облако, чтобы всё крутилось в Kubernetes. Это открывало дополнительную универсальную схему: так и команды, и DevOps-инженеры могли легко это всё поддерживать.

Как мы переезжали

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

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

Начали использовать MongoDB в качестве системы управления базами данных, но возникли проблемы. В части сервисов нужно было производить сложные выборки и вычисления. По философии и некоторым технических ограничениям MongoDB нам не очень подходила, так как приходилось делать «хитрые» агрегации на большом объеме данных. Например, считать процентные ставки. Это всё работало медленно, и мы поняли, что инструмент не подходит для таких задач. Сейчас MongoDB мы также используем, но PostgreSQL лучше подходит под наши задачи с точки зрения логики. Чтобы не грузить базу, часть расчетов мы вынесли либо на клиентскую, либо на серверную часть.

Что касается деплоя, то, как я сказал раньше, мы сделали несколько репозиториев под разные проекты. Например, репозитории под наш сервис по подбору кредитов выглядели так: credit_selection_frontend, credit_selection_service, credit_selection_backoffice.

Реализовали всё это через TeamCity. Заходя в TeamCity, любой разработчик или тестировщик может выкатить один сервис или сразу несколько. Выкатывается всё либо в тестовый, либо в продакшен Kubernetes. Это дает возможность изолированно тестировать функционал сразу в нескольких микросервисах. Добавляем в несколько микросервисов и смотрим, как это вместе работает и как будет выглядеть для пользователя. Как минимум это посмотрят тестировщики, а в некоторых случаях — продакт-оунеры. Грубо говоря, тестовые стенды создаются двумя кликами мыши через TeamCity. 

В конце концов мы перенесли всё в Kubernetes, это позволило легко создавать и деплоить новые сервисы. Сейчас у нас есть несколько нод Kubernetes для разных задач.

Проблемы и их решение

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

В процессе переезда запросы шли в монолит. В монолите был код, который эти запросы роутил либо в свои обработчики, либо на микросервисы, если запрашиваемый раздел был уже на новой архитектуре. Проблема была в том, что, например, когда мы выкатывали новый микросервис, могла произойти ошибка в таблице маршрутизации урлов. Это приводило к тому, что мы выкатывали сервис, а он не работал. 

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

В итоге решили разруливать запросы с помощью nginX, который служил единой точкой входа, а затем перекидывал запросы на монолит и микросервисы. Если раньше на этот процесс уходило от одного до нескольких часов, то теперь он стал занимать всего несколько минут. Такое решение помогло переезжать в более спокойном режиме.

Немного про тестирование и деплой

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

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

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

Для непрерывной интеграции и деплоя мы долгое время использовали TeamCity, сейчас потихоньку переезжаем на GitHub Actions.

Что в итоге?

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

Сейчас внедряем ML и тоже деплоим в Kubernetes. Но об этом расскажем в следующий раз. Буду рад ответить на комментарии и вопросы!

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


  1. CatalogLoader
    17.02.2022 20:16
    +5

    Хочется спросить про то как вам nodejs после .net ? ????


  1. Guzergus
    17.02.2022 20:33
    +3

    Интересуют некоторые детали.

    Из технологий выбрали Node.js для серверной части, React — для фронтенда и MongoDB — в качестве базы данных

    Какие причины переезда с .NET и почему именно Node? Рассматривал обе технологии, не заметил огромных преимуществ у ноды, которые бы обосновывали смену технологии после 7 лет опыта работы с другой (рассуждение применимо и к переезду Node -> .NET).

    Аналогичный вопрос про Mongo. Какие именно недостатки были у предыдущей базы данных и почему была выбрана Mongo? Используется ли шардирование? Репликация?
    Так же, не нашёл в статье, какая база изначально была у монолит решения. Postgres?


    1. Semenov Автор
      17.02.2022 21:33

      Не утверждаю, что технология X лучше технологии Y. Вот про мнолит vs микроскервисы готов поспорить :) Дотнет у нас используется и сейчас в целом ряде проектов, просто это уже не монолит как было раньше, а микросервисы. Про Монго — до этого использовался MS SQL, он не очень идеологически вписывался в наш новый опесорсный стек. Была выбрана Монга как простое решение, но смростом требований и фич оказалось, что она не очень подходит под наши новые задачи.


      1. Guzergus
        17.02.2022 21:53
        +6

        Вот про мнолит vs микроскервисы готов поспорить :)

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

        Ну, в тех проектах, где он перестал использоваться, в чём была причина?

        Выглядит как будто вы переехали на «крутые» штуки, особенно учитывая тот же выбор монго, хотя по факту ни у реляционных баз, ни у.нет нет никаких недостатков по сравнению с вашим предыдущим стеком.


      1. Matisumi
        18.02.2022 08:56
        +2

        То есть вы перешли с реляционной БД на no-sql только по идеологическим соображениям? Ведь есть же mysql, postgresql и тд.


  1. cross_join
    17.02.2022 21:09
    +15

    Для слоя БД описана типовая "история успеха":

    • Возьмем Монгу - это модно, стильно и молодежно

    • Уррра! Легко загрузили все данные. Но, блин, что-то с запросами совсем беда...

    • Переходим на Постгрес - это хоть и не молодежно, но модно и, типа, бесплатно

    • Ура! Запросы, наконец, стало удобно писать, язык человеческий. Но, блин, почему так медленно? Неужели Оракл и Микрософт не зря хотят денег за свои СУБД с оптимизаторами, вылизанными за десятки лет?

    • Нет, СУБД из Big-3 нам не потянуть, придется перенести часть вычислений из запросов и хранимых процедур в код служб...

    • Продолжение следует...


    1. Tzimie
      17.02.2022 21:45

      Именно


    1. Guzergus
      17.02.2022 22:04
      +4

      Уррра! Легко загрузили все данные.

      Ещё смешнее, что некоторые грузят данные в режиме j:false или вообще w:0, что значит, что они по сути не durable и могут потеряться даже после того, как клиент получил подтверждение об успешной записи. Более того, до версии 5 подобная настройка была дефолтной для монго.

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


      1. cross_join
        17.02.2022 22:39

        Если вставлять данные в РСУБД правильным образом, то это не будет медленнее даже не настроенной на durable Монги.


        1. Guzergus
          17.02.2022 23:31

          Расскажите, пожалуйста, подробнее, что значит правильный образ. С трудом представляю себе ситуацию, когда пересылка данных по сети и мгновенный ack может быть медленнее, чем пересылка + запись на диск.

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

          Можно, конечно, реляционную субд привести к поведению монги — in-memory tables, delayed transaction durability, но мне кажется вы не об этом.


          1. cross_join
            17.02.2022 23:45

            Давайте сравнивать яблоки с яблоками, а не с теннисными мячиками. В вашем примере можно замерить передачу данных по сети и снова окажется, что она быстрее у РСУБД за счет в разы более компактного табличного формата. Сравнивать время выполнения BULK INSERT корректно не с получением сигнала о передаче пакета (это легко изобразить с SqlBulkCopy.WriteToServerAsync), а с реальной вставкой данных. То, что она не гарантирована - следующий уровень игры.


            1. Guzergus
              18.02.2022 02:27

              Давайте более предметно.

              Вы утверждаете, что вставляя данные в субд «правильный образом» можно добиться скорости не меньше, чем с write concern 0 для монго. Распишите, пожалуйста, что вы понимаете под «правильный образом» и о каких данных мы говорим. Я с радостью проверю ваш способ на postgresql/ms sql, если он для них работает.


              1. cross_join
                18.02.2022 02:38

                Если все-таки желаете сравнивать теплое с мягким, проверяйте (с поправкой на объемы 2008 года)

                Using SSIS to load 1TB data into SQL Server in 30 mins, with simplified settings


                1. vagon333
                  18.02.2022 08:52
                  +1

                  Таки да - sql server bulk insert позволяет заливать данные не перестраивая индексы и закрывая транзакцию в момент окончания импорта.

                  Этот подход и скорость вставки позволила нам отстоять производительность RDBMS при импорте больших данных, когда бодались с большими клиентами.


                  1. cross_join
                    18.02.2022 14:59

                    Если условия позволяют использовать minimally logged (пустая таблица с ключом, индексы строим позже, заливка в порядке кластерного ключа), то даже простой INSERT SELECT отрабатывает так же быстро, как BULK.


    1. SkryabinD
      17.02.2022 22:08
      +3

      PostgreSQL не такой уж и медленный, нормальная альтернатива Oracle, если не нужно чего-то специфического.


    1. Naglec
      18.02.2022 00:29
      +4

      Можно подробнее на тему медленного PostgreSQL и быстрого ms sql/oracle


  1. tuxi
    17.02.2022 21:36

    Если не секрет, какой размер кода у бекенда? Меньше 1 млн строк или больше?


    1. Semenov Автор
      17.02.2022 22:06

      Если честно, очень сложно так навскидку оценить в строках. У нас очень много команд (~30), и репозиториев (~500). Каких-то готовых даже приблизительных оценок у меня на данный момент нет.


  1. abutorin
    17.02.2022 21:40

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

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

    У вас разные репозитории для продакшена и "основной" разработки?


    1. Semenov Автор
      17.02.2022 22:10

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


  1. SkryabinD
    17.02.2022 22:10

    В TeamCity нельзя разграничить доступ по веткам - если дать билдить программисту, то он не только в тест, но и на прод может запустить билд. У вас это как-то решалось?


  1. AndreyTS
    18.02.2022 00:23

    Спасибо за статью, рад что проект живёт. Я в свое время приложил руку к разработке этого монолита :-)

    Хотел спросить, тк не очень понял из статьи, на какие всё-таки конкретно микросервисы вы порезали его?


  1. Stas911
    19.02.2022 05:04

    Если вы из команды "Добро", то можно поинтресоваться, чем у вас занята команда "Зло"? Подбором коллекторов? :)


  1. ivanych
    20.02.2022 19:24

    Сколько людей за сколько времени это сделали?