Деплой, базы данных и мониторинг: жизнь после перехода на Go
Спикер курса «Golang для инженеров», Team Lead & Backend Developer в «Ситимобил» Тигран Ханагян, рассказывает о том, как и почему произошел переход на Golang в онлайн-сервисе такси.
Материал подготовлен на основе вебинара Слёрма по Golang.
До перехода на Go в компании был большой монолит на PHP. Монолит все еще есть, но уже на порядок меньше — около 2 миллионов строк кода. На сегодняшний день многое вынесено в микросервисы.
Команда разработки встала перед проблемой: кодовая база выросла настолько, что поддерживать и вести параллельную разработку такого большого монолита становилось сложно. Каждый раз нужно было доставлять на сервер очень большое количество файлов.
Вводные данные
Монолит на PHP давал свои побочные эффекты в виде непростой инфраструктуры: помимо Nginx-ов, которые отвечают за балансировку нагрузки, разработчикам приходилось держать отдельный Nginx для общения с FPM. Нужно было также администрировать и обслуживать FPM. И только после FPM — работать непосредственно с кодом.
Был нюанс и при работе базой данных. В PHP по умолчанию коннекты к БД открываются синхронно в зависимости от того, сколько пришло пользователей. Формула проста: Х пользователей принимаются на вход — Х коннектов к БД открываются. Конечно, это можно оптимизировать, но для этого нужны лишние манипуляции и дополнительные настройки со стороны разработки.
Сбор метрик тоже приносил немало боли. В «Ситимобил» для метрик применяется Prometheus, у которого pull модель: с определенной периодичностью он входит в нужный сервис, чтобы собрать метрики. Этот сервис должен обеспечить все меры, чтобы метрики отдать. Здесь PHP накладывал ряд ограничений, которые также решались дополнительной настройкой или даже инфраструктурой: нужно было поднять прокси, например.
Процесс выкатки занимал неприлично много времени из-за большого количества кода, папок. Плюс необходимо делать статик-анализы и линтеры, чтобы минимизировать человеческую ошибку. А когда на прод выкатывался релиз с багом, нужно было уметь очень быстро откатываться на предыдущий релиз, чтобы не держать на проде забагованную версию. Для этого тоже приходилось производить дополнительные манипуляции.
Прибавьте к этому то, что разработчики в «Ситимобил» пока не применяют Kubernetes. Кроме того, все проблемы удваивались из-за размера монолита.
В итоге было принято абсолютно логичное решение распилить монолит на микросервисы, хотя на тот момент еще не было понимания, какой язык использовать.
Какими критериями руководствовались
В первую очередь — комьюнити языка. Если у языка большое и активное комьюнити, значит этот язык развивается, у него много библиотек, которые этим самым комьюнити поддерживаются.
Важно, чтобы команда разработки смогла быстро перейти на новый язык. Например, разработчики пишут на PHP, и в какой-то момент им говорят: «Мы переходим на язык Х, вам надо быстро научиться на нем писать». Для успешного перехода нужно, чтобы язык был прост в освоении.
При выборе языка обязательно стоит учитывать спрос на рынке труда. Это особенно важно для больших компаний, куда вовлекаются десятки и сотни IT-специалистов. При найме не должно быть трудностей;, а сейчас, например, оперативно найти условного Delphi разработчика будет сложно.
Команде нужен был компилируемый язык со строгой типизацией, потому что так можно было предотвратить внутренние дополнительные проблемы. Это необязательный критерий, но он стал бы большим плюсом.
Еще хотелось, чтобы тулинг разработки вокруг нового языка помогал легко отлаживать и дебажить сервисы. В огромном монолите очень сложно и долго искать ошибку, а это уже влияет на Time To Market и другие важные показатели. Разработка пошла бы куда веселее при быстрых инструментах debug-а и отладки.
Поскольку в «Ситимобил» PHP давал накладные расходы при работе с БД, новый язык должен был из коробки поддерживать решение, помогающее оптимизировать работу с базой данных.
Наконец, надо было как-то решить проблему с билдом и деплоем, чтобы увеличить скорость доставки кода до production-а, ускорить откаты на другие релизы. Ну и вообще, как-то упростить весь процесс в целом.
Процесс отбора претендентов
Для начала было принято решение проанализировать популярность разных языков с упором на выбор разработчиков: насколько им понравится тот или иной язык.
Это статистика на конец 2020 года.
Интересно получается. На первом месте, конечно же, Python. Это логично: на Python написана тонна проектов. Например, основная масса Data Scientist проектов делается на нем. Сфера применения этого языка, в принципе, очень широкая.
На втором месте JS. Здесь тоже понятно: фронтенд-разработка, дизайн, ui/ux. С недавних пор эта область начала стремительно развиваться, поэтому JS вместе с фронтенд-разработкой претерпел серьезные изменения и, можно сказать, вышел на другой уровень.
Барабанная дробь… На третьем месте — Go! Это довольно достойное место, потому что к JS подмешан еще и фронтенд. А Python не подходил, потому что не совсем решал список проблем разработки в компании.
Выбор был сделан в пользу Golang.
Golang больше всего используется в IT-сфере. На нем разрабатывается солидный процент веб-сервисов, но что интересно: на Go также пишутся различные утилиты для приложений, библиотеки и фреймворки, осуществляется много инфраструктурной разработки. БД тоже есть. Стоит вообще упоминать про Kubernetes? С некоторыми дополнительными инструментами можно даже вести фронтенд-разработку.
В дополнение к сфере IT язык активно используют в финансовом секторе и облачных вычислениях.
Golang — компилируемый язык со строгой типизацией. Таких языков есть достаточно много, поэтому нужно было смотреть, чем Go отличается от того же C++. Вот график со временем компиляции проекта.
Go достаточно быстрый при компиляции — даже быстрее, чем С++. В больших проектах, где разработка ведется на C++, разработчик запускает компиляцию и идет пить кофе в ожидании, пока проект соберется, чтобы что-то потестировать. В этом плане Golang сразу приятно удивляет: он быстро собирается и компилируется в бинарник из коробки, дополнительно делать ничего не нужно.
Помимо компиляции есть runtime. Перед вами график, который показывает скорость выполнения тех или иных алгоритмов.
Это открытый ресурс, который проводит серию стандартных тестирований, чтобы определить, как язык справляется с разными алгоритмами и структурами данных.
Сравнивались Python, Node js, Golang, C++ и Java. Сразу видно, что Python проигрывает по производительности. Остается С++, Java, Node js и Golang. Node js в некоторых местах гораздо слабее остальных языков.
Остается выбор между Golang, Java и С++. Последний, конечно же, быстрее всех, а у Java с Golang показатели плюс-минус одинаковые.
Почему не С++? Разработка на этом языке — трудоемкий процесс, плюс переехать с PHP на С++ очень дорого и долго. Писать на C++ круто, но когда необходимо переписать монолит на отдельные микросервисы, решение не самое лучшее.
Остались Java и Golang. С Java не все так просто: у этого языка есть виртуальные машины, которые требуют определенной инфраструктуры и ресурсов.
Что там с простотой
Go достаточно прост тем, что у него очень мало синтаксического сахара. Вот список ключевых слов, которые есть в этом языке.
Изучив этот небольшой набор слов, вы уже сможете писать программы на Go. Поэтому освоить синтаксис языка не составит большого труда.
Что Golang предоставляет из коробки? Выше уже было написано, что в PHP по стандарту количество клиентов равно количеству соединений в БД, поэтому каждый новый запрос открывает новое соединение в базу. В Go из коробки используется пул соединений.
Здесь можно контролировать количество соединений для входа в базу на уровне приложения, прямо в коде. Возможно даже держать несколько таких пулов, например: пул для мастер-базы, пул для slave-базы, отдельный пул для slave-а, но с менее требовательными к таймаутам настройками — медленный slave. Он часто используется для аналитических запросов, где можно настроить тайм-аут на 10–20 секунд.
Также можно использовать отдельные пулы на приоритезацию. Представьте: есть пул соединений, на котором висит важная бизнес-логика. Потерять этот пул равносильно катастрофе. Так же есть пул с менее критичной логикой. При проблемах с сетью можно пожертвовать этим пулом, чтобы основной пул продолжил работать. С инструментами, которые дает Golang из коробки, это очень легко и удобно делать. Работать с базой данных в Go — приятно.
Переходим к отладке и debugging-у. У Go есть большое комьюнити, масса разных инструментов и библиотек. Не нравятся стандартные инструменты, которые идут из коробки? Похожий инструмент, но с доработками или дополнительными аспектами, можно найти в комьюнити.
В отладке то же самое. Из коробки имеем достаточно большой спектр инструментов, который позволит профилировать код. Справа на картинке вы видите граф вызовов функций.
Такой граф строит профилировщик, который есть в языке. Что это дает? Можно оперативно найти какие-то бутылочные горлышки в программах и устранить проблему. Без труда определить, какое место в коде тормозит. Существуют различные форматы, в которых это можно делать, но главное — инструмент это позволяет.
Слева на картинке показан результат просмотра программы в таком инструменте, как трассировщик. Он может показать более подробную информацию, так как его квант времени детальнее, чем у профилировщика. И здесь уже можно посмотреть: в каких горутинах что выполняется, что тормозит, что ждет и т. д. Это тоже мощный инструмент, с помощью которого можно находить узкие места в программе.
Как go решает проблему билда и деплоя
В PHP было очень неудобно работать с множеством файлов и папок. Go дает возможность скомпилировать все в один бинарник, причем это легко сделать для различных систем.
Находясь в Windows, мы можем собрать бинарник для Linux и доставить на продакшн тот бинарник, который подходит для этой системы. В конечном счете получается, что выкатка кода в production — это просто доставка одного файла, одного бинарника. Что очень удобно, согласитесь.
Подводя итоги
Перейти на Go достаточно просто, потому что язык не так многословен в плане синтаксиса. Управляющих конструкций не так много, как в других языках.
Golang сильно упростил в онлайн-сервисе такси «Ситимобил» вопросы билда и доставки кода: все свелось единственному файлу. Теперь разработчики имеют дело с одним бинарником, который можно доставить с помощью докер образа. Не хотите с помощью докера? Никто не мешает просто собрать этот бинарник, каким-либо образом доставить его на сервер и просто запустить.
С метриками тоже все прекрасно складывается, не нужна никакая дополнительная инфраструктура: в Go сбор метрик происходит в отдельной горутине параллельно с выполнением наших программ. Метрики копятся в памяти, пока за ними не придут. Как только кто-то вызывает API-метод, сервис на Go достаточно вежливо предоставляет метрики. Здесь не нужно изобретать велосипед, и все происходит компактно, в рамках 1 сервиса.
Очень важно, что Go позволил серьезно оптимизировать работу с БД. При больших нагрузках страдает, в основном, именно база данных. Особенно при неоптимальных запросах к ней. Уметь оперативно среагировать на это в нужной ситуации — серьезное преимущество, потому что можно держать намного больше нагрузки на приложение.
La Fin (конец). Практическую часть вебинара смотрите на этом видео, тайминг: 37:18 — 59:00.
А если хотите еще глубже погрузиться в изучение Go:
Занимайте места на 3 потоке курса Golang для инженеров, который стартует с 25 июля. Разберём особенности работы с контейнерами, тестами, кастомными операторами и паттернами Kubernetes. К концу курса создадим систему, которая будет собирать состояние других сервисов, сохранять собранное состояние в базу данных и предоставлять WEB API для доступа к сохраненным данным
Обучение пройдёт в живом формате — будут онлайн-встречи со спикерами, обратная связь по домашним заданиям от ревьюеров и закрытый чат для участников.
Посмотреть программу и записаться: https://slurm.club/3QOx4tw