Почему мы перешли с Python на Go

от автора

Поставщик высоконагруженного API Stream перешёл с Python на Go, хотя этот язык знают немногие. Причинами решения делимся под катом к старту курса по Backend-разработке на Go.

1. Производительность

Go — быстрый. Очень и очень быстрый. Его производительность близка к Java [сегодня используется на финансовом рынке для высокочастотного трейдинга — торговли на большой скорости — прим. ред.] или C++. В нашем случае Go чаще всего оказывался в 40 раз быстрее Python. Вот небольшое сравнение производительности этих языков.

2. Производительность языка имеет значение

Для многих приложений язык программирования — это просто связующее звено между программой и базой данных. И, как правило, производительность самого языка особо не важна. Но Stream — это поставщик API, который обеспечивает работу платформы с каналами и чатами более 700 компаний и более 500 миллионов конечных пользователей. Годами мы пытались оптимизировать Cassandra, PostgreSQL, Redis и т. д., но однажды наступает момент, когда пределы возможностей языка достигнуты.

Python — отличный язык, но для таких задач, как сериализация/десериализация, ранжирование и агрегирование, он довольно медлительный. Мы регулярно сталкивались с проблемами производительности, когда Cassandra получала данные за 1 мс, а следующие 10 мс Python тратил на превращение их в объекты.

3. Продуктивность разработчика и мало возможностей для креатива

Взгляните на этот небольшой кусок кода Go из статьи How I Start Go. Это отличная обучающая статьи и хорошая отправная точка для изучения Go.

Если вы новичок в Go, то в этом небольшом фрагменте кода вас мало что удивит. В нём показаны несколько присвоений, структуры данных, указатели, форматирование и встроенная HTTP-библиотека. Когда я только начал заниматься программированием, мне нравилось использовать тонкости возможностей Python. Python позволяет импровизировать с кодом, который вы пишете. Например, вы можете:

  • использовать метаклассы для самостоятельной регистрации классов при инициализации кода;

  • менять местами True и False;

  • добавлять функции в список встроенных функций;

  • перегружать операторы через magic-методы;

  • использовать функции в качестве свойств через декоратор @property.

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

Конечно же, это «просто» зависит от вашего конкретного случая. Если вы хотите создать базовое  CRUD API, то я бы порекомендовал всё-таки Django + DRF или Rails.

4. Конкурентность и каналы

Go, как и любой язык, стремится к простоте. Для него не придумывали множество новых понятий. Целью было создать простой язык — быстрый и удобный в работе. Единственная область, где в Go появилось нечто новое, — это горутины и каналы. (Если быть на 100% точными, то понятие CSP появилось в 1977 году, так что данное новшество — скорее уж, новый подход к старой идее).

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

Среда выполнения в Go справляется со всеми сложностями. Реализация конкуренции через горутины и каналы позволяет с лёгкостью использовать доступные ядра ЦП и обрабатывать конкурентный ввод-вывод — и всё это делается без усложнения разработки. Для запуска функции в горутине требуется минимальный шаблонный код (если сравнивать с Python/Java). Вы просто добавляете к вызову функции ключевое слово go:

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

Перевод твита

Тук-тук

Состояние гонки

Кто там?

Несколько полезных ресурсов для знакомства с Go и каналами вы найдёте ниже.

5. Быстрая компиляция

Сейчас наш крупнейший микросервис, написанный на Go, компилируется за 4 секунды. Быстрая компиляция Go — это его главный плюс по сравнению с такими языками, как Java и C++, которые славятся своей медленной скоростью компиляции. Мне нравится сражаться на мечах, но ещё приятнее решать задачи, пока я помню, что должен делать мой код:

Перевод

— Эй, давайте работать!

— Компиляция!

— Ох, продолжайте.

6. Возможность создать команду

Начнём с очевидного: разработчиков Go гораздо меньше, чем для более старых языков (C++ и Java). По данным StackOverflow, 38% разработчиков знают Java, 19,3% разбираются в C++ и лишь 4,6% освоили Go. В данных на GitHub прослеживается схожая тенденция: Go используется чаще таких языков, как Erlang, Scala и Elixir, но его популярность ниже, чем у Java и C++.

К счастью, Go — очень простой и лёгкий в изучении язык. В нём есть нужные вам базовые функции и ничего более. Из новшеств можно выделить оператор defer и встроенное управление конкурентностью через горутины и каналы. Для приверженцев чистоты языка: Go далеко не первый реализовал эти концепции, но именно он сделал их популярными. Благодаря простоте Go любой разработчик на Python, Elixir, C++, Scala или Java, которого вы возьмёте в свою команду, разберётся в нём буквально за месяц.

Мы заметили, что, по сравнению с другими языками программирования, гораздо проще собрать команду разработчиков на Go. И если вы нанимаете сотрудников в таких конкурентных экосистемах, как Boulder and Amsterdam, то это весомое преимущество.

7. Прочная экосистема

Для таких команд, как наша (~20 человек), экосистема важна. Вы не сможете создать ценность для своих клиентов, если придётся заново изобретать всю функциональность с нуля. В Go есть отличная поддержка используемых нами инструментов. Надёжные библиотеки уже доступны для Redis, RabbitMQ, PostgreSQL, парсинга шаблонов, задач, выражений и RocksDB.

Экосистема Go в разы лучше, чем у таких новых языков, как Rust или Elixir. Разумеется, она не так хороша, как в Java, Python или Node, но это стабильная экосистема, а для многих базовых задач уже доступны качественные пакеты.

8. Gofmt, принудительное форматирование кода

Для начала, а что такое Gofmt? И нет, это не ругательство. Gofmt — это потрясающая утилита для командной строки; она встроена в компилятор Go специально для форматирования кода. В плане функциональности она очень похожа на autopep8 для Python.

Большинство из нас вообще-то не любит спорить о табах и пробелах, что бы ни показывали в сериале «Силиконовая долина». Форматирование должно быть единообразным, а сами его стандарты не особо важны. Gofmt избавляет от всех этих дискуссий, предлагая один официальный способ по форматированию кода.

9. gRPC и буферы протокола

Go предлагает первоклассную поддержку буферов протокола и gRPC. Оба эти инструмента прекрасно работают вместе для создания микросервисов, которые должны взаимодействовать через RPC. От вас всего лишь требуется написать манифест, в котором вы определяете, какие RPC-вызовы нужно делать и какие аргументы они принимают.

Затем из этого манифеста автоматически генерируются серверный и клиентский коды. Итоговый код получается быстрым, оставляет совсем небольшой сетевой отпечаток и крайне прост в работе. Из того же манифеста вы можете сгенерировать клиентский код для многих других языков, включая C++, Java, Python и Ruby. Так что оставьте в прошлом неоднозначные конечные точки REST для внутреннего трафика, когда вам каждый раз приходится прописывать почти один и тот же клиентский и серверный код.

1. Нехватка фреймворков

В Go нет какого-то одного главного фреймворка, как, например, Rails для Ruby, Django для Python или Laravel для PHP. Это предмет самых горячих споров в сообществе Go, поскольку многие считают, что вам вообще не нужны никакие фреймворки. В некоторых случаях я полностью с ними согласен. Но если кто-то захочет создать простой CRUD API, то гораздо удобнее сделать это на Django/DJRF, Rails Laravel или Phoenix.

Дополнение: в комментариях пишут, что есть несколько проектов, которые предоставляют фреймворк для Go. Основными фаворитами называют Revel, Iris, Echo, Macaron и Buffalo. Мы предпочли не использовать фреймворки в Stream. Но для многих новых проектов, которые нацелены на предоставление простого CRUD API, отсутствие основного фреймворка является серьёзным недочётом.

2. Обработка ошибок

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

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

3. Управление пакетами

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

Управление пакетами в Go нельзя назвать идеальным. Там по умолчанию отсутствует возможность задавать конкретную версию зависимости и создавать воспроизводимые сборки. Системы управления пакетами в Python, Node и Ruby гораздо лучше. Но с правильными инструментами управление пакетами в Go работает вполне прилично.

Для управления зависимостями вы можете использовать Dep — он позволяет указывать и закреплять версии. Помимо этого, мы используем инструмент с открытым кодом под названием VirtualGo, который упрощает работу с несколькими проектами, написанными на Go.

Python или Go

Обновление: с момента написания этой статьи разница в производительности Python и Go возросла. (Go стал быстрее, а Python остался тем же). Мы провели интересный эксперимент: взяли наш функционал  ранжирования каналов на Python и переписали его в Go. Взгляните на этот пример метода ранжирования:

Для его поддержки код на Python и на Go должен делать следующее:

  1. Разбирать выражение для оценки. В данном случае мы хотим превратить эту строку "simple_gauss(time)*popularity" в функцию, которая берёт активность в качестве входного значения и возвращает оценку на выходе.

  2. Создавать частично определённые функции на основе конфигурации JSON. Например, мы хотим, чтобы "simple_gauss" вызывала "decay_gauss" со шкалой в 5 дней, смещением в 1 день и коэффициентом убывания в 0,3.

  3. Разобрать конфигурацию "defaults", чтобы у вас был резервный вариант на случай, если какое-то поле в активности не будет определено.

  4. Воспользоваться функцией из шага № 1, чтобы присвоить баллы всем активностям в канале.

Разработка Python-версии кода ранжирования заняла примерно 3 дня. Сюда вошли написание кода, модульные тесты и документация. Затем около 2 недель ушло на оптимизацию кода. Одна из оптимизаций переводила выражение оценки (simple_gauss(time)*popularity) в абстрактное синтаксическое дерево.

Кроме того, мы реализовали логику кеширования, которая предварительно вычисляла оценку для определённого времени в будущем. А разработка Go-версии кода, наоборот, заняла не более 4 дней. Дальнейшей оптимизации для работы не потребовалось. Первая часть разработки шла быстрее на Python, но в итоге версия для Go оказалась менее трудоёмкой.

Дополнительным плюсом было и то, что код Go работал примерно в 40 раз быстрее, чем наш самый хорошо оптимизированный код на Python. Это лишь один из примеров роста производительности, с которой мы столкнулись, перейдя на Go. Но, конечно же, такое сравнение — из серии «сопоставлять несопоставимое»: 

  • код ранжирования был моим первым проектом на Go;

  • код Go писался после кода на Python, так что к тому времени я лучше понимал сценарий использования;

  • библиотека Go для разбора выражений была исключительно высокого качества.

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

Elixir или Go — заслуженное серебро

Ещё один опробованный нами язык — это Elixir. Он построен поверх виртуальной машины Erlang. Это удивительный язык; мы решили к нему присмотреться, поскольку у одного члена нашей команды имелся солидный опыт работы с Erlang. В своих примерах мы заметили, что исходная производительность Go выше.

Go и Elixir отлично справляются с тысячами параллельных запросов. Однако если присмотреться к производительности отдельного запроса, то для нас Go оказался в разы быстрее.

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

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

Заключение

Go — это высокопроизводительный язык с отличной поддержкой конкурентности. Он почти такой же быстрый, как C++ и Java. На разработку кода в Go уходит чуть больше времени, чем в Python или Ruby, но вы существенно экономите на оптимизации кода. У нас есть небольшая команда разработчиков в Stream, который поддерживает работу каналов и чатов для более 500 миллионов конечных пользователей.

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

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

Наш новый Chat API также полностью написан на Go. Если вы хотите узнать о языке больше, почитайте статьи из списка ниже. А если интересно познакомиться со Stream, начните с этого интерактивного урока.

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

А мы поможем прокачать ваши навыки или с самого начала освоить профессию, востребованную в любое время:

Выбрать другую востребованную профессию.


ссылка на оригинал статьи https://habr.com/ru/company/skillfactory/blog/669818/


Комментарии

Добавить комментарий

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