Pourquoi nous sommes passés de Python à Go

Mise à jour le 14 mai 2019 pour mieux refléter les améliorations apportées à Go au cours des deux dernières années (gestion des paquets, meilleures performances, temps de compilation plus rapides et écosystème plus mature) Passer à un nouveau langage est toujours une étape importante, surtout lorsque seul un des membres de votre équipe a une expérience préalable de ce langage. Au début de cette année, nous avons changé le langage de programmation principal de Stream, passant de Python à Go. Ce billet explique certaines des raisons pour lesquelles nous avons décidé de laisser Python derrière nous et de passer à Go. Merci à Ren Sakamoto pour la traduction en japonais de Why we switched from Python to Go, なぜ私達は Python から Go に移行したのか.

Raison 1 – Performance

Go est rapide ! Le go est extrêmement rapide. Les performances sont similaires à celles de Java ou de C++. Pour notre cas d’utilisation, Go est typiquement 40 fois plus rapide que Python. Voici un petit jeu de benchmark comparant Go vs Python.

Raison 2 – Les performances du langage comptent

Pour de nombreuses applications, le langage de programmation est simplement la colle entre l’appli et la base de données. Les performances du langage lui-même n’ont généralement pas beaucoup d’importance. Stream, cependant, est un fournisseur d’API alimentant une plateforme de flux et de chat pour 700 entreprises et plus de 500 millions d’utilisateurs finaux. Nous optimisons Cassandra, PostgreSQL, Redis, etc. depuis des années, mais nous finissons par atteindre les limites du langage que nous utilisons. Python est un excellent langage, mais ses performances sont plutôt lentes pour les cas d’utilisation tels que la sérialisation/désérialisation, le classement et l’agrégation. Nous avons fréquemment rencontré des problèmes de performance où Cassandra prenait 1ms pour récupérer les données et Python passait les 10ms suivantes à les transformer en objets.

Raison 3 – Productivité des développeurs &Ne pas être trop créatif

Regardez ce petit extrait de code Go issu du tutoriel How I Start Go. (C’est un excellent tutoriel et un bon point de départ pour apprendre un peu de Go.)

Si vous êtes nouveau en Go, il n’y a pas grand-chose qui vous surprendra à la lecture de ce petit bout de code. Il met en avant les affectations multiples, les structures de données, les pointeurs, le formatage et une bibliothèque HTTP intégrée. Lorsque j’ai commencé à programmer, j’ai toujours aimé utiliser les fonctionnalités plus avancées de Python. Python vous permet d’être assez créatif avec le code que vous écrivez. Par exemple, vous pouvez :

  • Utiliser des MetaClasses pour auto-enregistrer des classes lors de l’initialisation du code
  • Echanger les Vrais et les Faux
  • Ajouter des fonctions à la liste des fonctions intégrées
  • Surcharger des opérateurs via des méthodes magiques
  • Utiliser des fonctions comme propriétés via le décorateur @property

Ces fonctionnalités sont amusantes pour jouer avec mais, comme la plupart des programmeurs en conviendront, elles rendent souvent le code plus difficile à comprendre lorsqu’on lit le travail de quelqu’un d’autre. Go vous oblige à vous en tenir aux principes de base. Il est donc très facile de lire le code de n’importe qui et de comprendre immédiatement ce qui se passe. Remarque : la « facilité » dépend bien sûr de votre cas d’utilisation. Si vous voulez créer une API CRUD de base, je recommanderais toujours Django + DRF, ou Rails.

Raison 4 – Concurrence &Canaux

En tant que langage, Go essaie de garder les choses simples. Il n’introduit pas beaucoup de nouveaux concepts. L’accent est mis sur la création d’un langage simple qui est incroyablement rapide et facile à travailler. Le seul domaine dans lequel il innove est celui des goroutines et des canaux. (Pour être 100% correct, le concept de CSP a commencé en 1977, donc cette innovation est plus une nouvelle approche d’une vieille idée). Les goroutines sont l’approche légère de Go pour le threading, et les canaux sont le moyen préféré pour communiquer entre goroutines. Les goroutines sont très bon marché à créer et ne prennent que quelques Ko de mémoire supplémentaire. Comme les goroutines sont si légères, il est possible d’en avoir des centaines, voire des milliers, qui tournent en même temps. Vous pouvez communiquer entre les goroutines en utilisant des canaux. Le runtime Go gère toute la complexité. Grâce aux goroutines et à l’approche de la concurrence basée sur les canaux, il est très facile d’utiliser tous les cœurs de processeur disponibles et de gérer les entrées-sorties concurrentes, sans compliquer le développement. Par rapport à Python/Java, l’exécution d’une fonction sur une goroutine nécessite un minimum de code passe-partout. Il suffit de faire précéder l’appel de fonction du mot clé « go »:

https://tour.golang.org/concurrency/1 L’approche de Go en matière de concurrence est très facile à travailler. C’est une approche intéressante par rapport à Node où le développeur doit faire très attention à la façon dont le code asynchrone est géré. Un autre aspect intéressant de la concurrence dans Go est le détecteur de course. Cela permet de déterminer facilement s’il y a des conditions de course dans votre code asynchrone.

Toc toc Condition de course Qui est là ?

– I Am Devloper (@iamdevloper) November 11, 2013

Voici quelques bonnes ressources pour démarrer avec Go et les canaux :

  • 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

Raison 5 – Temps de compilation rapide

Notre plus grand microservice écrit en Go prend actuellement 4 secondes à compiler. Les temps de compilation rapides de Go sont un gain de productivité majeur par rapport à des langages comme Java et C++ qui sont célèbres pour leur lenteur de compilation. J’aime le combat à l’épée, mais c’est encore plus agréable de faire les choses pendant que je me souviens encore de ce que le code est censé faire :

Raison 6 – La capacité à construire une équipe

D’abord, commençons par l’évidence : il n’y a pas autant de développeurs Go par rapport aux anciens langages comme C++ et Java. Selon StackOverflow, 38% des développeurs connaissent Java, 19,3% connaissent C++ et seulement 4,6% connaissent Go. Les données de GitHub montrent une tendance similaire : Go est plus largement utilisé que des langages tels que Erlang, Scala et Elixir, mais moins populaire que Java et C++. Heureusement, le go est un langage très simple et facile à apprendre. Il fournit les fonctionnalités de base dont vous avez besoin et rien d’autre. Les nouveaux concepts qu’il introduit sont l’instruction « defer » et la gestion intégrée de la concurrence avec les « routines go » et les canaux. (Pour les puristes : Go n’est pas le premier langage à implémenter ces concepts, juste le premier à les rendre populaires). Tout développeur Python, Elixir, C++, Scala ou Java qui rejoint une équipe peut être efficace en Go en un mois grâce à sa simplicité. Nous avons constaté qu’il est plus facile de constituer une équipe de développeurs Go que de nombreux autres langages. Si vous embauchez des personnes dans des écosystèmes compétitifs comme Boulder et Amsterdam, c’est un avantage important.

Raison 7 – Écosystème fort

Pour une équipe de notre taille (~20 personnes), l’écosystème compte. Vous ne pouvez tout simplement pas créer de la valeur pour vos clients si vous devez réinventer chaque petite pièce de fonctionnalité. Go offre un excellent support pour les outils que nous utilisons. Des bibliothèques solides étaient déjà disponibles pour Redis, RabbitMQ, PostgreSQL, Template parsing, Task scheduling, Expression parsing et RocksDB. L’écosystème de Go est un atout majeur par rapport à d’autres langages plus récents comme Rust ou Elixir. Bien sûr, il n’est pas aussi bon que des langages comme Java, Python ou Node, mais il est solide et pour de nombreux besoins de base, vous trouverez des paquets de haute qualité déjà disponibles.

Raison 8 – Gofmt, formatage de code forcé

Commençons par ce qu’est Gofmt ? Et non, ce n’est pas un gros mot. Gofmt est un utilitaire de ligne de commande génial, intégré au compilateur Go pour formater votre code. En termes de fonctionnalités, il est très similaire à autopep8 de Python. Bien que l’émission Silicon Valley dépeigne le contraire, la plupart d’entre nous n’aiment pas vraiment se disputer sur les tabulations ou les espaces. Il est important que le formatage soit cohérent, mais la norme de formatage réelle n’a pas beaucoup d’importance. Gofmt évite toutes ces discussions en ayant une seule façon officielle de formater votre code.

Raison 9 – gRPC et tampons de protocole

Go a un support de première classe pour les tampons de protocole et gRPC. Ces deux outils fonctionnent très bien ensemble pour construire des microservices qui doivent communiquer via RPC. Il suffit d’écrire un manifeste où vous définissez les appels RPC qui peuvent être effectués et les arguments qu’ils prennent. Le code du serveur et du client est ensuite automatiquement généré à partir de ce manifeste. Le code qui en résulte est à la fois rapide, a une très faible empreinte réseau et est facile à utiliser. À partir du même manifeste, vous pouvez générer du code client pour de nombreux langages différents, tels que C++, Java, Python et Ruby. Ainsi, fini les points de terminaison REST ambigus pour le trafic interne, pour lesquels vous devez écrire presque le même code client et serveur à chaque fois. .

Inconvénient 1 – Manque de frameworks

Go n’a pas de framework unique dominant comme Rails pour Ruby, Django pour Python ou Laravel pour PHP. C’est un sujet de débat animé au sein de la communauté Go, car beaucoup de gens préconisent de ne pas utiliser de framework pour commencer. Je suis tout à fait d’accord que c’est vrai pour certains cas d’utilisation. Cependant, si quelqu’un veut construire une simple API CRUD, il aura beaucoup plus de facilité avec Django/DJRF, Rails Laravel ou Phoenix. Mise à jour : comme les commentaires l’ont souligné, il existe plusieurs projets qui fournissent un framework pour Go. Revel, Iris, Echo, Macaron et Buffalo semblent être les principaux prétendants. Pour le cas d’utilisation de Stream, nous préférons ne pas utiliser de framework. Cependant, pour de nombreux nouveaux projets qui cherchent à fournir une API CRUD simple, l’absence d’un framework dominant sera un sérieux désavantage.

Désavantage 2 – Gestion des erreurs

Go gère les erreurs en renvoyant simplement une erreur depuis une fonction et en attendant que votre code d’appel gère l’erreur (ou la renvoie en haut de la pile d’appel). Bien que cette approche fonctionne, il est facile de perdre la portée de ce qui s’est mal passé pour s’assurer que vous pouvez fournir une erreur significative à vos utilisateurs. Le package errors résout ce problème en vous permettant d’ajouter un contexte et une trace de pile à vos erreurs. Un autre problème est qu’il est facile d’oublier de gérer une erreur par accident. Les outils d’analyse statique comme errcheck et megacheck sont pratiques pour éviter de faire ces erreurs. Bien que ces solutions de contournement fonctionnent bien, elles ne semblent pas tout à fait appropriées. Vous vous attendriez à ce que la gestion correcte des erreurs soit prise en charge par le langage.

Inconvénient 3 – Gestion des paquets

Mise à jour : la gestion des paquets de Go a parcouru un long chemin depuis que ce post a été écrit. Les modules Go sont une solution efficace, le seul problème que j’ai vu avec eux est qu’ils cassent certains outils d’analyse statique comme errcheck. Voici un tutoriel pour apprendre à utiliser Go en utilisant les modules Go. La gestion des paquets de Go est loin d’être parfaite. Par défaut, il n’est pas possible de spécifier une version spécifique d’une dépendance et il n’y a aucun moyen de créer des constructions reproductibles. Python, Node et Ruby ont tous de meilleurs systèmes de gestion des paquets. Cependant, avec les bons outils, la gestion des paquets de Go fonctionne assez bien. Vous pouvez utiliser Dep pour gérer vos dépendances et permettre de spécifier et d’épingler des versions. En dehors de cela, nous avons contribué à un outil open-source appelé VirtualGo qui facilite le travail sur plusieurs projets écrits en Go.

Python vs Go

Mise à jour : La différence de performance entre Python et Go a augmenté depuis que ce post a été écrit. (Go est devenu plus rapide et Python non) Une expérience intéressante que nous avons menée a été de prendre notre fonctionnalité de flux classé en Python et de la réécrire en Go. Jetez un coup d’œil à cet exemple de méthode de classement :

Le code Python et le code Go doivent tous deux faire ce qui suit pour prendre en charge cette méthode de classement :

  1. Parler l’expression pour le score. Dans ce cas, nous voulons transformer cette chaîne « simple_gauss(time)*popularité » en une fonction qui prend une activité en entrée et renvoie un score en sortie.
  2. Créer des fonctions partielles basées sur la configuration JSON. Par exemple, nous voulons que « simple_gauss » appelle « decay_gauss » avec une échelle de 5 jours, un décalage de 1 jour et un facteur de décroissance de 0,3.
  3. Parler de la configuration « defaults » pour avoir une solution de repli si un certain champ n’est pas défini sur une activité.
  4. Utiliser la fonction de l’étape 1 pour noter toutes les activités dans le flux.

Développer la version Python du code de classement a pris environ 3 jours. Cela comprend l’écriture du code, les tests unitaires et la documentation. Ensuite, nous avons passé environ 2 semaines à optimiser le code. L’une des optimisations consistait à traduire l’expression du score (simple_gauss(time)*popularité) en un arbre syntaxique abstrait. Nous avons également implémenté une logique de cache qui pré-calcule le score pour certains moments dans le futur. En revanche, le développement de la version Go de ce code a pris environ 4 jours. Les performances n’ont pas nécessité d’optimisation supplémentaire. Ainsi, si le développement initial a été plus rapide en Python, la version Go a finalement demandé beaucoup moins de travail à notre équipe. En outre, le code Go était environ 40 fois plus rapide que notre code Python hautement optimisé. Il ne s’agit là que d’un seul exemple des gains de performance que nous avons enregistrés en passant à Go. Il s’agit, bien sûr, de comparer des pommes à des oranges:

  • Le code de classement était mon premier projet en Go
  • Le code Go a été construit après le code Python, de sorte que le cas d’utilisation était mieux compris
  • La bibliothèque Go pour l’analyse syntaxique des expressions était d’une qualité exceptionnelle

Votre kilométrage variera. Certains autres composants de notre système ont pris beaucoup plus de temps à construire en Go par rapport à Python. En tant que tendance générale, nous constatons que le développement du code Go demande légèrement plus d’efforts. Cependant, nous passons beaucoup moins de temps à optimiser le code pour les performances.

Elixir vs Go – The Runner Up

Un autre langage que nous avons évalué est Elixir. Elixir est construit au-dessus de la machine virtuelle Erlang. C’est un langage fascinant et nous l’avons considéré car l’un des membres de notre équipe a une tonne d’expérience avec Erlang. Pour nos cas d’utilisation, nous avons remarqué que les performances brutes de Go sont bien meilleures. Go et Elixir sont tous deux capables de traiter des milliers de requêtes simultanées. Cependant, si vous regardez les performances des requêtes individuelles, Go est nettement plus rapide pour notre cas d’utilisation. L’écosystème est une autre raison pour laquelle nous avons choisi Go plutôt qu’Elixir. Pour les composants dont nous avions besoin, Go disposait de bibliothèques plus matures alors que, dans de nombreux cas, les bibliothèques Elixir n’étaient pas prêtes à être utilisées en production. Il est également plus difficile de former/trouver des développeurs pour travailler avec Elixir. Ces raisons ont fait pencher la balance en faveur de Go. Le framework Phoenix pour Elixir semble cependant génial et vaut vraiment le coup d’œil.

Conclusion

Go est un langage très performant avec un grand support pour la concurrence. Il est presque aussi rapide que des langages comme C++ et Java. Bien qu’il faille un peu plus de temps pour construire des choses en utilisant Go par rapport à Python ou Ruby, vous économiserez une tonne de temps passé à optimiser le code. Nous avons une petite équipe de développement chez Stream qui alimente les flux et le chat pour plus de 500 millions d’utilisateurs finaux. La combinaison d’un excellent écosystème, d’une intégration facile pour les nouveaux développeurs, de performances rapides, d’une prise en charge solide de la concurrence et d’un environnement de programmation productif font de Go un excellent choix. Stream utilise toujours Python pour son tableau de bord, son site et l’apprentissage automatique pour les flux personnalisés. Nous n’allons pas dire adieu à Python de sitôt, mais à l’avenir, tout le code à haute performance sera écrit en Go. Notre nouvelle API de chat est également entièrement écrite en Go. Si vous souhaitez en savoir plus sur Go, consultez les articles de blog ci-dessous. Pour en savoir plus sur Stream, ce tutoriel interactif est un excellent point de départ.

Plus de lecture sur le passage à Golang

  • 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

.

Apprentissage du 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
  • Tutoriel Go Rocket

.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.