Évolution et disruption
C’est dur de traduire le mot anglais “disruption”. Boulversement semble être le terme le plus approprié.
Le progrès, l’évolution, n’est absolument pas linéaire, de temps en temps, plutôt que la douce pente des améliorations, on monte une marche, d’un coup.
Golang fait partie des éléments “bouleverseurs” de l’informatique.
Les trois couches
En informatique, sur la table basse du hardware, on pose un gros gâteau multicouche : le logiciel. Tout en bas, la couche du noyau, tout en haut, la couche applicative. Au milieu, quelque chose de plus indistinct, la couche de middleware (intergiciel selon l’Office québécois de la langue française), les bases de données et les services non-métier.
Dans cette couche, on peut par exemple ranger les proxy, les brokers, les compteurs de métriques, les serveurs de cache/noms/temps… Tout ce qu’il faut pour relier les différentes parties applicatives qui, ensemble, forment une application.
On trouve dans cette catégorie des applications vénérables, éprouvées, indispensables, mais aussi très rigides. Déjà, corriger un bug, puis le maintenir en attendant qu’il passe upstream, peu de personnes peuvent se le permettre, alors écrire un plugin pour mettre du code métier en C …
Au-delà du C
Le C est sacré. Il a été créé pour inventer UNIX, et c’est la plus fine couche possible au-dessus de l’assembleur. Les seules limites sont donc le matériel (via le kernel), et des détails, comme la responsabilité de gérer la mémoire qui est à la charge du développeur.
On se retrouve donc avec les pleins pouvoirs, mais les temps de développement explosent. La vélocité (le ratio fonctionnalité/temps de développement) est un des gros problèmes du C.
Avec des gros processeurs (qui ne coute pas cher) et des petits temps de dev (qui coute cher), le C est hors de prix.
Il faut donc aller voir ailleurs : les surcouches, les langages spécialisés (mais compilés), ou les langages de scripts (mais génériques).
Le scripting
Les langages de scripting ont une vélocité inégalable, mais le surcout en RAM et CPU est loin d’être négligeable, tout comme sa tradition pénible de ne pas savoir utiliser correctement les threads. La réponse officielle pour les soucis de performances est un mensonge : si c’est mou, tu n’as qu’à recodé la partie qui grippe en C. Double mensonge.
La plupart des blocages sont dus à des problèmes d’IO que seul l’asynchrone peut sauver. OK, le SSD peut aider. Viennent ensuite les problèmes de CPU : le surcout de l’interprétation (qui peut être compensé par de la compilation Just In Time) et la parallélisation (faire bosser plusieurs coeurs de son processeur).
Optimiser en C
Le C permet d’utiliser très efficacement son processeur, mais la parallélisation et l’asynchrone ne seront pas offertes en cadeau. Pour appeler simplement du C depuis un autre langage, il existe depuis longtemps des outils génériques : SWIG ou FFI, une API C, et de la transcompilation, mais le mélange reste douloureux et ne facilitera pas la maintenance.
Dans tous les cas, imaginer qu’un bon scripteur fera du bon C est complètement farfelu.
Intégrer du scripting dans du code C (le contraire, donc), est faisable, mais obtenir de bonnes performances peut être complexe, il n’y a qu’à voir les performances de Postgresql avec ses procédures stockées exotiques.
Il existe des solutions hybrides, basées sur Numpy par exemple, qui émergent pour gérer efficacement des aller-retour entre le scripting et le C : Ibis et MonetDB. Mais ce sont des réponses super spécialisées.
Lua
Lua a été le premier a chambouler la frontière entre C est le reste du monde. Conçu pour gérer des configurations complexes d’une application en C en utilisant un modèle de mémoire similaire pour pouvoir partager des variables, il est aussi un langage de scripting plus décent que bien d’autres. Interprèté, simple et minimaliste, avec un JIT efficace, Lua est le chouchou des dév C pour embarquer du code métier.
Il y a en ce moment une vague de logiciels intégrant Lua pour définir le comportement métier :
- Torch l’utilise pour faire du calcul scientifique (avec la possibilité d’utiliser le GPU)
- Un patch Nginx (packagé Debian, c’est dire sa popularité) existe
- Haproxy l’aura dans sa prochaine version stable
- Haka sniff son réseau, Nmap le réseau des autres
- Sysdig surveille le système d’exploitation avec ses chisels
- Redis et ses procédures stockées. Notons la présence d’un dévermineur spécifique
- …
Bref ça fonctionne, et bien.
Mais, il y a un mais. Débuguer et tester du lua embarqué peut rapidement devenir apocalyptique. Comprendre le workflow du code en C pour y intercaler ses bouts de scripts n’est pas évident, et il ne sera pas possible d’aller au-delà de ce qu’expose l’application hôte.
Javascript
Javascript a un indéniable côté universel et il propose maintenant de bonnes performances grâce au JIT et ses Arrays spécialisés. Nodejs l’a définitivement rendu crédible hors du browser, mais il est dur de trouver des applications embarquant du Javascript.
- Electron est un framework javascript pour créer des applications pour le “desktop”, Javascript y est l’hôte, et non l’invité.
- Nginx a du créer son propre interpréteur Javascript : nginScript car les usages prévus par V8 ou Gecko sont trop spécialisés.
Comme langage autonome, Node a surpris tout le monde en démontrant l’efficacité de l’approche asynchrone et la maturité des interpréteurs Javascripts modernes. Par contre, il n’a rien réglé sur la confusion du code et la fiabilité en production. Node a effectué un débroussaillage salutaire, mais sans arriver à atteindre un niveau de sérieux suffisant. Il faut voir ce que va apporter ES6, mais il reste encore beaucoup de travail.
Golang
Conçu pour remplacer le C++ moche, Golang a, contre toutes attentes, été massivement adopté par les scripteurs (python, ruby…) lassés du code linéaire, mou et imprévisible que permet leurs langages pourtant si véloces.
Golang a des partis pris un peu abrupts :
- Golang n’invente rien, n’amène aucun nouveau concept. Pas de modèle objet complexe ni même d’exceptions. Quelqu’un sachant déjà coder ne sera pas perdu en découvrant Golang.
- Golang est compilé. Le typage est fort, et même le style de syntaxe est imposé.
- Golang ressemble à du scripting, la syntaxe est expressive, la compilation rapide, il n’y a pas de gestion de mémoire grâce au ramasse-miette, les bibliothèques systèmes sont généreuses.
- La parallélisation et l’asynchrone sont prévus dés le départ.
- Golang utilise les types et les structs du C, appeler du code C est triviale avec cgo.
Avec ces choix et ces contraintes, Golang permet de développer efficacement des applications à tous les niveaux du gateau logiciel : des couches basses (comme le fameux Docker), des applications distribuées, des bases de données, du middleware, des applications métiers.
Golang fait sauter la distinction entre le code système et le code métier.
Malgré l’absence de modules dynamiques et de generics, Golang fournit des bibliothèques de grande qualitées : beaucoup d’applications Golang ne sont en fait que des gros frameworks permettant de composer une application, et pas simplement la configurer.
Ces deux derniers points sont tout simplement une révolution.
Avant, les gens se contentaient de tricoter avec des outils existants, où s’arrêtait en route, faute d’outils.
La fin de la loi de Moore, la maturité du cloud et de la virtualisation, les exigences de résiliance, tout pousse les applications à travailler en environement distribué. Ce type d’environnement amène avec lui son lot de complexité, qui nécessite des bibliothèques robustes, et plus seulement des services fiables.
Java a bien prouvé qu’il était possible de cloner les outils révélés par Google (MapReduce, BigTable, Chubby, Borg, Dremel…), mais voilà, Java, quoi. Le ticket d’entrée est élevé, que ce soit en compétence technique et en nombre de serveurs. C’est rentable pour du big data, qui nécessite déjà des machines par palettes entières, mais c’est rapidement impressionnant pour des applications distribuées plus classiques. Les gros fournisseurs de clouds fournissent certains des ses outils, mais comme service, en mode boite noire, la vengeance du logiciel propriétaire contre l’hégémonie de Linux.
Golang a montré que l’on pouvait créer ces outils de coordinations, simplement, et de manière composable, pour ensuite créer des applications distribuées. Cette zone floue entre bibliothèque et service semble impossible en C/C++, un problème de packaging, de divergence d’approche. C’est dommage, des applications distribuées existent, comme les bases de données ScyllaDB, RethinkDB, Aerospike …
Ces applications sont prometteuses, mais sont développés en autarcie, pas moyen d’en recomposer simplement de nouvelles à partir du travail existant.
De son côté Golang propose des briques de base permettant d’agir sur l’ensemble de la stack.
Les bibliothèques de Golang permettent un accès complet aux couches basses, mais le réseau est sa grande force, il gère différents protocoles réseau, au-delà de l’inévitable HTTP : http2, ssh, tls, dns… ce qui permet d’agir à chacune des étapes du protocole. Des outils complémentaires permettent de simplifier l’utilisation de protocoles classiques, comme oxy pour créer des proxy http, yamux pour multiplexer des connexions TCP.
D’autres bibliothèques réseaux, elles, fournissent des outils de haut niveau, comme la coordination sans maitre : raft (Paxos étant jugé trop complexe) ou la communication par potin (gossip) : serf, ou le déverrouillage par double clef : red october.
Par dessus sont construit des outils réseaux de haut niveau : Vulcand, etcd, Consul…
Il faut être honnête et reconnaitre que l’écosystème Java fournit des outils similaires avec Zoo keeper, Akka, Netty, Hystrix, Zuul…, mais on est clairement à une autre échelle, et Java est une famille envahissante.
Golang profite d’une bienveillante neutralité, faisant fi des guerres des langages historiques.
Jusqu’à présent, il y avait une séparation claire entre langages système et langage métier. Cette frontière est fortement remise en cause avec Golang, et je suis curieux de voir ce que va pouvoir apporter Rust dans cette zone intermédiaire.