Paris, le 6 mai 2024
En septembre dernier, ldap2pg 6.0 arrive après une réécriture complète en Go. Pourquoi abandonner Python ? Pourquoi choisir Go ? Quels sont les risques et les avantages ? Étienne BERSAC, mainteneur du projet, détaille ce changement radical.
Le développeur a une éternelle tentation : réécrire le logiciel qu’il développe. Voire celui qu’il utilise. Un biais cognitif nous fait penser qu’écrire du code est plus facile que de comprendre un code existant. Pourtant, la réécriture du code est tout sauf anodine.
En effet, la programmation est un processus itératif. Le code accumule des ajustements, correctifs, adaptations et optimisations progressivement. Parfois au détriment de la lisibilité. Nous sous-estimons les besoins et les contraintes qui ont aboutis au code actuel. Ainsi, de nombreux chantiers de réécritures se sont soldés par un échec: le nouveau logiciel n’est guère mieux, les fonctionnalités et la compatibilité sont moindres, certains bug ont été remplacés par d’autres. Tout cela pour un coût pharamineux.
Conditions de succès
Pour se prémunir de l’échec, nous avons quelques repères avant de se lancer. D’abord, l’équipe réécrit elle-même son propre code. Ainsi, le besoin vient moins d’un manque de compréhension du code, au contraire. De plus, la mémoire aidant, le risque de passer à la trappe des besoins et des contraintes diminue.
Autre repère : réécrire dans un but fonctionnel et non esthétique. Nous incluons les performances dans le fonctionnel. Il faut déterminer le gain de la réécriture pour l’utilisateur avant de se lancer.
Besoins pour ldap2pg
ldap2pg date de 2017. Il a connu de nombreuses itérations et accumulé beaucoup de savoir faire. Le choix a porté naturellement sur Python pour sa popularité, sa simplicité et la richesse de l’écosystème. Depuis 2022, nous avons constatés plusieurs limites à ce choix.
D’abord la complexité de l’installation. L’écosystème Python évolue très vite. Trop vite pour le monde des bases de données. Lorsque RHEL livre une nouvelle version, l’interpréteur Python disponible est bientôt abandonné par la communauté Python. Le problème est accentué par l’abandon rapide du support des vieilles versions de Python par l’écosystème Python lui-même. L’utilisateur se retrouve avec un interpréteur obsolète pendant environ 10 ans. Tant qu’il se contente des outils livrés avec la distribution, ce n’est pas gênant. Quant il s’agit de proposer de nouveaux outils, c’est plus compliqué.
Pour ldap2pg, la charge revenait surtout au développeur. L’effort pour porter ldap2pg sur les divers systèmes actuels exécutant les bases de données est considérable. La première mesure a été de réduire au maximum les dépendances de ldap2pg. Malgré cela, il fallait supporter différentes versions de chaque dépendances, avec souvent des changements majeurs.
La deuxième limite de Python est la performance. Bien que ldap2pg soit surtout un client LDAP et Postgres en attente de réponse des serveurs, il peut manipuler de grande quantité de données lorsqu’il gère les privilèges. Nous avons constaté que ldap2pg consommait plus de 1Go de mémoire et 10 minutes de traitement pour synchroniser quelques centaines de rôles et leurs privilèges sur plusieurs centaines d’objets. Cela crée des pics de consommation de ressources lors de la synchronisation.
En dernier point, nous constatons un morcellement de l’écosystème Python. La communauté se focalise sur le développement d’application et l’IA, avec peu de contrainte de portabilité grâce à Docker ou un environnement cible unique vendu en SaaS. À l’inverse, les outils systèmes comme Ansible sont à la peine pour être portable. Par exemple barman, temBoard, patroni ou PoWA doivent tous supporter Python 3.6, obsolète depuis 2021, mais bien installée dans les infratructures. Ils passeront à 3.8 lorsque celui-ci sera déjà abandonné par la communauté Python.
Pourquoi Go ?
On ne présente plus le langage Go. Annoncé en 2009, il est aujourd’hui reconnu et populaire. Il est le langage de prédilection du monde DevOps. En 2024, Go est entrée dans le top 10 de l’index de popularité TIOBE, sur près de 300 langages comparés. On le retrouve dans Docker, Kubernetes, Terraform, Grafana, CockroachDB et bien d’autres outils reconnus.
Comme Python, Go est un langage simple. Le typage de Go est plus simple et plus fiable que celui de MyPy. L’asynchronicité n’impose pas la coloration des fonctions. C’est à dire qu’il n’y a pas deux implémentations pour une même fonctionnalité, l’une synchrone et l’autre asynchrone.
Côté outillage, c’est le jour et la nuit. Go fournit d’office une gestion des dépendances sûre, la publication de projet, la plateforme de documentation globale pkg.go.dev, l’analyse statique et le formatage du code.
Pour la portabilité, la différence est encore plus flagrante. Un projet Go peut choisir une et une seule version pour le compilateur et chacune des dépendances. Fournir les dépendances et le compilateur n’est plus à la charge de l’utilisateur. Le livrable binaire est extrêmement portable.
Enfin pour les performances, Go bat évidemment Python. D’abord parce que Go est compilé en langage machine. Mais aussi parce que Go ne fournit pas l’introspection permanente qu’offre Python et son typage dynamique. Les interfaces de Go offrent une souplesse suffisante qui ne fait pas regretter le typage dynamique de Python.
La réécriture
ldap2pg dispose d’une batterie de tests unitaires. Ces tests étant couplés au code Python, ils ont été abandonnés avec. En revanche, la batterie de tests de bout-en-bout a été préservée et même étendue. Cette batterie protège ldap2pg 6.0 de nombreuses régressions depuis ldap2pg 5.9.
Dans un premier temps, les deux implémentations ont cohabité dans le dépôt.
Puis, une fois les premiers gains confirmés,
nous avons dérivé la branche v5 avec exclusivement l’implémentation Python historique.
L’implémentation Go est restée seule sur la branche master
.
Avant de livrer la version 6.0 finale, le code a été remanié plusieurs fois pour être plus idiomatique pour Go. La synchronisation des privilèges travaille désormais pour chaque privilège indépendemment permettant un gain sensible de consommation mémoire à un instant T.
Nous avons mesuré le temps consacré à la réécriture, il se chiffre à 4 mois ETP. Pas d’effet tunnel à constater. Nous n’avons pas eut besoin de faire une version 5.10 intermédiaire avant de livrer la 6.0 finale.
Les régressions et ruptures de compatibilités
ldap2pg a gardé longtemps la compatibilité avec des syntaxes de configuration historiques. La réécriture en Go a été l’occasion d’abandonner ces syntaxes obsolètes.
ldap2pg 5 proposait une API Python publique pour une usage serverless. Cette fonctionnalité est nécessairement cassé par le changement de langage. Néanmoins, il est envisageable de restaurer une API publique Go équivalente.
Les bibliothèques d’analyse des paramètres de lignes de commande en Go sont sensiblement différentes d’argparse en Python.
Aussi la CLI de ldap2pg a légèrement changé, provoquant une rupture de compatibilité.
Le cas courant est le passage de --no-color
à --color=false
, typique des CLI en Go.
Certaines fonctionnalités obsolètes ont été abandonnées comme la condition sur nom de rôle avec role_match
.
Nous avons repoussé d’autres fonctionnalités rarement critiques à une version ultérieure comme la gestion de valeurs hétérogènes avec on_unexpected_dn
.
Plus important, la gestion de l’héritage a été revue de sorte que ldap2pg peut hériter un rôle des groupes pg_
prédéfinis plus facilement.
La réécriture a donc été l’occasion de changements majeurs.
Les gains
Le coût du binaire statique est faible comparé au coût de l’écosystème Python. L’image Docker de ldap2pg est passée de 50Mo à 7Mo à télécharger. Sur disque de 120Mo à 18Mo.
Enfin, le gain en performance est colossal. ldap2pg 6.0 peine à dépasser 2Mo de mémoire pour les synchronisations les plus complexes et seulement 6 secondes pour une exécution sans changement. En fait, le temps d’exécution de ldap2pg 6 dépend essentiellement du temps de réponses de l’annuaire et de Postgres.
Go est un langage plus verbeux que Python. Paradoxalement, la réimplémentation en Go est aussi concise que l’implémentation en Python. ldap2pg est passé de 5800 lignes de Python à 5500 lignes de Go. Nous avons observé un gain notable sur tout le code autour du projet pour gérer la qualité et la livraison. La chaîne de CI est passée de 9 à 5 tâches, que nous pourrions davantage simplifier encore. Le passage à Go a simplifié le projet.
Conclusion
Le succès de la réécriture est dû à plusieurs paramètres: la réécriture par la même équipe qui a écrit le code original, les objectifs fonctionnels qui ont justifié le chantier.
Nous constatons que la nouvelle version est bien reçue. La popularité de ldap2pg continue son chemin dans un secteur niche. Le nombre de retours par ticket a augmenté légèrement, non pour remonter des erreurs ou des régressions, mais pour demander de l’accompagnement ou de nouvelles fonctionnalités.
- 2024 (5) ,
- ldap2pg (15) ,
- développeur (11) ,
- optimisation (10) ,
- performance (32) ,
- Python (11) ,
- Go (1) ,
- retour d'expérience (13)