Почему мы перешли с 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, начните с этого интерактивного урока.

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

Изучение Go

  • https://learnxinyminutes.com/docs/go/

  • https://tour.golang.org/

  • http://howistart.org/posts/go/1/

  • https://getstream.io/blog/building-a-performant-api-using-go-and-cassandra/

  • https://www.amazon.com/gp/product/0134190440

  • Go Rocket Tutorial

Горутины

  • https://gobyexample.com/channels

  • https://tour.golang.org/concurrency/2

  • http://guzalexander.com/2013/12/06/golang-channels-tutorial.html

  • https://www.golang-book.com/books/intro/10

  • https://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html

  • Goroutines vs Green threads

Причины перехода на Go

  • https://movio.co/en/blog/migrate-Scala-to-Go/

  • https://hackernoon.com/why-i-love-golang-90085898b4f7

  • https://sendgrid.com/blog/convince-company-go-golang/

  • https://dave.cheney.net/2017/03/20/why-go

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

  • Профессия Backend-разработчик на Go

  • Профессия Data Scientist

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

Источник: https://habr.com/ru/company/skillfactory/blog/669818/


Интересные статьи

Интересные статьи

Всем же интересно про зарплаты? Давайте про зарплатыМеня зовут Алексей и я работаю в Райффайзенбанке техлидом. Последние 12 лет я нон-стоп нанимаю технических специалисто...
В 2020 году в топе популярных игр оказалась Animal Crossing: New Horizons от Nintendo, где надо полоть сорняки и торговать репкой. Кроме того, исходя из статистики обсужд...
Мы всегда хотим писать код быстро, но за это приходится платить. На обычных высокоуровневых гибких языках можно быстро разрабатывать программы, но после запуска они работают медленно. Например, ч...
В прошлой статье я рассказал о режиме ограничения доступа к аксессуарам в устройствах под управлением iOS. В статье я акцентировал внимание на особенности iOS, которая ставит безопасность данных ...
Целью данного проекта было: Изучение протокола DHCP при работе в сети IPv4 Изучение Python (немножко более чем с нуля ;) ) замена серверу DB2DHCP (мой форк), оригинал здесь, который со...