<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr"><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="https://engineering.pix.fr/feed.xml" rel="self" type="application/atom+xml" /><link href="https://engineering.pix.fr/" rel="alternate" type="text/html" hreflang="fr" /><updated>2023-11-27T08:58:55+00:00</updated><id>https://engineering.pix.fr/feed.xml</id><title type="html">Le blog technix</title><subtitle>Articles, actualités et aventures techniques des équipes Pix.</subtitle><entry xml:lang="fr"><title type="html">Suivi de la dérive de nos dépendances</title><link href="https://engineering.pix.fr/2023/11/27/suivre-la-derive-de-nos-dependances.html" rel="alternate" type="text/html" title="Suivi de la dérive de nos dépendances" /><published>2023-11-27T00:00:00+00:00</published><updated>2023-11-27T00:00:00+00:00</updated><id>https://engineering.pix.fr/2023/11/27/suivre-la-derive-de-nos-dependances</id><content type="html" xml:base="https://engineering.pix.fr/2023/11/27/suivre-la-derive-de-nos-dependances.html">&lt;h2 id=&quot;le-constat&quot;&gt;Le constat&lt;/h2&gt;

&lt;p&gt;Le suivi de nos dépendances logiciels n’a jamais été un sujet suivi chez Pix. Les mises à jour se sont fait donc au bon vouloir de quelques personnes.&lt;/p&gt;

&lt;p&gt;Dans le passé, une petite équipe avait été créée pour mettre à jour Ember sur nos applications clientes. Cela fonctionne bien par à-coups, mais ne règle pas le problème de suivi.&lt;/p&gt;

&lt;p&gt;C’est pourquoi &lt;a href=&quot;https://www.mend.io/renovate/&quot;&gt;Renovate&lt;/a&gt; a été mis en place. Renovate est présent sur tous nos dépôts et nous pousse en permanence des mises à jour de nos dépendances. Avec le “&lt;a href=&quot;https://docs.renovatebot.com/key-concepts/dashboard/&quot;&gt;Dependency dashboard&lt;/a&gt;” (&lt;a href=&quot;https://github.com/1024pix/pix/issues/5637&quot;&gt;voir celui de 1024pix/pix&lt;/a&gt;), nous pouvons avoir les différentes mises à jour en attente. Cela nous donne une idée du retard mais cela reste très grossier.&lt;/p&gt;

&lt;p&gt;C’est pourquoi lors des Tech Days 2023 (du travail technique réalisé pendant l’été), une équipe s’est chargée de remettre de l’effort sur les mises à jour de dépendances. Le travail a été de passer à node 18 sur notre application principale et de configurer Renovate pour que tout roule.&lt;/p&gt;

&lt;p&gt;Le besoin de quantifier d’une manière commune le retard de mise à jour des dépendances sur les différents projets nous a amené à nous intéresser à une métrique intéressante, &lt;a href=&quot;https://libyear.com/&quot;&gt;libyear&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;libyear-à-la-rescousse&quot;&gt;Libyear à la rescousse&lt;/h2&gt;

&lt;p&gt;En bref &lt;a href=&quot;https://libyear.com/&quot;&gt;libyear&lt;/a&gt; permet de quantifier l’âge de l’ensemble des dépendances.&lt;/p&gt;

&lt;p&gt;En calculant cette métrique sur nos différents projets, nous avons une mesure &lt;strong&gt;comparable&lt;/strong&gt; pour &lt;strong&gt;constater&lt;/strong&gt; et &lt;strong&gt;décider&lt;/strong&gt; des actions de mise à jour.
C’est pourquoi nous avons développé &lt;a href=&quot;https://github.com/Dependency-Drift-Tracker/dependency-drift-tracker&quot;&gt;dependency-drift-tracker&lt;/a&gt; qui nous permet de connaitre la dérive actuelle de nos dépendances, de voir l’évolution et de comparer ce retard avec les autres.&lt;/p&gt;

&lt;p&gt;En plus du retard de nos dépendances, nous affichons également la mesure en années depuis la dernière release, appelée Pulse. Cela permet de repérer des dépendances qui sont devenues obsolètes ou dépréciées. Cela nécessite une analyse un peu plus poussée manuelle.&lt;/p&gt;

&lt;h2 id=&quot;a-quoi-cela-ressemble-&quot;&gt;A quoi cela ressemble ?&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/suivi-dependance/dependency-drift-tracker.png&quot; alt=&quot;L'interface de dependency-drift-tracker&quot; /&gt;&lt;/p&gt;

&lt;p&gt;À gauche, en un clin d’œil, on voit les dépôts suivis avec l’information du retard. Ensuite dans le panneau principal, 2 métriques sont affichées, le retard et et le pulse.&lt;/p&gt;

&lt;p&gt;Les 2 graphs suivants permettent de suivre l’évolution de ces chiffres à travers le temps.&lt;/p&gt;

&lt;p&gt;Enfin le tableau final affiche le résultat du dernier lancement de libyear avec les informations de dépendances individuelles.&lt;/p&gt;

&lt;p&gt;Les données sont rafraîchies tous les jours. Un retour suffisamment régulier pour valoriser le travail accompli la veille, et planifier la suite.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Rendre visible notre retard nous a permis de nous améliorer sur le suivi des versions. Voir la baisse de la courbe après une mise à jour est toujours un plaisir. Même si, d’une manière continue et semble t’il inexorable, la courbe remonte suite aux mises à jour des dizaines de dépendances utilisées par nos applications.&lt;/p&gt;

&lt;p&gt;Curieux·ses, voici le &lt;a href=&quot;https://1024pix.github.io/dependency-drift-tracker/&quot;&gt;suivi des applications Pix&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;essayez-le&quot;&gt;Essayez-le&lt;/h2&gt;

&lt;p&gt;Nous avons rendu le code de suivi générique pour être utilisable par n’importe quel projet javascript.
Vous pouvez facilement gérer les fichiers de suivi manuellement via &lt;a href=&quot;https://github.com/Dependency-Drift-Tracker/dependency-drift-tracker&quot;&gt;dependency-drift-tracker&lt;/a&gt;, ou encore plus simplement avec l’action GitHub &lt;a href=&quot;https://github.com/Dependency-Drift-Tracker/dependency-drift-tracker-action&quot;&gt;dependency-drift-tracker-action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Et lisez la &lt;a href=&quot;https://github.com/Dependency-Drift-Tracker/dependency-drift-tracker?tab=readme-ov-file#usage&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;</content><author><name></name></author><summary type="html">Ou comment voir le retard des mises à jour à faire sur l'ensemble de nos applications.</summary></entry><entry><title type="html">Migrer de CommonJS vers ECMAScript Modules - #2</title><link href="https://engineering.pix.fr/javascript/2023/09/06/migrer-de-commonjs-vers-esm-migration.html" rel="alternate" type="text/html" title="Migrer de CommonJS vers ECMAScript Modules - #2" /><published>2023-09-06T12:04:00+00:00</published><updated>2023-09-06T12:04:00+00:00</updated><id>https://engineering.pix.fr/javascript/2023/09/06/migrer-de-commonjs-vers-esm-migration</id><content type="html" xml:base="https://engineering.pix.fr/javascript/2023/09/06/migrer-de-commonjs-vers-esm-migration.html">&lt;h2 id=&quot;ya-quoi-au-menu-&quot;&gt;Y’a quoi au menu ?&lt;/h2&gt;

&lt;p&gt;Cet article est la suite de &lt;a href=&quot;/javascript/2023/08/25/migrer-de-commonjs-vers-esm.html&quot;&gt;la théorie sur la migration ESM&lt;/a&gt;. Il explique comment nous nous y sommes pris pour migrer l’API du monorepo &lt;a href=&quot;https://github.com/1024pix/pix/tree/dev/api&quot;&gt;Pix&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Si vous n’avez que cinq minutes, lisez &lt;a href=&quot;#la-stratégie&quot;&gt;la stratégie&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Si vous avez un peu plus de temps, choisissez dans le menu.&lt;/p&gt;

&lt;p&gt;Voilà les plats principaux :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;monter et faire vivre &lt;a href=&quot;#léquipe&quot;&gt;l’équipe dédiée&lt;/a&gt; ;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#la-stratégie&quot;&gt;la stratégie&lt;/a&gt; de migration ;
un zoom sur &lt;a href=&quot;#deux-choix-dimplémentation-de-la-codebase&quot;&gt;deux décisions&lt;/a&gt; d’implémentation de la codebase ;&lt;/li&gt;
  &lt;li&gt;faire de la &lt;a href=&quot;#en-approchant-de-la-production&quot;&gt;mise en production&lt;/a&gt; un non-évènement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et si vous êtes gourmands, lisez tout du début à la fin…&lt;/p&gt;

&lt;h2 id=&quot;léquipe&quot;&gt;L’équipe&lt;/h2&gt;

&lt;h3 id=&quot;traiter-les-besoins-internes&quot;&gt;Traiter les besoins internes&lt;/h3&gt;
&lt;p&gt;Chez Pix, le développement est effectué par des &lt;a href=&quot;https://github.com/1024pix/pix-lifestyle/blob/main/organisation/equipes.md&quot;&gt;Feature Teams&lt;/a&gt; (FT), qui reçoivent les besoins utilisateurs principalement du Product Owner (PO). Les développeurs peuvent participer au recueil et au raffinement du besoin, et le questionner lors de l’implémentation (le détail est expliqué &lt;a href=&quot;https://m.youtube.com/watch?v=r0VmGhjnJr4&quot;&gt;ici&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Les besoins d’amélioration interne au pôle engineering, dont fait partie la migration ESM, suivent un processus particulier, appellé “Impact Team”. Des développeurs sont détachés de leur équipe pour un temps défini, en général quelques semaines, avec un objectif clair, même si le sujet est exploratoire.&lt;/p&gt;

&lt;p&gt;Pour la migration ESM, un développeur a fait une présentation de 15 minutes à tous les autres développeurs, en exposant le besoin et la solution qu’il proposait. Il a ensuite proposé qu’un autre développeur, volontaire, le rejoigne pour deux semaines afin de réaliser la migration.&lt;/p&gt;

&lt;h3 id=&quot;travailler-ensemble&quot;&gt;Travailler ensemble&lt;/h3&gt;

&lt;p&gt;La taille de l’équipe, 2 personnes, est importante :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;suffisamment grande pour résoudre le problème théorique et permettre les absences ponctuelles ;&lt;/li&gt;
  &lt;li&gt;suffisamment petite pour minimiser le &lt;a href=&quot;#coût-de-coordination-et-complexité&quot;&gt;surcoût de coordination&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le développement, dès le début, s’est fait en pair programming, en rotations de 30 minutes, la plupart du temps en présentiel. Le pair programming est &lt;a href=&quot;https://github.com/1024pix/pix-lifestyle/blob/main/pratiques/pair-et-mob-programming.md&quot;&gt;encouragé chez Pix&lt;/a&gt; et il a été crucial ici : répartir la connaissance, trouver les solutions et se soutenir lorsqu’il fallait effectuer des corrections manuelles.&lt;/p&gt;

&lt;p&gt;Les corrections manuelles ont aussi été faites en pair programming, même si on pense spontanément à paralléliser les tâches répétitives. En effet, la personne qui ne code pas, pendant que son partenaire modifie les fichiers :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;signale les erreurs d’inattention ;&lt;/li&gt;
  &lt;li&gt;pense aux cas qui ne sont pas couverts ;&lt;/li&gt;
  &lt;li&gt;suggère de meilleurs moyens d’effectuer la correction (ex: grouper les fichiers) ;&lt;/li&gt;
  &lt;li&gt;prend du recul sur ce qui se passe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Être deux nous a aussi permis de prendre soin l’un de l’autre et de mener à bien la migration. Seuls, nous n’aurions pas reconnu la fatigue et aurions persisté dans des solutions qui étaient des impasses. Mon ressenti est qu’il a été plus facile de prendre soin de l’autre que de moi-même, et que l’autre me rappelait quand je ne prenais pas soin de moi. Et vice-versa, dans un cercle vertueux.&lt;/p&gt;

&lt;p&gt;En plus de cette boucle de feedback de 30 minutes, il y avait une boucle quotidienne. La journée commençait avec un partage de 15 minutes de l’avancée avec les CTO, sur la base d’un résumé fait en fin de journée sur le Wiki interne. Cela permet de s’assurer que nous étions sur le bon chemin, et d’envisager des actions pour enlever des obstacles, par exemple demander de l’aide en dehors de l’équipe.&lt;/p&gt;

&lt;p&gt;Ce partage quotidien a assuré la confiance. Lorsque les deux semaines initialement imparties étaient écoulées, les CTO ont décidé que la migration pouvait être poursuivie, sachant que nous traitions les risques les plus importants en premier. Si la migration s’avérait impossible, ils le sauraient rapidement et pourraient décider d’arrêter le chantier.&lt;/p&gt;

&lt;p&gt;Ne gardons pas le suspense plus longtemps, d’autant que la présentation de l’article le mentionne : la migration a pris deux mois au lieu des 2 semaines annoncées (&lt;a href=&quot;#estimation&quot;&gt;ce qui était prévisible après tout&lt;/a&gt;).&lt;/p&gt;

&lt;h2 id=&quot;la-stratégie&quot;&gt;La stratégie&lt;/h2&gt;

&lt;p&gt;Parler de stratégie n’est pas tout à fait honnête, car nous n’avions pas de plan, uniquement quelques principes.
Nous partageons ici notre expérience pour vous aider à en avoir une si vous vous lancez dans l’aventure.&lt;/p&gt;

&lt;h3 id=&quot;un-feedback-rapide-&quot;&gt;Un feedback rapide ?&lt;/h3&gt;

&lt;p&gt;Nous n’avons envisagé la migration que parce que nous avions des tests automatisés avec une bonne couverture.&lt;/p&gt;

&lt;p&gt;La démarche classique de refactoring (car c’en est un) aurait été de migrer un fichier (SUT) en ESM et de vérifier que le test passe toujours. Le test (CJS) importerait le SUT (ESM), qui lui-même importerait des dépendances (ESM).
Pour cela, il faudrait que ESM et CJS soient interopérables, et bien que le sujet commence à être &lt;a href=&quot;https://github.com/johnloy/esm-commonjs-interop-manual#tooling&quot;&gt;implémenté et documenté&lt;/a&gt;, l’investissement ne semblait pas être un bon compromis.&lt;/p&gt;

&lt;p&gt;Nous pensions procéder en trois étapes :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;migrer l’implémentation et effectuer un test manuel, à savoir démarrer l’API ;&lt;/li&gt;
  &lt;li&gt;exécuter &lt;a href=&quot;https://github.com/1024pix/pix/tree/dev/high-level-tests/e2e&quot;&gt;les tests automatisés de bout en bout (front + back)&lt;/a&gt; ;&lt;/li&gt;
  &lt;li&gt;migrer les tests automatisés de l’API puis les exécuter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La conséquence était la suivante : pendant la première partie, nous n’avions plus aucun test qui nous couvrait. Autrement dit : nous modifions la moitié de la codebase sans savoir si nos modifications étaient correctes. Même la vérification statique (lint) n’a pas fonctionné tout de suite, car un fichier dont le code est invalide ne peut être parsé. Les modifications étant effectuées par des codemods et des modifications manuelles, si elles se révélaient incorrectes et qu’il fallait les rejouer, nous n’étions pas à l’abri de devoir refaire ces actions manuelles.&lt;/p&gt;

&lt;p&gt;Lorsque les deux semaines se sont allongées, nous ressentions de plus en plus cette épée de Damoclès. À la prochaine migration de codebase, il serait judicieux de remettre en cause cette stratégie.&lt;/p&gt;

&lt;h3 id=&quot;des-compromis-encore-et-toujours&quot;&gt;Des compromis, encore et toujours&lt;/h3&gt;

&lt;p&gt;Dès le début, nos réflexes de développement ont été mis à rude épreuve.
Voilà les observations des équipes sur le travail de l’impact Team.&lt;/p&gt;

&lt;p&gt;La migration a duré deux mois au lieu des deux semaines annoncées.
Son développement a duré un mois et demi et la mise en production 15 jours. La PR principale modifiait la moitié de la codebase, et devait être rebase manuellement avant chaque livraison. Elle comportait 130 commits, dont une centaine pour les corrections manuelles. Les codemods ont été écrits par l’équipe en charge de la migration, ne sont pas dans la codebase et ne sont pas réutilisables. La manière de travailler de l’impact team ne semble pas leur avoir simplifié la vie.
&lt;img src=&quot;/assets/images/posts/migrer-de-commonjs-vers-esm-migration/codemod-workflow.png&quot; alt=&quot;workflow de migration&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Nous avons pourtant travaillé dur pour que la solution soit simple et viable, ce qui nous fait dire : &lt;strong&gt;La solution la plus simple n’a pas toujours l’air d’être la plus simple&lt;/strong&gt;. Faire des compromis est un challenge intellectuel.&lt;/p&gt;

&lt;p&gt;Tout d’abord, nous n’avons pas utilisé les &lt;a href=&quot;https://github.com/azu/commonjs-to-es-module-codemod/blob/master/transforms/require-to-import-default.js&quot;&gt;codemods de la communauté&lt;/a&gt;. Bien qu’ils traitent une partie du problème, leur compréhension et leur extension auraient pris plus de temps qu’une réécriture. Nous avons décidé d’écrire nos propres codemods, et d’en écrire le moins possible. Pourquoi ? Parce que cela demande un investissement conséquent pour s’assurer que seul le code à modifier est modifié. Nous avons écrit des tests unitaires pour vérifier que le code n’est pas modifié, mais cela ne couvre que les cas que nous avons identifié. De plus, ils ne testent pas le comportement lorsque les codemods sont appliqués successivement sur un même fichier : des dépendances temporelles peuvent se produire.&lt;/p&gt;

&lt;p&gt;Ensuite, nous avons choisi à de nombreuses reprises de migrer manuellement le code. Cela correspond à deux cas distincts : le code CJS est complexe à migrer en ESM, ou le cas est très peu présent. Nous préférons de loin développer des codemods et nous avons acquis de l’expérience, mais comme expliqué ci-dessus, nous avois choisi d’en écrire le moins possible.&lt;/p&gt;

&lt;p&gt;Et pour finir, nous avons travaillé sur plusieurs branches/PR CJS et sur une seule branche/PR ESM. Lorsqu’une fonctionnalité CJS est difficile à migrer en ESM, on la remplace &lt;a href=&quot;https://github.com/1024pix/pix/pull/5715/commits/6b2b224953a16c2c5b821a0521d3fe2e1262c746&quot;&gt;par une autre fonctionnalité CJS&lt;/a&gt;, elle-même plus facile à migrer, couverte par les tests et mergeable de suite. Ces modifications ont été faites par codemods (CJS → CJS) et manuellement.&lt;/p&gt;

&lt;p&gt;Le résultat final de toutes ces contraintes est un état d’esprit que nous avons appelé “codemodception”.
Lorsque nous identifions une syntaxe CJS non traitée, nous pensons à la manière la plus efficace de le faire entrer dans des cas déjà traités. Cela demande une bonne connaissance de la syntaxe CJS et ESM, de mémoriser les codemods et leur enchaînement.&lt;/p&gt;

&lt;h2 id=&quot;deux-choix-dimplémentation-de-la-codebase&quot;&gt;Deux choix d’implémentation de la codebase&lt;/h2&gt;

&lt;p&gt;Nous avons conservé les fonctionnalités existantes d’import/export lorsque cela était possible.
Dans deux cas exposés ci-dessous, nous avons fait des choix structurants d’implémentation, après avoir consulté toutes les équipes et validé la solution ensemble.&lt;/p&gt;

&lt;h3 id=&quot;les-exports-nommés&quot;&gt;Les exports nommés&lt;/h3&gt;

&lt;p&gt;Les exports nommés &lt;a href=&quot;https://basarat.gitbook.io/typescript/main-1/defaultisbad&quot;&gt;ont des avantages&lt;/a&gt;, et nous trouvons important celui de l’amélioration de la maintenabilité du code : permettre l’import sélectif des composants d’un module.
En l’absence de standards sur ce point, le codebase CJS contenait des exports nommés et des exports anonymes.&lt;/p&gt;

&lt;p&gt;Nous avons proposé aux équipes de choisir :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;garder la situation existante : permettre les exports nommés et anonymes ;&lt;/li&gt;
  &lt;li&gt;utiliser uniquement des exports nommés, en transformant les exports anonymes via des codemods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La solution n°2, la suppression des exports anonymes, a été choisie.
Elle comporte tout de même un inconvénient : si un fichier &lt;a href=&quot;https://github.com/1024pix/pix/pull/5787/files#diff-734269ab23f38145d1f6742bd4282022dc6d47a8f57c3e2b372e45da7f1d88f4&quot;&gt;n’exporte qu’un seul composant&lt;/a&gt;, le développeur est obligé de lui trouver un nom, alors que le nom du fichier contient déjà toute l’information nécessaire.&lt;/p&gt;

&lt;p&gt;Cette décision aurait pu être prise indépendamment de la migration ESM, mais la prendre de suite nous permettait de ne pas écrire de codemod ESM d’export/export anonymes, dont la syntaxe est complexe.&lt;/p&gt;

&lt;p&gt;Nous l’avons mise en œuvre en CJS avec un codemod, partant du principe que le nom de l’import anonyme existant était celui du fichier, transposé de snake-case en PascalCase. C’est un usage que nous avions constaté dans la plupart des fichiers, mais il y a bien eu des exceptions, que nous avons corrigées manuellement.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/1024pix/pix/pull/5731&quot;&gt;La PR&lt;/a&gt; a ainsi pu être envoyée en production au plus tôt&lt;/p&gt;

&lt;h3 id=&quot;les-tests-et-linjection-de-dépendance&quot;&gt;Les tests et l’injection de dépendance&lt;/h3&gt;

&lt;p&gt;Lorsque l’API démarrait enfin, et que les tests manuels et de bout-en-bout passaient, nous avons entamé la migration des tests automatisés API. Le message qui allait hanter les jours suivants apparut très vite :
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ES Modules cannot be stubbed&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/sinonjs/sinon/blob/2ddf7ff8105d15c9609f155822fda4aac0db875b/lib/sinon/stub.js#L76&quot;&gt;Il provient&lt;/a&gt; de la librairie de test SinonJs, dans les tests unitaires, lorsque dans la section “Given” du test, nous remplaçons un composant importé par &lt;a href=&quot;https://martinfowler.com/bliki/TestDouble.html&quot;&gt;une doublure de test&lt;/a&gt;. Pour faire court, les exports CJS sont mutables alors que les exports ESM sont immutables (pour être précis, on parle de &lt;a href=&quot;https://2ality.com/2015/07/es6-module-exports.html&quot;&gt;live bindings&lt;/a&gt;. La conséquence : tous les tests qui substituent une doublure de tests à un composant importé ne sont plus valables en ESM.&lt;/p&gt;

&lt;p&gt;Après quelques jours à explorer les &lt;a href=&quot;https://github.com/sinonjs/sinon/issues/1711#issuecomment-369510514&quot;&gt;librairies de doublure&lt;/a&gt;, nous nous sommes rendus à l’évidence : le problème n’est pas ESM, mais le fait que nous n’avons pas &lt;a href=&quot;https://github.com/1024pix/pix/blob/dev/docs/adr/0046-injecter-les-dependances-api.md&quot;&gt;injecté les dépendances&lt;/a&gt; à plusieurs endroits dans l’API.
Si nous l’avions fait, au lieu de substituer une doublure de test dans un import, nous aurions injecté dans le composant à tester la doublure de test (&lt;a href=&quot;https://github.com/1024pix/pix/commit/027b560b42ef72731662559f906de33ff749a008&quot;&gt;voir cet exemple&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Nous avons longuement réfléchi à la solution :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;écrire un codemod qui injecterait les dépendances ;&lt;/li&gt;
  &lt;li&gt;injecter manuellement les dépendances ;&lt;/li&gt;
  &lt;li&gt;écrire un codemod qui &lt;a href=&quot;https://stackoverflow.com/questions/48510504/es6-module-immutability&quot;&gt;encapsulerait les exports&lt;/a&gt; dans un objet, contournant ainsi le problème en rendant mutables les exports.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous avons fini par conclure que l’injection de dépendances, bien que plus verbeuse, était souhaitable. Elle rend explicite les dépendances d’un module, et rend les tests plus compréhensibles. Il n’est plus nécessaire de connaître le pattern proxy et son implémentation en Javascript pour comprendre comment fonctionnent les tests.&lt;/p&gt;

&lt;p&gt;Il est apparu assez rapidement qu’injecter les dépendances via un codemod était complexe, et comme la migration avait du retard, nous avons préféré une solution manuelle dont le temps pouvait être prévu à une solution automatisée dont le temps était difficilement prévisible. Nous avons recensé le volume de fichiers à modifier en modifiant le code source de SinonJs pour qu’il mentionne le nom du module : pour chaque composant d’un module utilisant un composant importé, on ajoute la dépendance en tant que paramètre du composant à tester (on utilise une valeur par défaut), et on modifie le test pour injecter la doublure au lieu du composant. Cela donnait un total de 500 fichiers.&lt;/p&gt;

&lt;p&gt;Nous avons décidé de demander de l’aide aux autres équipes, sur base du volontariat, pour effectuer ces corrections manuelles en CJS. Durant deux après-midi, après un exposé du principe, chaque groupe de quelques personnes injectait manuellement les dépendances sur un périmètre défini.&lt;/p&gt;

&lt;h2 id=&quot;en-approchant-de-la-production&quot;&gt;En approchant de la production&lt;/h2&gt;

&lt;p&gt;Après quelques semaines de développement, il est devenu clair que le sujet était bien plus complexe que nous ne l’imaginions. Même une fois que tous les tests automatisés passaient, des comportements imprévus pouvaient apparaître en production. Nous avons alors décidé de demander de l’aide à l’équipe en charge à mitiger les risques liés au changement, l’équipe &lt;a href=&quot;/organisation/2020/04/14/les-capitaines-de-la-production.html&quot;&gt;captains&lt;/a&gt;. Une seule séance de brainstorming de deux heures a permis de trouver une stratégie pour sécuriser ce changement. Je vous l’expose ici.&lt;/p&gt;

&lt;h3 id=&quot;le-suivi-et-la-motivation&quot;&gt;Le suivi et la motivation&lt;/h3&gt;

&lt;p&gt;L’état et la vitesse d’avancement de la migration n’était pas très visible, aussi l’équipe captains a affiché en dynamique l’état des tests automatisés sur &lt;a href=&quot;https://1024pix.github.io/areweesmyet/&quot;&gt;Are we ESM yet ?&lt;/a&gt;. C’est une &lt;a href=&quot;https://github.com/1024pix/areweesmyet&quot;&gt;Github page&lt;/a&gt; qui interroge la CI via une API : simple et efficace. Elle rend le travail et les difficultés visibles à tous.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/migrer-de-commonjs-vers-esm-migration/are-we-esm-yet.png&quot; alt=&quot;dashboard code&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;sécuriser-le-déploiement&quot;&gt;Sécuriser le déploiement&lt;/h3&gt;

&lt;p&gt;Pour vérifier le comportement d’ESM, la production était le meilleur endroit. Le tout était de limiter l’impact qu’un problème éventuel aurait pu avoir si cela se produisait.&lt;/p&gt;

&lt;p&gt;Nous avons listé des scénarios que nous avons priorisés par ordre d’importance décroissant :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;les utilisateurs internes de Pix, dans l’interface d’administration ;&lt;/li&gt;
  &lt;li&gt;les utilisateurs externes de Pix, dans des usages non critiques ;&lt;/li&gt;
  &lt;li&gt;les utilisateurs externes de Pix, dans des usages critiques, à savoir le passage de certification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ensuite, nous avons réfléchi à la manière d’utiliser ESM sur une fraction du traffic API dans ces scénarios.&lt;/p&gt;

&lt;p&gt;Nous avons fini par trouver une implémentation satisfaisante de ce qui est connu sous le nom de &lt;a href=&quot;https://en.wikipedia.org/wiki/Feature_toggle#Canary_release&quot;&gt;canary release&lt;/a&gt; : deux instances d’APi tournent simultanément : l’une en CJS, l’autre en ESM. On paramètre les front-end pour qu’ils envoient progressivement une partie des requêtes sur la version ESM.&lt;/p&gt;

&lt;p&gt;Un tableau de suivi &lt;a href=&quot;/bdd/2022/09/20/steampipe-dashboard.html&quot;&gt;Steampipe&lt;/a&gt; donne une vue d’ensemble de la situation.&lt;/p&gt;

&lt;p&gt;Voilà un exemple à mi-chemin de l’expérimentation : les front-end utilisent ESM pour une requête sur deux, sauf l’application principale. Les traitements asynchrones (workers) utilisent déjà uniquement la version ESM.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/migrer-de-commonjs-vers-esm-migration/canary.png&quot; alt=&quot;dashboard canary&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;happy-end&quot;&gt;Happy end&lt;/h2&gt;

&lt;p&gt;Le 1er juin 2023, 3 mois après avoir donné le coup d’envoi, toute la production tourne en ESM !!
Le changement a été un non-événement.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/migrer-de-commonjs-vers-esm-migration/the-end.png&quot; alt=&quot;dashboard code&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;and-finally-monsieur-a-wafer-thin-mint&quot;&gt;And finally, monsieur, a wafer-thin mint&lt;/h2&gt;

&lt;h3 id=&quot;le-code&quot;&gt;Le code&lt;/h3&gt;

&lt;p&gt;Voilà la &lt;a href=&quot;https://github.com/1024pix/pix/pull/5787&quot;&gt;pull request principale&lt;/a&gt; :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;+35/-40 k fichiers&lt;/li&gt;
  &lt;li&gt;132 commits&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/1024pix/pix/pull/5787/commits/2f561f5af281948662bd86d3eef491cf122a3919&quot;&gt;les codemods&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;canary-release&quot;&gt;Canary release&lt;/h3&gt;

&lt;p&gt;Le canary release a été implémenté avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ningx&lt;/code&gt; depuis &lt;a href=&quot;https://github.com/1024pix/pix/blob/dev/mon-pix/servers.conf.erb#L30&quot;&gt;les serveurs front-end&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Il était paramétrable dynamiquement via des scripts bash, en modifiant les variables d’environnement du PaaS &lt;a href=&quot;https://doc.scalingo.com/platform/cli/start&quot;&gt;via le CLI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Il a complexifié le workflow de déploiement, en ajoutant des interventions manuelles à un workflow sur lequel les PO étaient autonomes.
&lt;img src=&quot;/assets/images/posts/migrer-de-commonjs-vers-esm-migration/workflow-canary.png&quot; alt=&quot;workflow canary&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Nous avons limité la complexité de coordination entre PO et développeurs en modifiant le workflow de déploiement Slack, pour y mentionner les actions manuelles.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/migrer-de-commonjs-vers-esm-migration/canary-slack.png&quot; alt=&quot;workflow MEP slack &quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;coût-de-coordination-et-complexité&quot;&gt;Coût de coordination et complexité&lt;/h3&gt;

&lt;p&gt;Un problème qui ne peut être résolu seul demande de la coordination, qui a un coût.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In tasks that can be partitioned but which require communication among the subtasks, the effort of communication must be added to the amount of work to be done. The added burden of communication is made up of two parts, training and intercommunication. Each worker must be trained in the technology, the goals of the effort, the overall strategy, and the plan of work. This training cannot be partitioned, so this part of the added effort varies linearly with the number of workers. Intercommunication is worse. If each part of the task must be separately coordinated with each other part, the effort increases as n(n-1)/2. Three workers require three times as much pairwise intercommunication as two; four require six times as much as two.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;If, moreover, there need to be conferences among three, four, etc., workers to resolve things jointly, matters get worse yet. The added effort of communicating may fully counteract the division of the original task&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;in Fred Brooks, The mythical man-month, Chapter “The Man-month”, page 18&lt;/p&gt;

&lt;h3 id=&quot;estimation&quot;&gt;Estimation&lt;/h3&gt;

&lt;p&gt;L’informatique n’est plus si jeune, les développeurs non plus, et nous sommes toujours obstinément optimistes. Le mystère reste entier..&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;All programmers are optimists.&lt;/p&gt;

  &lt;p&gt;Perhaps this modern sorcery especially attracts those who believe in happy endings and fairy godmothers. Perhaps the hundreds of nitty frustrations drive away all but those who habitually focus on the end goal. Perhaps it is merely that computers are young, programmers are younger, and the young are always optimists.&lt;/p&gt;

  &lt;p&gt;But however the selection process works, the result is indisputable: ‘This time it will surely run,’’ or “I just found the last bug.”. So the first false assumption that underlies the scheduling of systems programming is that all will go well, i.e., that each task will take only as long as it “ought” to take.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;in Fred Brooks, The mythical man-month, Chapter “Optimism”, page 15&lt;/p&gt;</content><author><name></name></author><category term="javascript" /><summary type="html">Récit d'une aventure : migrer 400kLoc NodeJs en deux mois - #2: le faire</summary></entry><entry><title type="html">Migrer de CommonJS vers ECMAScript Modules #1</title><link href="https://engineering.pix.fr/javascript/2023/08/25/migrer-de-commonjs-vers-esm.html" rel="alternate" type="text/html" title="Migrer de CommonJS vers ECMAScript Modules #1" /><published>2023-08-25T07:49:00+00:00</published><updated>2023-08-25T07:49:00+00:00</updated><id>https://engineering.pix.fr/javascript/2023/08/25/migrer-de-commonjs-vers-esm</id><content type="html" xml:base="https://engineering.pix.fr/javascript/2023/08/25/migrer-de-commonjs-vers-esm.html">&lt;h2 id=&quot;ya-quoi-au-menu-&quot;&gt;Y’a quoi au menu ?&lt;/h2&gt;
&lt;p&gt;Cet article partage avec vous deux mois d’efforts (et de doutes, aussi) pour migrer une base de code NodeJS de 400 kLoC. Spoiler : nous y sommes arrivés !&lt;/p&gt;

&lt;p&gt;Vous y trouverez de l’histoire, de la syntaxe, des considérations techniques et organisationnelles, de l’outillage et du monitoring : il y en a pour tous les goûts.&lt;/p&gt;

&lt;p&gt;Voilà, en entrée, le résumé du challenge à accomplir :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#une-histoire-de-modules&quot;&gt;comprendre&lt;/a&gt; ce qu’est un système de module ;&lt;/li&gt;
  &lt;li&gt;se souvenir &lt;a href=&quot;#les-modules-côté-javascript&quot;&gt;de ce qu’il en est&lt;/a&gt; en Javascript ;&lt;/li&gt;
  &lt;li&gt;savoir si ESM &lt;a href=&quot;#pourquoi-migrer-en-esm-&quot;&gt;peut t’être utile&lt;/a&gt; ;&lt;/li&gt;
  &lt;li&gt;comprendre &lt;a href=&quot;#un-peu-de-syntaxe&quot;&gt;la syntaxe ESM&lt;/a&gt; ;&lt;/li&gt;
  &lt;li&gt;comment &lt;a href=&quot;#modifier-le-code&quot;&gt;modifier le code&lt;/a&gt; (la &lt;a href=&quot;#un-peu-de-syntaxe&quot;&gt;syntaxe&lt;/a&gt; est un pré-requis).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si vous êtes déjà familier avec tout ça, attendez plutôt la sortie de l’article &lt;a href=&quot;/javascript/2023/08/25/migrer-de-commonjs-vers-esm.html&quot;&gt;en plat principal&lt;/a&gt;, à savoir comment nous avons mené la migration.&lt;/p&gt;

&lt;h2 id=&quot;une-histoire-de-modules&quot;&gt;Une histoire de modules&lt;/h2&gt;

&lt;p&gt;Dans la plupart des langages de programmation (C, Java, JavaScript…), le fichier de code (source) est un élément important. Il est implémenté dans le système de fichiers de l’OS, par un fichier texte. Il y a quelque chose de fondamentalement réconfortant à cela. Utiliser un langage qui stocke le code source dans des fichiers aux noms cryptiques et au contenu binaire, c’est une perte d’autonomie.&lt;/p&gt;

&lt;p&gt;Pourquoi ? Parce que ranger des fichiers dans des dossiers est une métaphore puissante : elle permet de regrouper ce qui se ressemble en traçant des frontières. C’est la même chose qui se passe au niveau en-dessous lorsqu’on crée des fonctions.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Aside from the computer itself, the routine is the single greatest invention in computer science. (…) Create a routine to hide information so that you won’t need to think about it. (…) Without the abstractive power of routines, complex programs would be impossible to manage intellectually.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;in Steve McConnell, Code Complete - Chapter 7 : High-quality routine&lt;/p&gt;

&lt;p&gt;En théorie informatique, ce genre de découpage (de décomposition) porte le nom de &lt;a href=&quot;https://en.wikipedia.org/wiki/Modular_programming&quot;&gt;module&lt;/a&gt;, un terme déjà utilisé en &lt;a href=&quot;https://www.cnrtl.fr/definition/module&quot;&gt;construction&lt;/a&gt; pour exprimer la capacité à bâtir un système en assemblant des composants. La réutilisation de code, sous forme de librairie, en fait partie.&lt;/p&gt;

&lt;h2 id=&quot;les-modules-côté-javascript&quot;&gt;Les modules côté Javascript&lt;/h2&gt;

&lt;p&gt;La notion de module est assez large en Javascript : on désigne parfois la même chose par librairie, package, script, balise &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, module, fichier … Revenons en arrière pour mieux comprendre.&lt;/p&gt;

&lt;h3 id=&quot;tout-débute-dans-un-navigateur&quot;&gt;Tout débute dans un navigateur&lt;/h3&gt;

&lt;p&gt;Au début de Javascript (1995), il n’y avait pas de système de module natif : tout le code était contenu dans la balise &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;. Pour que les “pages web” (contenu statique) puissent devenir des applications web (contenu dynamique), des fonctionnalités génériques (ex : appeler une API REST) sont distribuées sous forme de librairies Js. Elles pouvaient être importées depuis un fichier via l’attribut &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src&lt;/code&gt; de la balise &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;script&lt;/code&gt;, par exemple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;script src=&quot;my-library.js&quot;&amp;gt;&lt;/code&gt;. Le code de l’application (composant) pouvait aussi être modularisé ainsi.&lt;/p&gt;

&lt;h3 id=&quot;un-petit-tour-côté-serveur&quot;&gt;Un petit tour côté serveur&lt;/h3&gt;

&lt;p&gt;Le passage de Javascript côté serveur (2009) changea l’approche.&lt;/p&gt;

&lt;p&gt;Dans NodeJS, les libraires (packages) :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;déclarent leur interface (API) avec le mot-clef &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt; (notion de module) ;&lt;/li&gt;
  &lt;li&gt;sont enregistrées par l’application dans le fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt; ;&lt;/li&gt;
  &lt;li&gt;sont installées par npm dans le fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_modules&lt;/code&gt; (encore le module) ;&lt;/li&gt;
  &lt;li&gt;sont importées avec le mot-clef &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Les composants internes de l’application utilisent les mêmes mot-clefs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt; et &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module.exports&lt;/code&gt;. Il n’y a pas de différence visible entre l’import d’une libraire et d’un composant, à part le fait qu’une libraire est mentionnée par son nom et le composant par son chemin.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// library&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;pick&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;lodash&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// component&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;controller&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./lib/application/certification/certification-controller&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;happy-end-&quot;&gt;Happy end ?&lt;/h3&gt;

&lt;p&gt;Maintenant que nous sommes familiers avec le concept de modules dans Javascript, nous arrivons enfin à ESM.&lt;/p&gt;

&lt;p&gt;Actuellement, il y a plusieurs systèmes de modules en Javascript : AMD, CJS, ESM, UMD.&lt;/p&gt;

&lt;p&gt;Le système CommonJs (en abrégé CJS) est &lt;a href=&quot;https://github.com/wooorm/npm-esm-vs-cjs&quot;&gt;le plus utilisé&lt;/a&gt; côté serveur, car :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;CJS était fourni avec NodeJs ;&lt;/li&gt;
  &lt;li&gt;il n’existait pas de système de module natif, partagé par les navigateurs et NodeJs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En 2015, le comité chargé du langage met un terme à cette situation, à savoir :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;le comité TC39 (chargé de Javascript)&lt;/li&gt;
  &lt;li&gt;de l’organisation de normalisation ECMA&lt;/li&gt;
  &lt;li&gt;publie dans la version 2015&lt;/li&gt;
  &lt;li&gt;la spécification ECMAScript module, en abrégé ESM.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cinq ans plus tard, en 2020, les navigateurs web et NodeJs (v12) l’ont implémenté de manière stable.&lt;/p&gt;

&lt;h2 id=&quot;pourquoi-migrer-en-esm-&quot;&gt;Pourquoi migrer en ESM ?&lt;/h2&gt;

&lt;p&gt;La base de code Pix, créée en 2016, utilise le format CJS. Si le format qu’elle utilise est éprouvé, pourquoi migrer pour un système qui n’a que trois ans de support officiel ? La décision tient à une raison : faciliter le passage à Typescript.&lt;/p&gt;

&lt;p&gt;Si vous n’êtes pas dans cette situation et cherchez ce que ESM pourrait vous apporter, nous conseillons &lt;a href=&quot;https://webreflection.medium.com/cjs-vs-esm-5f8b90a4511a&quot;&gt;cet article&lt;/a&gt;, écrit par un membre de &lt;a href=&quot;https://github.com/nodejs/modules&quot;&gt;l’équipe&lt;/a&gt; qui a implémenté ESM dans NodeJS&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;CJS vs ESM is not a war, rather a topic to talk even more about, once constraints and respective features are clear, as opposite of just taking a side out of habits, or effort needed, to move on (to ESM).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Voilà tout de même les avantages d’expérience de développement après quelques mois d’utilisation :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Strict_mode&quot;&gt;mode strict&lt;/a&gt; actif ;&lt;/li&gt;
  &lt;li&gt;autocomplétion des imports par les IDE ;&lt;/li&gt;
  &lt;li&gt;vérification &lt;a href=&quot;https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/named.md&quot;&gt;statique&lt;/a&gt; des imports, absente en CJS;&lt;/li&gt;
  &lt;li&gt;syntaxe moins permissive, qui améliore la lisibilité du code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour illustrer la syntaxe, voilà une version valide en CJS.&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./ham&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;carrot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./garden&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;digCarrot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;potatoes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./field&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;harvestPotatoes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./tools&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;basket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/* some code */&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;some_condition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()){&lt;/span&gt;
   &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./orchard&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;spray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;omelette&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./egg&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/* some code */&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ham&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;someFunction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./spam&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;someFunction&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voilà la version équivalente en ESM :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;les imports sont visibles en haut du fichier ;&lt;/li&gt;
  &lt;li&gt;l’interface (= les exports) est claire.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./Ham.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;egg&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./egg.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;someFunction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./spam.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;digCarrot&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./garden.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;spray&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./orchard.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;harvestPotatoes&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./field&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;basket&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./tools&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;potatoes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;digPotatoes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;basket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;carrot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;digCarrot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/* some code */&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;some_condition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
   &lt;span class=&quot;nx&quot;&gt;spray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;omelette&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cook&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;egg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;cm&quot;&gt;/* some code */&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ham&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nx&quot;&gt;ham&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;nx&quot;&gt;someFunction&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;un-peu-de-syntaxe&quot;&gt;Un peu de syntaxe&lt;/h2&gt;

&lt;p&gt;Pour que vous puissiez suivre la démarche de migration, il est important d’avoir les bases de la syntaxe ESM. Partons du principe que vous connaissez &lt;a href=&quot;https://apprendre-nodejs.fr/v1/chapter-04/index.html#modules&quot;&gt;la syntaxe CJS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Tous les composants (nombre, objets, fonctions, classes) peuvent être exposés dans les modules ESM. Ils portent un nom d’export. Celui-ci peut être remplacé par un autre lors de l’import dans un autre module. Il est aussi possible de supprimer leur nom à l’export, pour le remplacer par le nom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;. Il ne s’agit pas à proprement parler d’export anonyme, simplement d’un export nommé &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;, aussi appelé export par défaut.&lt;/p&gt;

&lt;p&gt;Les imports et exports doivent être à la racine du fichier (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;top-level&lt;/code&gt;).
Ils sont interdits dans une structure de contrôle ou une fonction.&lt;/p&gt;

&lt;p&gt;Les imports doivent figurer en début de fichier.
Leur syntaxe est la suivante, du plus simple au plus complexe :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import from './module.js'&lt;/code&gt; : si l’on ne veut pas récupérer d’import, uniquement exécuter du code&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import module from './module.js'&lt;/code&gt; : pour récupérer l’export par défaut et le nommer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;module&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import { foo, bar } from './module.js'&lt;/code&gt; : pour récupérer certains exports nommés&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import { foo , bar as baz } from './module.js'&lt;/code&gt; : même chose, en les renommant&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import * as module from './module.js'&lt;/code&gt; : pour récupérer tous les exports nommés dans un objet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La syntaxe des exports est la suivante, du plus simple au plus complexe :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export foo;&lt;/code&gt; : exporter un objet, peut être invoqué plusieurs fois&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export default foo&lt;/code&gt; : export par défaut (l’objet exporté perd son nom &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;foo&lt;/code&gt; pour prendre celui de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export { foo, bar }&lt;/code&gt; : export de plusieurs objets&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export { foo , bar as baz }&lt;/code&gt; : même chose, avec renommage&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export { foo, bar } from './module.js&lt;/code&gt; : vous avez bien lu, on importe les exports nommés et on les exporte (ré-export)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export * from './module.js&lt;/code&gt; : même chose, pour sélectionner tous les exports nommés&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si vous êtes curieux de connaître l’implémentation de ESM, &lt;a href=&quot;https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/&quot;&gt;cet article&lt;/a&gt; est une excellente introduction sur les modules en général et ESM en particulier.&lt;/p&gt;

&lt;h2 id=&quot;modifier-le-code&quot;&gt;Modifier le code&lt;/h2&gt;

&lt;h3 id=&quot;une-solution-simple&quot;&gt;Une solution simple&lt;/h3&gt;

&lt;p&gt;Le passage de CJS à ESM concerne tous les fichiers avec au moins un import/export, autrement dit tous les fichiers.
Étant donné la volumétrie, une modification de code manuelle est exclue. Quelles sont les solutions pour modifier du code ?&lt;/p&gt;

&lt;p&gt;Si les modifications sont mineures, par exemple supprimer le &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s&lt;/code&gt; de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exports&lt;/code&gt;, les outils quotidiens du développeur suffisent : soit IDE (Find/Replace avec expressions régulières), soit un script bash utilisant find, grep, sed. Si l’on veut éviter d’écrire du bash, une solution est de modifier le fichier avec NodeJs, en utilisant la &lt;a href=&quot;https://nodejs.dev/en/learn/reading-files-with-nodejs/&quot;&gt;librairie I/O&lt;/a&gt; standard. Les lignes de code sont des chaînes de caractère comme les autres, après tout.&lt;/p&gt;

&lt;p&gt;L’affaire se complique si la transformation modifie la syntaxe et pas seulement un mot-clef.&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// CJS&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./ham&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ESM&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./Ham.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Car, même si transformer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const { &amp;lt;TOKEN&amp;gt; }&lt;/code&gt; en  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import { &amp;lt;TOKEN }&lt;/code&gt; semble simple, il faut aussi éviter les effets de bord sur des expressions qui y ressemblent, mais qui n’en sont pas, par exemple les imports nommés non sélectifs.&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// CJS&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;egg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./egg.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ESM&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;egg&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./egg.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et que dire si la transformation est multi-lignes ?&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// CJS&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ham&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./ham&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ESM&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./Ham.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ham&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et encore, ici les lignes sont consécutives, alors qu’il est indispensable en réalité de pouvoir modifier les exports, qui peuvent être n’importe où dans le fichier.&lt;/p&gt;

&lt;p&gt;Bref, il nous fallait une autre solution. Nous avons cherché comment d’autres avaient fait, car nous ne sommes pas les seuls à vouloir migrer en ESM. Nous avons constaté que leurs solutions (&lt;a href=&quot;https://github.com/azu/commonjs-to-es-module-codemod#readme&quot;&gt;celle-ci&lt;/a&gt; ou &lt;a href=&quot;https://github.com/wessberg/cjstoesm&quot;&gt;celle-là&lt;/a&gt;) ne fonctionnaient pas sur notre codebase, mais elles nous ont mis sur la voie.&lt;/p&gt;

&lt;p&gt;Entrons dans le monde de l’AST, dirigé par les &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codemods&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;une-solution-complexe-et-puissante--les-codemods&quot;&gt;Une solution complexe et puissante : les codemods&lt;/h3&gt;

&lt;h4 id=&quot;last&quot;&gt;L’AST&lt;/h4&gt;
&lt;p&gt;Les outils dédiés à la modification de code sont appelés “code modificators” (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;codemods&lt;/code&gt; en abrégé).  Ils s’appuient sur une représentation du fichier plus riche qu’une suite de chaînes de caractères, pour que nous puissions, par exemple :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;sélectionner un import nommé sélectif CJS ;&lt;/li&gt;
  &lt;li&gt;mais pas un import par défaut, ou un import nommé global ;&lt;/li&gt;
  &lt;li&gt;et le remplacer par un autre import nommé, cette fois-ci en ESM, ainsi qu’une déclaration de variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour cela, ils vont effectuer un travail similaire à l’interpréteur NodeJS :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;un lexer qui transforme une chaîne de caractères en “mots” (tokens)&lt;/li&gt;
  &lt;li&gt;un parser qui transforme cette suite de tokens en éléments du langage, organisés en arbre.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cet arbre s’appelle un “Abstract Syntax Tree” (AST).&lt;/p&gt;

&lt;h5 id=&quot;sélectionner-un-élément-à-modifier&quot;&gt;Sélectionner un élément à modifier&lt;/h5&gt;

&lt;p&gt;Voyons ce que donne le fichier contenant un import CJS dans un &lt;a href=&quot;https://rajasegar.github.io/ast-builder/&quot;&gt;constructeur d’AST&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;egg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./egg.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;AST&lt;/p&gt;
&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;VariableDeclaration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;declarations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;VariableDeclarator&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Identifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;egg&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;init&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CallExpression&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;callee&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Identifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;require&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;arguments&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Literal&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./egg.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;raw&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;'./egg.js'&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;kind&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;const&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Cet “Abstract Syntax Tree” (AST) peut être interrogé, tout comme on interroge le DOM avec Xpath: la requête s’appelle un sélecteur.&lt;/p&gt;

&lt;p&gt;Ici, nous voulons récupérer les imports nommés globaux, le sélecteur qui nous intéresse est un nœud :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;avec affectation à une variable (sans invocation de code), donc de type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VariableDeclaration&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;sans déstructuration, donc avec un descendant de type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Identifier&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;d’import CJS, donc avec un descendant de type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CallExpression&lt;/code&gt; et de name &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Les autres syntaxes d’import (ex : import sélectif) ne sont pas inclus pour la lisibilité, mais il faut tous les comparer pour trouver leurs points communs et leurs différences, pour éviter les faux :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;positifs : sélectionner un import indésirable ;&lt;/li&gt;
  &lt;li&gt;négatifs : ne pas sélectionner un import désiré.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;modifier-lélément-sélectionné&quot;&gt;Modifier l’élément sélectionné&lt;/h5&gt;

&lt;p&gt;Une fois le sélecteur écrit, il faut réécrire le code.
Nous avons utilisé un outil JS, &lt;a href=&quot;https://github.com/facebook/jscodeshift&quot;&gt;jscodeshift&lt;/a&gt;, comme exemple ci-dessous.
Dans Jscodeshift, le code ne peut pas être modifié “sur place” via des mutations, il est remplacé par du code que nous allons générer. La librairie sous-jacente, &lt;a href=&quot;https://github.com/benjamn/recast&quot;&gt;recast&lt;/a&gt;, fournit des constructeurs pour les éléments du langage.&lt;/p&gt;

&lt;p&gt;Pour générer le code ESM suivant&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;egg&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./egg.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;La syntaxe est&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;variableDeclaration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;variableDeclarator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;identifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;egg&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;callExpression&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;identifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;literal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./egg.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notez que certains paramètres de fonction doivent être récupérés dans l’AST précédent :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;le nom de la constante &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;egg&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;le chemin du fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./egg.js&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si on assemble tous les éléments, le codemod ressemble à &lt;a href=&quot;https://github.com/1024pix/pix/pull/5787/commits/1f5f207361cad7a04037429408b2f9545a176e4a&quot;&gt;ça&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;un-périmètre-de-travail-parfois-limité&quot;&gt;Un périmètre de travail parfois limité&lt;/h4&gt;

&lt;p&gt;Jscodeshift suit les étapes suivantes :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;lire (récursivement) tous les fichiers d’un répertoire ;&lt;/li&gt;
  &lt;li&gt;sur chaque fichier :
    &lt;ul&gt;
      &lt;li&gt;générer l’AST ;&lt;/li&gt;
      &lt;li&gt;appliquer les sélecteurs ;&lt;/li&gt;
      &lt;li&gt;pour chaque sélection qui matche, appeler le constructeur de code ;&lt;/li&gt;
      &lt;li&gt;assembler le code obtenu et réécrire le fichier d’origine.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le plus grand périmètre de travail est le fichier, tout comme dans eslint.
Même si Jscodeshift traite les fichiers en parallèle, il n’est pas possible d’accéder à l’AST d’un autre fichier depuis l’AST courant. La conséquence : il n’est pas possible de faire une modification qui impacte deux fichiers.&lt;/p&gt;

&lt;p&gt;Si les modifications de code ne concernent que l’intérieur d’une fonction, cela ne pose pas de problème.
Dans les cas d’ESM, qui par définition traite des interfaces entre fichiers, cela a été une sérieuse limitation.&lt;/p&gt;

&lt;h3 id=&quot;se-faire-aider&quot;&gt;Se faire aider&lt;/h3&gt;

&lt;h4 id=&quot;tests-automatisés&quot;&gt;Tests automatisés&lt;/h4&gt;

&lt;p&gt;Il n’est pas possible, à part avec une profonde expérience du langage et des AST, de se représenter tous les cas possibles.
Il est donc à peu près exclu de garantir que tous les cas seront traités et qu’ils le soient correctement.&lt;/p&gt;

&lt;p&gt;Une suite de tests automatisés est donc indispensable pour :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;d’une part, documenter les cas traités ;&lt;/li&gt;
  &lt;li&gt;détecter les cas qui ne sont pas bien traités ;&lt;/li&gt;
  &lt;li&gt;éviter les régressions, à savoir modifier du code qui ne doit pas être modifié.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jscodeshift embarque un framework de test dédié et les cas de test sont lisibles. Ils contiennent le code en entrée (given) et celui attendu en sortie (then).&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// input.js&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;datasource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./datasource.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// output.js&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;datasource&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./datasource.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id=&quot;lint&quot;&gt;Lint&lt;/h4&gt;

&lt;p&gt;Après l’exécution du codemod, si l’exécution du fichier retourne une erreur, c’est que le codemod est incorrect. Si l’on inspecte le contenu du fichier, on peut constater l’erreur et corriger le codemod. Mais parfois, on a besoin d’une vue d’ensemble pour modifier le codemod pour s’assurer que tous les imports référencent un fichier existant ?&lt;/p&gt;

&lt;p&gt;C’est là que le plugin &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eslint-plugin-import&lt;/code&gt; peut faire gagner du temps, par exemple en remontant tous les cas d’imports non résolus avec la règle &lt;a href=&quot;https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unresolved.md&quot;&gt;no-unresolved&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Il peut aussi y avoir beaucoup de violations.&lt;/p&gt;

&lt;p&gt;Pour y voir plus clair, vous pouvez :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;utiliser le package &lt;a href=&quot;https://github.com/IanVS/eslint-nibble&quot;&gt;eslint-nibble&lt;/a&gt; qui groupe par règle ;&lt;/li&gt;
  &lt;li&gt;exécuter une seule règle sur tous les fichiers avec l’option &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--rule&lt;/code&gt; de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eslint&lt;/code&gt; : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx eslint --rule eslint-plugin-import/no-unresolved ./lib&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;aussi utiliser des expressions régulières avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sed&lt;/code&gt; pour extraire la liste des fichiers à modifier.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;heuristique-dautomatisation&quot;&gt;Heuristique d’automatisation&lt;/h3&gt;

&lt;p&gt;Maintenant que vous savez qu’il est possible de modifier du code complexe, va-t-on écrire des codemods pour gérer tous les cas possibles ? Sinon, quels sont les codemods qu’il faut absolument écrire ? Voilà quelques règles, inspirées de &lt;a href=&quot;https://xkcd.com/1205/&quot;&gt;la matrice d’automatisation&lt;/a&gt;.&lt;/p&gt;

&lt;h5 id=&quot;cas-1---evidence--il-faut-automatiser&quot;&gt;Cas 1 - Evidence : il faut automatiser&lt;/h5&gt;
&lt;p&gt;Si une fonctionnalité CJS est reprise telle quelle dans ESM et qu’elle est utilisée dans la plupart de la codebase, écrire le codemod. Il pourrait être réalisé en script bash, mais le framework de test de jscodeshift évite les erreurs.&lt;/p&gt;

&lt;p&gt;C’est le cas de l’import nommé.&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// CJS&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;egg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./egg.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ESM&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;egg&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./egg.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;cas-2---questionnement--faut-il-automatiser-&quot;&gt;Cas 2 - Questionnement : faut-il automatiser ?&lt;/h5&gt;
&lt;p&gt;Si une fonctionnalité CJS n’est pas reprise dans ESM et est facilement simulable (ex : avec plusieurs instructions au lieu d’une), alors se demander si elle est utilisée dans la plupart de la codebase.&lt;/p&gt;

&lt;p&gt;Si c’est le cas, écrire le codemod. Si ce n’est pas le cas, modifier les fichiers manuellement.&lt;/p&gt;

&lt;p&gt;C’est le cas CJS de l’import + invocation dans la même instruction, remplacée par un import nommé avec assignation + invocation dans une autre instruction en ESM.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// CJS&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ham&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./ham&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ESM&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./Ham.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ham&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Ham&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h5 id=&quot;cas-3---impasse--il-est-impossible-dautomatiser&quot;&gt;Cas 3 - Impasse : il est impossible d’automatiser&lt;/h5&gt;
&lt;p&gt;Si une fonctionnalité CJS n’est pas reprise dans ESM et qu’elle n’est pas simulable facilement, la seule solution est de modifier le fichier manuellement.&lt;/p&gt;

&lt;p&gt;C’est le cas des &lt;a href=&quot;https://2ality.com/2015/07/es6-module-exports.html&quot;&gt;exports immuables&lt;/a&gt; en ESM. Tous les tests CJS qui substituent une &lt;a href=&quot;https://martinfowler.com/bliki/TestDouble.html&quot;&gt;doublure de test&lt;/a&gt; à la volée ne sont plus valables : seule &lt;a href=&quot;https://github.com/1024pix/pix/commit/027b560b42ef72731662559f906de33ff749a008&quot;&gt;l’injection des dépendances&lt;/a&gt; permet de résoudre le problème. Bien sûr, il est possible en théorie d’écrire un codemod qui injecterait les dépendances : à vous de voir le coût.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Nous avons vu les avantages d’ESM et qu’il était possible d’automatiser la migration du code.&lt;/p&gt;

&lt;p&gt;Si vous envisagez une migration, le plus important est de garder l’&lt;a href=&quot;#heuristique-dautomatisation&quot;&gt;heuristique d’automatisation&lt;/a&gt; en tête et de vous concentrer sur le cas 2, où l’automatisation peut être envisagée, mais pas systématique. C’est ce cas qui demande le plus de recul, les deux autres cas étant évidents.&lt;/p&gt;

&lt;p&gt;Il est en effet possible de :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;sous-estimer le coût de développement d’un codemod ;&lt;/li&gt;
  &lt;li&gt;mais aussi de sous-estimer le caractère répétitif et frustrant de modifier des centaines de fichiers manuellement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous vous conseillons de suivre votre intuition en se fixant des limites pour éviter &lt;a href=&quot;https://en.wikipedia.org/wiki/Sunk_cost&quot;&gt;le biais d’investissement&lt;/a&gt;, puis d’apprendre en essayant. Si tout ne se déroule pas comme prévu, s’autoriser à revenir en arrière.&lt;/p&gt;

&lt;p&gt;Par exemple, si l’on estime que :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;la modification manuelle de fichier prend 1 jour ;&lt;/li&gt;
  &lt;li&gt;le codemod prend 2 jours ;&lt;/li&gt;
  &lt;li&gt;et qu’au bout d’un jour de correction manuelle, on comprend qu’il reste en fait 3 jours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alors, on peut supprimer les modifications manuelles de fichiers et développer un codemod. Bien sûr, le développement prendra peut-être plus de temps, mais parfois les modifications manuelles peuvent saper l’énergie de l’équipe. Tout est question de contexte.&lt;/p&gt;

&lt;h2 id=&quot;bonus&quot;&gt;Bonus&lt;/h2&gt;

&lt;p&gt;ESM apporte aussi les modifications suivantes :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;remplacement des pseudo-variables &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__dirname&lt;/code&gt; et &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__filename&lt;/code&gt; par la fonction &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fileURLToPath&lt;/code&gt; de la librairie &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url&lt;/code&gt; ;&lt;/li&gt;
  &lt;li&gt;l’extension de fichier devient obligatoire dans l’import, exemple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;from './egg.js'&lt;/code&gt; ;&lt;/li&gt;
  &lt;li&gt;la lecture de fichier JSON demande une clause supplémentaire &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert { type: 'json' }&lt;/code&gt;, exemple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;import packageJSON from '../package.json' assert { type: 'json' };&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voilà quelques références pour démarrer les codemods :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://medium.com/@andrew_levine/writing-your-very-first-codemod-with-jscodeshift-7a24c4ede31b&quot;&gt;un tutoriel JScodeshift&lt;/a&gt; ;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://astexplorer.net/&quot;&gt;une sandbox&lt;/a&gt; pour écrire des codemods (activez l’option Transform avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;recast&lt;/code&gt;);&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/benjamn/ast-types/tree/master/src/def&quot;&gt;la définition&lt;/a&gt; des constructeurs pour générer le code.&lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><category term="javascript" /><summary type="html">Récit d'une aventure : migrer 400kLoc NodeJs en deux mois - #1: les enjeux</summary></entry><entry><title type="html">Tester unitairement les composants sous Embroider</title><link href="https://engineering.pix.fr/tests/2023/05/22/tester-unitairement-les-composants-sous-embroider.html" rel="alternate" type="text/html" title="Tester unitairement les composants sous Embroider" /><published>2023-05-22T10:27:00+00:00</published><updated>2023-05-22T10:27:00+00:00</updated><id>https://engineering.pix.fr/tests/2023/05/22/tester-unitairement-les-composants-sous-embroider</id><content type="html" xml:base="https://engineering.pix.fr/tests/2023/05/22/tester-unitairement-les-composants-sous-embroider.html">&lt;p&gt;Nous avons récemment migré nos applications Ember.js sous &lt;a href=&quot;https://github.com/embroider-build/embroider/&quot;&gt;Embroider&lt;/a&gt;,
le nouveau builder pour Ember.js. Embroider offre une fonctionnalité intéressante appelée “route splitting”,
qui permet d’envoyer uniquement les fichiers JavaScript nécessaires pour une ou un groupe de routes.
Cela réduit la taille du JavaScript téléchargé par les utilisateurs, à condition que le découpage des routes soit bien pensé.&lt;/p&gt;

&lt;p&gt;Cependant, l’activation de cette fonctionnalité nécessite plusieurs options, dont &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;staticComponents&lt;/code&gt;,
qui a pour effet de résoudre tous les composants nécessaires et de les inclure dans la construction finale.&lt;/p&gt;

&lt;p&gt;En activant cette option et en suivant &lt;a href=&quot;https://github.com/embroider-build/embroider/blob/main/docs/replacing-component-helper.md&quot;&gt;le guide de migration pour les composants helpers&lt;/a&gt;,
les tests unitaires de nos composants ne passaient plus.&lt;/p&gt;

&lt;h2 id=&quot;le-défi-des-tests-unitaires-avec-embroider-et-les-composants-glimmer&quot;&gt;Le défi des tests unitaires avec Embroider et les composants Glimmer&lt;/h2&gt;

&lt;p&gt;Lors de la migration vers les composants Glimmer, nous nous étions rendu compte que Glimmer ne supportait plus nativement les tests unitaires,
nous avions utilisé une parade proposée par la communauté sur le &lt;a href=&quot;https://discord.gg/emberjs&quot;&gt;Discord d’Ember&lt;/a&gt;,
que vous pouvez retrouver en détail sur &lt;a href=&quot;https://timgthomas.com/2019/11/unit-testing-glimmer-components/&quot;&gt;cet article&lt;/a&gt; et dont voici la solution :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getContext&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@ember/test-helpers&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GlimmerComponentManager&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@glimmer/component/-private/ember-component-manager&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lookupPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;named&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;owner&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;componentClass&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;factoryFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lookupPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;componentManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GlimmerComponentManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;componentManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;componentClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;named&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Qui permet dans un test unitaire de faire :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;component:component-name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;argument&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Cette solution est désormais obsolète, car avec Embroider et l’option &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;staticComponents&lt;/code&gt;
la méthode &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;factoryFor&lt;/code&gt; ne retourne rien.
Cela parait cohérent avec la volonté d’Embroider qui souhaite créer des builds statiques analysables.&lt;/p&gt;

&lt;h2 id=&quot;une-nouvelle-solution-avec-importsync-de-embroidermacros&quot;&gt;Une nouvelle solution avec importSync de @embroider/macros&lt;/h2&gt;

&lt;p&gt;La nouvelle solution que nous utilisons se sert  d’&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;importSync&lt;/code&gt; proposé par &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@embroider/macros&lt;/code&gt;,
qui permet à Embroider de signaler l’existence d’un module et de l’inclure dans le build.&lt;/p&gt;

&lt;p&gt;Voici donc le code avec la modification :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getContext&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@ember/test-helpers&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GlimmerComponentManager&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@glimmer/component/-private/ember-component-manager&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;importSync&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;@embroider/macros&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lookupPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;named&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;owner&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getContext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;componentClass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;importSync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`../../components/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lookupPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;componentManager&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GlimmerComponentManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;owner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;componentManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;componentClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;named&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et son usage&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;createComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;component-name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;argument&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Grâce à cette simple modification, nous pouvons à nouveau continuer de tester unitairement nos composants Glimmer et
commencer à utiliser les fonctionnalités proposées par Embroider.&lt;/p&gt;</content><author><name></name></author><category term="tests" /><summary type="html">Découvrez comment faire des tests unitaires de vos composants Glimmer dans votre application Ember.js utilisant Embroider.</summary></entry><entry><title type="html">Une montée de version PostgreSQL en PaaS surprenante</title><link href="https://engineering.pix.fr/bdd/2023/04/21/surprising-paas-database-upgrade.html" rel="alternate" type="text/html" title="Une montée de version PostgreSQL en PaaS surprenante" /><published>2023-04-21T12:27:00+00:00</published><updated>2023-04-21T12:27:00+00:00</updated><id>https://engineering.pix.fr/bdd/2023/04/21/surprising-paas-database-upgrade</id><content type="html" xml:base="https://engineering.pix.fr/bdd/2023/04/21/surprising-paas-database-upgrade.html">&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;Les Database-as-a-service, c’est pratique pour ne pas avoir trop d’expertise en interne. Mais parfois, on a quand même des surprises.&lt;/p&gt;

&lt;p&gt;Si vous :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;êtes hébergés par Scalingo;&lt;/li&gt;
  &lt;li&gt;utilisez les addons PostgreSQL;&lt;/li&gt;
  &lt;li&gt;souhaitez passer de la version 13.7 à 13.9.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alors prévoyez un créneau de maintenance pour recréer certains index.&lt;/p&gt;

&lt;p&gt;Pourquoi ?&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;parce qu’une mise à jour d’addon inclut, en plus de l’application “base de données”, potentiellement, des mises à jour de l’OS ;&lt;/li&gt;
  &lt;li&gt;un lien existe entre l’OS et les index qui utilisent &lt;a href=&quot;https://www.postgresql.org/docs/15/collation.html&quot;&gt;une collation&lt;/a&gt; par défaut.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;le-décor&quot;&gt;Le décor&lt;/h2&gt;

&lt;p&gt;Pix utilise un PaaS, &lt;a href=&quot;https://scalingo.com/&quot;&gt;Scalingo&lt;/a&gt;, pour l’hébergement de la plupart ses applications.
Il fournit un service de base de données sous la forme d’addon, dont la gestion (sauvegarde, PITR, réplication, maintenance) est garantie.
Nous utilisons ce service, car notre utilisation ne justifie pas la formation d’une équipe dédiée en interne.&lt;/p&gt;

&lt;p&gt;Nous gardons toutefois la main sur le déclenchement des montées de version, car celles-ci requièrent des interruptions de service &lt;a href=&quot;https://doc.scalingo.com/databases/postgresql/start#database-upgrade&quot;&gt;dans certains cas&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;If your database uses a business plan, we are able to achieve zero-downtime upgrade of minor version.
In the case of major version upgrade, we need to completely stop the nodes, hence we can’t achieve zero-downtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nous effectuons préalablement des tests automatisés:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;de non-régression sur la version de BDD &lt;a href=&quot;https://github.com/1024pix/pix/pull/5431&quot;&gt;en local et dans la CI&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;de performance sur l’environnement cible Scalingo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A la sortie de PostgreSQL 14 sur Scalingo &lt;a href=&quot;https://scalingo.com/blog/dbaas-postgresql14&quot;&gt;en janvier&lt;/a&gt;, nous avons déroulé notre scénario habituel.&lt;/p&gt;

&lt;h2 id=&quot;le-drame&quot;&gt;Le drame&lt;/h2&gt;

&lt;p&gt;Le lendemain de la mise à jour en production, le monitoring remonte des erreurs 500 sur plusieurs routes API.
Les erreurs n’étant pas nombreuses et limitées à l’authentification, nous analysons les logs de manière détaillée..
et nous rendons compte qu’il y a deux comptes actifs possédant le même email. Cela nous surprend, puisque nous nous sommes assurés que cela n’arriverait pas, grâce au mécanisme de &lt;a href=&quot;https://www.postgresql.org/docs/current/ddl-constraints.html#DDL-CONSTRAINTS-UNIQUE-CONSTRAINTS&quot;&gt;contrainte d’unicité&lt;/a&gt;
en base de données qui exclut cette possibilité.&lt;/p&gt;

&lt;p&gt;Fait troublant, les logs mentionnent aussi des requêtes de mise à jour de données rejetées pour cause de violations de contraintes.&lt;/p&gt;

&lt;p&gt;Une première requête nous rassure.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'john.doe@example.net'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;----------&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;john&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;net&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Mais une deuxième nous inquiète.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;666&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;----------&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;john&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;net&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;666&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;john&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;net&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Force est de constater qu’il y a plusieurs enregistrements :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;avec des identifiants différents ;&lt;/li&gt;
  &lt;li&gt;et un champ email identique.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Il existe pourtant &lt;a href=&quot;https://github.com/1024pix/pix/blob/dev/api/db/migrations/20170418114929_remove_login_from_users.js&quot;&gt;une contrainte UNIQUE&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Indexes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;&quot;users_email_unique&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;UNIQUE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CONSTRAINT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;btree&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nous ne savons pas pourquoi cela se produit, mais nous avons accepté le fait qu’il y ait des doublons.
Comme nous n’arrivons pas à récupérer tous les enregistrements en doublons (probablement une optimisation basée sur la contrainte d’unicité),
nous revenons à une détection bas-niveau avec un hash sur le champ en question.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;HAVING&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;hash&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;------------------------------------&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;40&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c05&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(..)&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;lenquête&quot;&gt;L’enquête&lt;/h2&gt;

&lt;p&gt;Nous sommes parti d’un seul indice : nous avions mis à jour la base de données la veille.
Notre hypothèse est que la mise à jour de PostgreSQL ne concerne à priori que la base de donnée, donc nous parcourons la documentation.
Et tombons sur &lt;a href=&quot;https://wiki.postgresql.org/wiki/Locale_data_changes#What_to_do&quot;&gt;ce paragraphe&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;When an instance needs to be upgraded to a new glibc release, for example to upgrade the operating system, then after the upgrade
all indexes involving columns of type text, varchar, char, and citext should be reindexed before the instance is put into production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Il existerait donc une dépendance entre la montée de version d’une librairie de l’OS (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glibc&lt;/code&gt;) et le tri dans la BDD, qui pourrait expliquer ce comportement, et pour laquelle il existe une solution (la reconstruction des index).&lt;/p&gt;

&lt;p&gt;Il n’y a à priori aucun lien entre une montée de version de la BDD et de l’OS qui la supporte, mais à bien y réfléchir, pourquoi pas ?
Scalingo met à disposition pour les conteneurs un environnement nommé &lt;a href=&quot;https://doc.scalingo.com/platform/internals/stacks/scalingo-22-stack&quot;&gt;stack&lt;/a&gt;, qui embarque l’OS.
Le service de base de données n’utilise pas cette notion de stack, pour la bonne raison que c’est un service managé. Une montée de version de l’addon peut donc inclure des mises à jour d’OS.&lt;/p&gt;

&lt;p&gt;Inspectons l’image docker dans ses versions successives sur le &lt;a href=&quot;https://hub.docker.com/r/scalingo/postgresql/tags&quot;&gt;repository Scalingo&lt;/a&gt;: on voit que &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;glibc&lt;/code&gt; a été mise à jour lors du passage de la version docker de 13.7 à 13.9.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; scalingo/postgresql:13.7.0-1 ldd &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
ldd &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Debian GLIBC 2.24-11+deb9u4&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2.24

docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; scalingo/postgresql:13.9.0-1 ldd &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
ldd &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Debian GLIBC 2.31-13+deb11u5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2.31
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Cette version est &lt;a href=&quot;https://postgresql.verite.pro/blog/2018/08/27/glibc-upgrade.html&quot;&gt;explicitement mentionnée&lt;/a&gt; comme embarquant des mises à jours de locales, lesquelles sont impliquées dans le tri exploité par les index, eux-mêmes utilisés pour implémenter la contrainte d’unicité. CQFD.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;For Postgres databases (..), it means that certain strings might sort differently after this upgrade.
A critical consequence is that indexes that depend on such collations must be rebuilt immediately after the &amp;gt; upgrade&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;sen-sortir&quot;&gt;S’en sortir&lt;/h2&gt;

&lt;p&gt;Si vous vous retrouvez dans la même situation que nous, à savoir que vous n’avez pas reconstruit les index, et que la base de données a été ouverte au traffic, voilà la solution que nous avons trouvée.&lt;/p&gt;

&lt;h3 id=&quot;détecter-les-index-impactés&quot;&gt;Détecter les index impactés&lt;/h3&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;indrelid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regclass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;indexrelid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regclass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;pg_get_indexdef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indexrelid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;collname&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collation_type&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indexrelid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indrelid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indcollation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coll&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pg_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_subscripts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indcollation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pg_collation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oid&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collprovider&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'d'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'c'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collname&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'C'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'POSIX'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://wiki.postgresql.org/wiki/Locale_data_changes#What_indexes_are_affected&quot;&gt;Wiki PostgrSQL&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;rechercher-les-données-invalides&quot;&gt;Rechercher les données invalides&lt;/h3&gt;

&lt;p&gt;Rechercher les doublons sur les champs protégés par les contraintes uniques utilisant les index détectés ci-dessus.
En effet, la reconstruction d’index unique n’est pas possible si les données sont en doublon.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;HAVING&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Déterminer quel enregistrement doit être supprimé, car créé par erreur après la montée de version.
Pour cela, s’assurer qu’aucune données liée ne soit perdue.&lt;/p&gt;

&lt;h3 id=&quot;recréer-les-index&quot;&gt;Recréer les index&lt;/h3&gt;

&lt;p&gt;Il est temps de recréer les index, maintenant que les tables sont saines.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt;
   &lt;span class=&quot;s1&quot;&gt;'REINDEX INDEX '&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indexrelid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;regclass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;';'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indexrelid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indrelid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;indcollation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coll&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pg_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;generate_subscripts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;indcollation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pg_collation&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coll&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;oid&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collprovider&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'d'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'c'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;collname&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'C'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'POSIX'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui nous donne :&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;REINDEX&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users_email_unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;REINDEX&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users_username_unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Assurez-vous de planifier un créneau de maintenance pour cela, la recréation d’index posant en général un verrou en écriture sur la table.
Il existe d’autres options prenant en compte les traitements concurrents, mais dans ce cas elles ne sont probablement pas indiquées.&lt;/p&gt;</content><author><name></name></author><category term="bdd" /><summary type="html">Les contraintes ne sont pas toujours respectées..</summary></entry><entry><title type="html">Passer une PK référencée du type INT en BIGINT</title><link href="https://engineering.pix.fr/bdd/2022/12/14/modifier-clef-primaire-referencee-type-bigint.html" rel="alternate" type="text/html" title="Passer une PK référencée du type INT en BIGINT" /><published>2022-12-14T14:59:42+00:00</published><updated>2022-12-14T14:59:42+00:00</updated><id>https://engineering.pix.fr/bdd/2022/12/14/modifier-clef-primaire-referencee-type-bigint</id><content type="html" xml:base="https://engineering.pix.fr/bdd/2022/12/14/modifier-clef-primaire-referencee-type-bigint.html">&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;Dans un &lt;a href=&quot;/bdd/2021/09/03/modifier-clef-primaire-non-referencee-type-bigint.html&quot;&gt;précédent article&lt;/a&gt;, nous partagions comment modifier une propriété du type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INT&lt;/code&gt; vers le type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BIGINT&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;si celle-ci n’était pas référencée par une autre table;&lt;/li&gt;
  &lt;li&gt;en minimisant le temps d’indisponibilité;&lt;/li&gt;
  &lt;li&gt;en modifiant les données dans une colonne temporaire de la table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Depuis, nous avons modifié une propriété référencée, en l’occurrence par une clef étrangère.
Nous avons essayé de migrer les données progressivement sur une copie de la table:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;pour limiter la fragmentation;&lt;/li&gt;
  &lt;li&gt;pour ne pas modifier l’organisation des données;&lt;/li&gt;
  &lt;li&gt;en modifiant l’allocation des ressources CPU/mémoire de la migration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous avons fini par comprendre que le facteur limitant était l’I/O, et que celle-ci, contrôlée par notre PaaS, ne pouvait être améliorée.&lt;/p&gt;

&lt;p&gt;Nous avons abandonné cette piste et utilisé un créneau de maintenance d’une journée pour migrer en utilisant la solution native.&lt;/p&gt;

&lt;p&gt;Nous avons surtout retenu de consulter le métier le plus tôt possible (ici pour se mettre d’accord sur les plages de maintenance planifiées), afin
de faire les choix techniques les mieux informés.&lt;/p&gt;

&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;/h2&gt;

&lt;p&gt;La &lt;a href=&quot;/bdd/2021/09/03/modifier-clef-primaire-non-referencee-type-bigint.html#motivation&quot;&gt;motivation reste la même&lt;/a&gt; que sur la propriété non référencée, pour rappel:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;pas d’interruption de service;&lt;/li&gt;
  &lt;li&gt;pas de dégradation des temps de réponse pendant l’ensemble des opérations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A l’arrivée, on souhaite que ces 3 éléments soient de type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BIGINT&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;la propriété elle-même : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; sur la table &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;answers&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;la séquence utilisée pour alimenter cette propriété : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;answers_id_seq&lt;/code&gt;;&lt;/li&gt;
  &lt;li&gt;les propriétés référençant cette propriété : clef étrangère &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;answerId&lt;/code&gt; sur la table &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;knowledge-elements&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;recherche-de-solution&quot;&gt;Recherche de solution&lt;/h2&gt;

&lt;h3 id=&quot;solution-1--instructions-natives-postgresql&quot;&gt;Solution 1 : instructions natives PostgreSQL&lt;/h3&gt;

&lt;p&gt;Nous avons commencé par obtenir le temps d’exécution de la solution native sur un environnement comparable à l’environnement cible.
Il servira de référence pour le temps maximal d’indisponibilité: si l’on trouve une solution avec un temps inférieur à la solution native, elle est retenue comme candidate.&lt;/p&gt;

&lt;p&gt;La solution native se résume à ces instructions, et prend 15h.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-postgres-sql&quot;&gt;ALTER TABLE &quot;knowledge-elements&quot; ALTER COLUMN &quot;answerId&quot; TYPE BIGINT;
ALTER TABLE &quot;flash-assessment-results&quot; ALTER COLUMN &quot;answerId&quot; TYPE BIGINT;
ALTER TABLE &quot;answers&quot; ALTER COLUMN &quot;id&quot; TYPE BIGINT;
ALTER SEQUENCE AS BIGINT;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Le temps d’exécution est partagé de manière à peu près égale entre les deux tables, et celles-ci sont successivement inaccessibles en lecture et en écriture.
Ces tables étant utilisées de manière conjointe, cela donne un temps d’indisponibilité de 15 heures.&lt;/p&gt;

&lt;h3 id=&quot;solution-2--migration-en-arrière-plan&quot;&gt;Solution 2 : migration en arrière plan&lt;/h3&gt;

&lt;h4 id=&quot;investigation&quot;&gt;Investigation&lt;/h4&gt;

&lt;p&gt;Les données à modifier sont:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;les méta-données de la table, utilisées lors de la création d’un enregistrement;&lt;/li&gt;
  &lt;li&gt;les données existantes brutes, c’est à dire les enregistrements de table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cependant, les données d’index doivent aussi être modifiées explicitement dans cette solution alors que ces modifications sont implicites dans les instructions natives.
Nous y reviendrons en détail dans la seconde partie.&lt;/p&gt;

&lt;h5 id=&quot;migration-des-données&quot;&gt;Migration des données&lt;/h5&gt;

&lt;h6 id=&quot;migration-dans-la-même-table&quot;&gt;Migration dans la même table&lt;/h6&gt;

&lt;p&gt;Comme dans &lt;a href=&quot;/bdd/2021/09/03/modifier-clef-primaire-non-referencee-type-bigint.html#implémentation&quot;&gt;l’article précédent&lt;/a&gt;, le temps d’indisponibilité ne peut être réduit qu’à condition d’effectuer:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;tout d’abord la plus grande partie du travail, la migration des données existantes, en tâche de fond;&lt;/li&gt;
  &lt;li&gt;et ensuite de migrer les données récentes lors d’un créneau de maintenance réduit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si la migration en tâche de fond est effectuée sur la même instance de base de données, ou la même table, elle peut impacter l’activité habituelle.&lt;/p&gt;

&lt;p&gt;C’est pour cela que nous commençons par &lt;a href=&quot;https://github.com/1024pix/pix/pull/3817&quot;&gt;un POC&lt;/a&gt;, qui migre les données:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;par batch;&lt;/li&gt;
  &lt;li&gt;avec un délai d’exécution;&lt;/li&gt;
  &lt;li&gt;pouvant être interrompu et relancé sans recommencer toute la migration;&lt;/li&gt;
  &lt;li&gt;avec des réglages (ex: taille de batch) modifiables en temps réel.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Devant cette complexité, qui nous expose à la corruption de données, nous choisissons d’implémenter:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;la migration en NodeJs, afin de disposer de tests automatisés;&lt;/li&gt;
  &lt;li&gt;la copie des données insérées entre-temps (qui ne peut être faite que coté serveur) via des triggers PostgreSQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le dispositif est le suivant:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;une migration de BDD crée:
    &lt;ul&gt;
      &lt;li&gt;la colonne de type BIGINT;&lt;/li&gt;
      &lt;li&gt;la table de paramétrage;&lt;/li&gt;
      &lt;li&gt;les triggers de copie ;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;un script client migre les données existantes:
    &lt;ul&gt;
      &lt;li&gt;il lit le paramétrage dans une table;&lt;/li&gt;
      &lt;li&gt;il migre un batch de données et trace les données traitées;&lt;/li&gt;
      &lt;li&gt;il relit le paramétrage au cas où il aurait été mis à jour;&lt;/li&gt;
      &lt;li&gt;il traite le prochain batch là où il s’était arrêté.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le code client a été réalisé:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;en TDD, avec des tests unitaires/intégration/acceptance (700 lignes sur les 1000 de la pull-request);&lt;/li&gt;
  &lt;li&gt;en Clean Architecture (voir le &lt;a href=&quot;https://github.com/1024pix/pix/pull/3817/files#diff-a0caa09715e2b8ff7e9d0e7bdd7b7f072862d3af9d290c0314a827467702e55b&quot;&gt;use-case&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h6 id=&quot;obstacle-la-fragmentation&quot;&gt;Obstacle: la fragmentation&lt;/h6&gt;

&lt;p&gt;Il y a un compromis à trouver en ce qui concerne la taille des batchs:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;plus le nombre de lignes traité par batch est petit, plus l’impact est réduit sur les autres requêtes;&lt;/li&gt;
  &lt;li&gt;plus le nombre de lignes traité par batch est grand, plus l’exécution de la migration est rapide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lors de la migration sur la table non référencée, nous avions effectué des batchs de 1 million d’enregistrements, pour
une durée moyenne de 2 minutes, en gardant une expérience utilisateur dégradée mais acceptable (pc98 de l’ordre de 100 ms =&amp;gt; 1 s).
Il est maintenant possible de baisser la taille de ces batchs à 100 000 enregistrements pour moins impacter le temps de réponse.
Mais il faut prendre en compte un autre facteur: la fragmentation des données dans la table.&lt;/p&gt;

&lt;p&gt;Lorsque PostgreSQL met à jour l’enregistrement (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UPDATE answers SET id_bigint = id&lt;/code&gt;), il ne met pas à jour l’original, mais insère un autre
enregistrement et marque le précédent comme obsolète (c’est le &lt;a href=&quot;https://www.postgresql.org/docs/current/mvcc.html&quot;&gt;MVCC&lt;/a&gt;). De plus, l’insertion de données se fait par ensemble d’enregistrements (page)
pour des questions de performance. Sans oublier que les données normalement insérées par l’application continuent d’arriver.&lt;/p&gt;

&lt;p&gt;Ici, nous demandons la modification de tous les enregistrements de la table: PostgreSQL va progressivement réécrire sur le disque l’intégralité de
la table. Il est possible qu’à la fin de cette opération, les données:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;soient physiquement stockées dans un ordre différent que celui dans lequel elles ont été écrites initialement;&lt;/li&gt;
  &lt;li&gt;prennent plus de place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Après la migration sur la table non référencée, nous avons constaté:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;une densité d’enregistrements par page qui varie de 60 à 35;&lt;/li&gt;
  &lt;li&gt;sur des pages contigües, des données d’octobre 2020 côtoient des données de février 2021, puis l’on repasse à octobre 2020.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cela n’est pas forcément un problème:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;si vos index sont suffisamment sélectifs;&lt;/li&gt;
  &lt;li&gt;si vos données ne sont pas interrogées chronologiquement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dans notre cas, la perspective d’obtenir des résultats encore plus accentués en diminuant la taille des batchs ne nous convenait pas.&lt;/p&gt;

&lt;h6 id=&quot;migration-des-données-dans-une-table-séparée&quot;&gt;Migration des données dans une table séparée&lt;/h6&gt;

&lt;p&gt;&lt;strong&gt;Exploration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prenons du recul sur le but: migrer les données en BIGINT sans modifier l’activité sur la table cible.&lt;/p&gt;

&lt;p&gt;Comme application ne fait qu’insérer des données dans la table, nous décidons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;d’isoler les données existantes dans une nouvelle table;&lt;/li&gt;
  &lt;li&gt;de les transformer;&lt;/li&gt;
  &lt;li&gt;dans le créneau de maintenance, de rajouter les données insérées au fil de l’eau;&lt;/li&gt;
  &lt;li&gt;de remplacer la table d’origine par la nouvelle table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lorsque se pose la question d’alimentation de la nouvelle table, nous nous rendons compte qu’il est possible de transformer les données en même temps.
Si la propriété de la nouvelle table est de type BIGINT, PostgreSQL effectue une coercition de type (cast) sur la donnée INT.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-postgres-sql&quot;&gt;CREATE TABLE &quot;answers_bigint&quot; (id BIGINT,..);
INSERT INTO &quot;answers_bigint&quot; (id, ..)
SELECT id, ... FROM answers;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nous avons aussi envisagé deux variantes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;pour décharger la BDD de la lecture des données, il est possible d’importer un &lt;a href=&quot;https://github.com/1024pix/pix/commit/7aefd223c2340e8533a680a2507ac14ca3b739ec#diff-76a491eba3a01404c3a05b940c2101e722f17a27fb81d390bdc9b33cf080a645&quot;&gt;dump&lt;/a&gt;;&lt;/li&gt;
  &lt;li&gt;pour insérer les données dans un ordre particulier, en délégant le tri à &lt;a href=&quot;https://github.com/1024pix/pix/commit/07200e0ed9677819d7ac0d6c335ca1daf0e94e95&quot;&gt;un processus hors BDD&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous sommes restés sur la solution &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT INTO&lt;/code&gt; :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;l’impact sur la BDD était négligeable;&lt;/li&gt;
  &lt;li&gt;elle est la plus rapide;&lt;/li&gt;
  &lt;li&gt;nous n’étions pas sûr du tri qui nous serait le plus bénéfique;&lt;/li&gt;
  &lt;li&gt;elle était la plus simple.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Implémentation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Comme on travaille avec deux tables, pour garder l’intégrité des données, nous choisissons d’implémenter:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;la migration en NodeJs, afin de disposer de tests automatisés;&lt;/li&gt;
  &lt;li&gt;la copie des données insérées entre-temps (qui ne peut être faite que coté serveur) via des triggers PostgreSQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le tout se compose de 6 pull-requests, afin de faciliter les revues:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/1024pix/pix/pull/4108&quot;&gt;partie 1&lt;/a&gt;: création des tables cibles;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/1024pix/pix/pull/4127&quot;&gt;partie 2&lt;/a&gt;: copie des données;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/1024pix/pix/pull/4130&quot;&gt;partie 3&lt;/a&gt;: création des index;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/1024pix/pix/pull/4134&quot;&gt;partie 4&lt;/a&gt;: recopie des données au fil de l’eau;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/1024pix/pix/pull/4135&quot;&gt;partie 5&lt;/a&gt;: récupération des données non recopiés au fil de l’eau;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/1024pix/pix/pull/4137&quot;&gt;partie 6&lt;/a&gt;: exposition des tables cibles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5 id=&quot;création-des-index&quot;&gt;Création des index&lt;/h5&gt;

&lt;p&gt;Dans l’implémentation précédente, vous avez peut-être remarqué que la création des index était effectué après la copie des données.&lt;/p&gt;

&lt;p&gt;Nous avons fait ce choix pour deux raisons:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;intuitivement, la création d’index en une seule fois, qui requiert la lecture &lt;a href=&quot;https://stackoverflow.com/questions/43484346/explain-analyze-on-create-index-with-postgresql&quot;&gt;toute la table&lt;/a&gt;, semble plus efficace que des mises à jour d’index à chaque insertion d’enregistrement;&lt;/li&gt;
  &lt;li&gt;cela permet de contrôler les ressources CPU allouées.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Une partie de la création d’index peut être effectuée en parallèle par des agents (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;worker&lt;/code&gt;) sur des CPU différents.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/sql-createindex.html#Notes&quot;&gt;La documentation&lt;/a&gt; explique les pré-requis de cette allocation.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;PostgreSQL can build indexes while leveraging multiple CPUs in order to process the table rows faster for index methods that support building indexes in parallel (currently, only B-tree).&lt;/p&gt;

  &lt;p&gt;(..)&lt;/p&gt;

  &lt;p&gt;Generally, a cost model automatically determines how many worker processes should be requested, if any.&lt;/p&gt;

  &lt;p&gt;(..)&lt;/p&gt;

  &lt;p&gt;Increasing max_parallel_maintenance_workers may allow more workers to be used, which will reduce the time needed for index creation, so long as the index build is not already I/O bound.
Of course, there should also be sufficient CPU capacity that would otherwise lie idle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;https://www.postgresql.org/docs/13/runtime-config-resource.html#GUC-MAX-PARALLEL-WORKERS-MAINTENANCE&quot;&gt;Elle ajoute&lt;/a&gt; mentionne:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Parallel workers are taken from the pool of processes established by max_worker_processes, limited by max_parallel_workers.
Note that the requested number of workers may not actually be available at run time.
If this occurs, the utility operation will run with fewer workers than expected.&lt;/p&gt;

  &lt;p&gt;(..)&lt;/p&gt;

  &lt;p&gt;Note that parallel utility commands should not consume substantially more memory than equivalent non-parallel operations.&lt;/p&gt;

  &lt;p&gt;(..)&lt;/p&gt;

  &lt;p&gt;However, parallel utility commands may still consume substantially more CPU resources and I/O bandwidth.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h6 id=&quot;allouer-peu-de-ressources&quot;&gt;Allouer peu de ressources&lt;/h6&gt;

&lt;p&gt;Nous souhaitons allouer un seul agent pour créer cet index, pour laisser la majorité des ressources disponibles à l’application.&lt;/p&gt;

&lt;p&gt;Nous essayons d’abord de modifier ce paramètre pour toute la base de données.
Cela affecte les création d’index et les &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VACUUM&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Il vaut par défaut 2, nous le passons au minimum: 1.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-postgres-sql&quot;&gt;SHOW max_parallel_maintenance_workers;
&amp;gt; max_parallel_maintenance_workers
&amp;gt;----------------------------------
&amp;gt; 2
SET max_parallel_maintenance_workers = 1;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nous démarrons des simulations d’activité sur l’application et démarrons la création d’index.
On observe une dégradation nette des temps de réponse.&lt;/p&gt;

&lt;p&gt;Nous essayons de limiter cette modification uniquement à cette table.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-postgres-sql&quot;&gt;ALTER TABLE answers_bigint  SET (parallel_workers = 1);
SELECT reloptions FROM pg_class WHERE oid = 'answers_bigint'::regclass::oid;
&amp;gt;      reloptions
&amp;gt;----------------------
&amp;gt; {parallel_workers=1}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On observe toujours une dégradation.&lt;/p&gt;

&lt;h6 id=&quot;allouer-beaucoup-de-ressources&quot;&gt;Allouer beaucoup de ressources&lt;/h6&gt;

&lt;p&gt;L’hypothèse de ne pas impacter les temps de réponse semble compromise.
Nous nous dirigeons alors vers un créneau de maintenance, où l’on essaierait de créer l’index le plus rapidement possible.
Pour cela, il nous faut donner la majorité des ressources. Nous augmentons le nombre de worker pour qu’il soit le même que le nombre de CPU.&lt;/p&gt;

&lt;p&gt;Nous suivons le nombre de worker réellement utilisés.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-postgres-sql&quot;&gt;SELECT
    current_setting('max_parallel_workers')::integer AS max_workers,
    COUNT(*) AS active_workers
FROM pg_stat_activity
WHERE backend_type = 'parallel worker';
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Malgré cela, le temps d’exécution ne change pas.&lt;/p&gt;

&lt;p&gt;Nous explorons &lt;a href=&quot;https://www.cybertec-postgresql.com/en/postgresql-parallel-create-index-for-better-performance/&quot;&gt;d’autres pistes&lt;/a&gt;, comme la mémoire ou les tablespace alloués.
Nous ne constatons pas de changement de comportement.&lt;/p&gt;

&lt;h6 id=&quot;utiliser-lallocation-native&quot;&gt;Utiliser l’allocation native&lt;/h6&gt;

&lt;p&gt;Notre dernier essai est d’abandonner la création d’index en parallèle, pour tester cet extrait de la documentation.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;However, parallel utility commands may still consume substantially more CPU resources and I/O bandwidth.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;La durée d’exécution est la même, 12 heures, un peu moins que la solution native, pour une complexité bien supérieure.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Table answers
- PK: 35 minutes
- FK: 21 minutes
- NOT NULL: 2 heures 16 minutes
- Autres index: 31 minutes

Table knowledge-elements
- PK: 1 heure 17 minutes
- FK: 2 heures 50 minutes
- NOT NULL: 3 heures 28 minutes
- Autres index: 1 heure 8 minutes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h6 id=&quot;le-temps-de-la-réflexion&quot;&gt;Le temps de la réflexion&lt;/h6&gt;

&lt;p&gt;Nous avons testé beaucoup de réglages et prenons un peu de recul, à savoir relire la documentation. Cette phrase attire enfin notre attention.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Increasing max_parallel_maintenance_workers may allow more workers to be used, which will reduce the time needed for index creation,
&lt;strong&gt;so long as the index build is not already I/O bound&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nous faisons &lt;a href=&quot;https://dba.stackexchange.com/questions/305356/track-down-which-resource-limits-index-creation-in-postgresql&quot;&gt;appel à la communauté&lt;/a&gt; pour savoir comment vérifier si l’I/O est le facteur limitant.
La réponse est de nous tourner vers &lt;a href=&quot;https://scalingo.com/&quot;&gt;notre PaaS&lt;/a&gt;, car ces métriques ne sont pas accessibles dans la base de données.
C’est ce que nous faisons, et la réponse est claire&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;D’après nos hypothèses, la limitation ici serait lié aux IOPS. Pourquoi ? Lors de vos opérations intenses en lectures/écritures, vous pouvez voir que le CPU ne monte pas tellement
considérant qu’ils y a 8 cores disponibles avec 4 garanties (400% CPU) pour le plan. Le nombre d’IOPS disponible est proportionnel à la capacité de la base (..) donc 4500 IOPS.&lt;/p&gt;

  &lt;p&gt;Les IOPS dans l’univers du cloud est souvent ce qui est le plus coûteux par rapport à la capacité et nous ne pouvons pas simplement vous donner 20 000 IOPS par exemple.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;bilan&quot;&gt;Bilan&lt;/h4&gt;

&lt;p&gt;La solution native prenait 15 heures et la solution en arrière plan nécessitait avec un créneau de maintenance de 12 heures.
Lors d’essais quelques mois après, la durée d’exécution de la solution en arrière plan prenait encore plus de temps, alors que la solution native restait stable.&lt;/p&gt;

&lt;p&gt;Il restait la possibilité de répartir la création des index dans le temps, dans les plages de faible activité de la plateforme (nuit, week-end).
Cela entrainait malgré tout une baisse de la qualité de service pendant la création d’au moins un index, qui pouvait prendre jusqu’à 2h50 pour le plus important.
Il serait dans ce cas nécessaire de mobiliser une équipe pendant ce créneau, pour monitorer les opérations et les interrompre si la qualité de service était trop impactée.&lt;/p&gt;

&lt;p&gt;Nous avons alors proposé ces deux solutions, native et en arrière plan, à l’équipe métier, au contact des utilisateurs et ayant une vision d’ensemble.
Leur réponse fut claire; ils préféraient une solution simple, avec interruption totale de service, prévisible et annoncée à l’avance aux partenaires et aux utilisateurs.&lt;/p&gt;

&lt;p&gt;Nous avons donc choisi la solution native, pour sa constance en termes de temps d’exécution et sa faible complexité.
Cela a été l’occasion de multiples apprentissages, notamment du risque de réorganisation de la table et des facteurs limitant sur des opérations intensives.&lt;/p&gt;

&lt;p&gt;Il existe peut-être une solution sans interruption de service, par exemple dans une instance séparée et une réplication logique, mais ces services ne sont pas disponibles pour l’instant.&lt;/p&gt;

&lt;h2 id=&quot;implémentation&quot;&gt;Implémentation&lt;/h2&gt;

&lt;p&gt;La solution native est dans &lt;a href=&quot;https://github.com/1024pix/pix/pull/3839&quot;&gt;cette courte pull-request&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;exécution-en-production-et-perspectives&quot;&gt;Exécution en production et perspectives&lt;/h2&gt;

&lt;p&gt;Nous avions appris dans la phase d’exploration que la durée d’exécution assez longue de la migration sans état d’avancement était frustrant.
L’exécution de la solution native étant prévue sur un créneau de faible fréquentation, à savoir la nuit, il était important de disposer d’informations
claires sur ce qui se passait pour pouvoir prendre les bonnes décisions, même sous la fatigue et la pression.&lt;/p&gt;

&lt;p&gt;Un &lt;a href=&quot;https://engineering.pix.fr/bdd/2022/09/20/steampipe-dashboard.html&quot;&gt;monitoring&lt;/a&gt; complet a été mis en place sur les 3 phases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;arrêt des applications;&lt;/li&gt;
  &lt;li&gt;avancement de la migration;&lt;/li&gt;
  &lt;li&gt;redémarrage des applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;L’exécution en production s’est bien passée. La durée de traitement a été plus longue que sur l’environnement de test,
à savoir 19 heures au lieu de 12 heures. Nous ne connaissons pas la cause de cet allongement; on peut faire l’hypothèse
que les données étaient éparpillées sur le disque en production alors que sur la plateforme de test, importée par dump,
les données étaient contigües.&lt;/p&gt;

&lt;p&gt;Nous en retenons la nécessité de prévoir un créneau de maintenance un peu plus important.&lt;/p&gt;</content><author><name></name></author><category term="bdd" /><summary type="html">Sur plusieurs tables volumineuses avec PostgreSQL</summary></entry><entry><title type="html">Suivi de migration avec Steampipe</title><link href="https://engineering.pix.fr/bdd/2022/09/20/steampipe-dashboard.html" rel="alternate" type="text/html" title="Suivi de migration avec Steampipe" /><published>2022-09-20T08:10:42+00:00</published><updated>2022-09-20T08:10:42+00:00</updated><id>https://engineering.pix.fr/bdd/2022/09/20/steampipe-dashboard</id><content type="html" xml:base="https://engineering.pix.fr/bdd/2022/09/20/steampipe-dashboard.html">&lt;p&gt;Le 23 juillet 2022 à 22h et jusqu’au 24 juillet à 17h, Pix était en maintenance. La raison ? Nous avons profité de la période de calme de l’été pour migrer l’identifiant primaire de l’une des tables de notre base de données de int à bigint. Cela fait suite à la &lt;a href=&quot;/bdd/2021/09/03/modifier-clef-primaire-non-referencee-type-bigint.html&quot;&gt;précédente migration d’août dernier&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Dans ce genre de moment, tout le monde doit être aligné. En effet pour que la migration se fasse, il faut s’assurer que l’ensemble des applications impactées soient en maintenance, que la base de données soit dans le bon état, lancer le script de migration, attendre (longtemps), vérifier le bon résultat, et relancer les applications.&lt;/p&gt;

&lt;p&gt;Pour tout cela il est nécessaire d’avoir de bons outils. Des outils nous en avons justement à foison, entre le monitoring externe de nos applications &lt;a href=&quot;https://www.freshworks.com/website-monitoring/&quot;&gt;Freshping&lt;/a&gt;, &lt;a href=&quot;https://www.datadoghq.com/&quot;&gt;Datadog&lt;/a&gt; pour les logs des applications, &lt;a href=&quot;https://scalingo.com/&quot;&gt;Scalingo&lt;/a&gt; notre hébergeur et &lt;a href=&quot;https://www.atlassian.com/software/confluence&quot;&gt;Confluence&lt;/a&gt; pour la documentation.&lt;/p&gt;

&lt;p&gt;Tous ces outils, associés à 6 personnes qui doivent se relayer pas toujours à l’aise avec les outils de suivi de la production, rendent l’alignement complexe.&lt;/p&gt;

&lt;p&gt;Pour résoudre ce problème nous avons donc utilisé un nouvel outil, &lt;a href=&quot;https://steampipe.io/&quot;&gt;Steampipe&lt;/a&gt;, pour les gouverner tous.&lt;/p&gt;

&lt;p&gt;Nous utilisons déjà Steampipe pour vérifier l’état de la production tous les jours, mais c’était notre première utilisation des dashboards.&lt;/p&gt;

&lt;p&gt;Nous l’avons construit pour permettre un suivi chronologique de la migration:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;La mise en maintenance des applications Pix impactées&lt;/li&gt;
  &lt;li&gt;Le suivi de la migration&lt;/li&gt;
  &lt;li&gt;La réouverture&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Le dashboard agit comme une tour de contrôle de la migration. En un coup d’oeil, nous savons où nous en sommes.
Le code du dashboard est, comme beaucoup de choses chez Pix, &lt;a href=&quot;https://github.com/1024pix/steampipe-dashboard-bigint&quot;&gt;accessible sur GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Trêve de bavardage, à quoi il ressemble ?&lt;/p&gt;

&lt;p&gt;Au début tout est rouge:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dashboard-bigint/rouge.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Et puis une fois la mise en maintenance effectuée, cela passe au vert (vous avez remarqué les jolies icônes ?)&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dashboard-bigint/vert.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Tout est éteint certes, mais est-ce que nous n’avons pas oublié des applications qui auraient des connexions ouvertes sur la base ?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dashboard-bigint/open.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ensuite on lance le processus de migration:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dashboard-bigint/progress.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Au bout de quelques heures, on y est ?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dashboard-bigint/presque.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;OUI&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dashboard-bigint/oui.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;sous-le-capot&quot;&gt;Sous le capot&lt;/h2&gt;

&lt;p&gt;Coté hébergement, comme pour tout le reste, nous l’avions déployé sur Scalingo grâce au &lt;a href=&quot;https://github.com/francois2metz/steampipe-buildpack&quot;&gt;buildpack steampipe&lt;/a&gt;. Comme nous manipulions des informations de production, il était sur la région osc-secnum-fr1.&lt;/p&gt;

&lt;p&gt;La partie suivie de la mise en maintenance utilisait le &lt;a href=&quot;https://hub.steampipe.io/plugins/turbot/net&quot;&gt;plugin net&lt;/a&gt;, en vérifiant le code HTTP 503 des applications. Pour les applications non directement accessibles depuis internet, nous avons utilisé le &lt;a href=&quot;https://hub.steampipe.io/plugins/francois2metz/scalingo&quot;&gt;plugin scalingo&lt;/a&gt; pour vérifier que tous les conteneurs étaient éteints. La vérification de la désactivation du monitoring a été faite avec le &lt;a href=&quot;https://github.com/francois2metz/steampipe-plugin-freshping&quot;&gt;plugin freshping&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Avant de lancer la migration, nous avions le nombre de connexions ouvertes a PostgreSQL affichées. Utile pour savoir si tout avait bien été éteint. La récupération se faisait avec le &lt;a href=&quot;&quot;&gt;plugin datadog&lt;/a&gt;, qui récupérait les infos de &lt;a href=&quot;https://github.com/1024pix/pix-db-stats&quot;&gt;pix-db-stats&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Le suivi de la migration était plutôt minimale, parce que c’est PostgreSQL qui faisait le travail, sans réelle possibilité de connaitre de suivre précisément la progression. Mais nous pouvions monitorer les différentes étapes du script de migration, et ce sur les 2 bases à migrer simultanément. Pour cela le &lt;a href=&quot;https://hub.steampipe.io/plugins/turbot/datadog&quot;&gt;plugin datadog&lt;/a&gt; regardait les logs générés par les conteneurs pour afficher l’étape en cours.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;La migration s’est bien déroulée, et nous sommes contents d’avoir pu expérimenter les tableaux de bord Steampipe. Sa capacité à interroger différents services, de les mixer et d’avoir une jolie interface permet de démarrer très rapidement et de rajouter au fur et a mesure des informations.&lt;/p&gt;

&lt;p&gt;Des choses étaient tout de même perfectibles, comme le suivi de la migration qui était un peu fragile, ou bien certaines étapes de la mise en maintenance qui n’étaient pas visibles dans Steampipe, comme l’arrêt de l’infrastructure data sur OVH (par manque de temps principalement).&lt;/p&gt;

&lt;p&gt;Si nous espérons ne pas avoir souvent à mettre en maintenance tout pix, avoir un outil qui nous permet de centraliser toutes les informations nous sera certainement utile à l’avenir avec l’expérience accumulée cette fois-ci.&lt;/p&gt;</content><author><name></name></author><category term="bdd" /><summary type="html">L'histoire d'un tableau de bord qui a permis de suivre les différentes étapes de la migration de notre base de données.</summary></entry><entry><title type="html">Passer une PK de INT en BIGINT en zéro downtime</title><link href="https://engineering.pix.fr/bdd/2021/09/03/modifier-clef-primaire-non-referencee-type-bigint.html" rel="alternate" type="text/html" title="Passer une PK de INT en BIGINT en zéro downtime" /><published>2021-09-03T08:10:42+00:00</published><updated>2021-09-03T08:10:42+00:00</updated><id>https://engineering.pix.fr/bdd/2021/09/03/modifier-clef-primaire-non-referencee-type-bigint</id><content type="html" xml:base="https://engineering.pix.fr/bdd/2021/09/03/modifier-clef-primaire-non-referencee-type-bigint.html">&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;Dans quelques mois, la table la plus volumineuse (nombre de lignes) aurait épuisé les valeurs disponibles pour sa clef primaire. Pour éviter &lt;a href=&quot;https://twitter.com/dhh/status/1060565296048562177?lang=fr&quot;&gt;cette situation&lt;/a&gt;, nous souhaitions changer de type en minimisant l’impact utilisateur, en visant des migrations de BDD “zero-downtime”.&lt;/p&gt;

&lt;p&gt;Nous avons réussi et partageons avec vous nos apprentissages. Pour les impatients, la solution est &lt;a href=&quot;#implémentation&quot;&gt;ici&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ceci dit, cela a eu lieu dans des circonstances particulières :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;table non référencée par d’autres tables ;&lt;/li&gt;
  &lt;li&gt;trafic réduit (vacances).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous avons prévu de migrer d’autres tables, référencées, en charge nominale. Nous partagerons les résultats dans un autre article.&lt;/p&gt;

&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;/h2&gt;

&lt;p&gt;En un an, depuis septembre 2020, la fréquentation de Pix a augmenté &lt;a href=&quot;https://pix.fr/statistiques&quot;&gt;de manière spectaculaire&lt;/a&gt;. La charge a été bien absorbée par l’infrastructure, mais nous nous sommes rendu compte d’une autre conséquence: une table allait épuiser l’identifiant utilisé par sa clef primaire.&lt;/p&gt;

&lt;p&gt;Il est bien sûr possible de modifier le type de cette propriété avec l’instruction native &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER TABLE foo ALTER COLUMN id SET DATA TYPE BIGINT&lt;/code&gt;, mais cela a un coût proportionnel au remplissage de la table. Une exécution de cette instruction sur les données de production, sur un environnement moins puissant, a duré 8 heures.&lt;/p&gt;

&lt;p&gt;Nous souhaitions trouver une alternative, à savoir effectuer ce changement sans que les utilisateurs ne s’en rendent compte :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;pas d’interruption de service, même sous la forme d’une fenêtre de maintenance planifiée ;&lt;/li&gt;
  &lt;li&gt;pas de dégradation sensible des temps de réponse pendant les opérations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;recherche-de-solution&quot;&gt;Recherche de solution&lt;/h2&gt;

&lt;p&gt;Ce &lt;a href=&quot;https://stackoverflow.com/questions/54795701/migrating-int-to-bigint-in-postgressql-without-any-downtime/54796046&quot;&gt;thread Stack Overflow&lt;/a&gt; est un excellent point d’entrée sur le sujet.&lt;/p&gt;

&lt;p&gt;Deux solutions y sont proposées :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;soit utiliser une colonne temporaire ;&lt;/li&gt;
  &lt;li&gt;soit utiliser une base de données temporaire, alimentée par une réplication logique.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous avons choisi la colonne temporaire, car cette solution peut être mise en œuvre immédiatement dans notre environnement.&lt;/p&gt;

&lt;p&gt;En effet, nous utilisons les services d’un PaaS, &lt;a href=&quot;https://scalingo.com/&quot;&gt;Scalingo&lt;/a&gt;, et sommes heureux de ne pas avoir à installer et maintenir nos bases de données. Le service spécifique de la réplication logique n’est pour l’instant pas offert.&lt;/p&gt;

&lt;p&gt;Examinons pour commencer la &lt;a href=&quot;https://tech.coffeemeetsbagel.com/schema-migration-from-int-to-bigint-on-a-massive-table-in-postgres-aabb835c3b84&quot;&gt;solution d’origine&lt;/a&gt;, celle avec colonne temporaire, puis voyons les modifications que nous y avons apportées.&lt;/p&gt;

&lt;h3 id=&quot;étape-1--convertir-les-données-en-tâche-de-fond&quot;&gt;Étape 1 : convertir les données en tâche de fond&lt;/h3&gt;

&lt;p&gt;La commande &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER TABLE foo ALTER COLUMN id SET DATA TYPE BIGINT&lt;/code&gt; effectue (entre autres) une conversion des données (cast) de INTEGER à BIGINT.&lt;/p&gt;

&lt;p&gt;La &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-altertable.html#notes&quot;&gt;documentation&lt;/a&gt; mentionne en effet :&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Changing the type of an existing column will require the entire table and its indexes to be rewritten.
As an exception, when changing the type of an existing column and the old type is either binary coercible to the new type a table rewrite is not needed; but any indexes on the affected columns must still be rebuilt.
Table and/or index rebuilds may take a significant amount of time for a large table; and will temporarily require as much as double the disk space.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Comme l’opération native pose un verrou exclusif sur la table, il faut attendre sa réécriture complète pour y accéder.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;An ACCESS EXCLUSIVE lock is held unless explicitly noted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L’alternative à la solution native est d’effectuer la conversion des données en tâche de fond :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;dans une colonne de type BIGINT, non exposée à l’API ;&lt;/li&gt;
  &lt;li&gt;pour les nouveaux tuples, à l’aide d’un trigger ;&lt;/li&gt;
  &lt;li&gt;pour les tuples existants, par un traitement batch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;étape-2--substituer-les-colonnes-et-créer-la-clef-primaire&quot;&gt;Étape 2 : substituer les colonnes et créer la clef primaire&lt;/h3&gt;

&lt;p&gt;Une fois la migration achevée, une fenêtre de maintenance est requise (1h dans le &lt;a href=&quot;https://tech.coffeemeetsbagel.com/schema-migration-from-int-to-bigint-on-a-massive-table-in-postgres-aabb835c3b84&quot;&gt;cas d’origine&lt;/a&gt;) pour :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;supprimer les contraintes sur la colonne INTEGER (PK, FK) ;&lt;/li&gt;
  &lt;li&gt;promouvoir la colonne de type BIGINT en tant qu’identifiant ;&lt;/li&gt;
  &lt;li&gt;recréer les contraintes (PK et FK).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;améliorations&quot;&gt;Améliorations&lt;/h3&gt;

&lt;p&gt;Cette solution va dans le bon sens, mais ne satisfait par tous nos critères : il reste une fenêtre de maintenance. Nous pensons qu’il est possible de pousser la solution et d’éliminer totalement cette fenêtre.&lt;/p&gt;

&lt;p&gt;Le principe est le suivant :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;la contrainte de clef primaire comporte une contrainte NOT NULL et une contrainte UNIQUE ;&lt;/li&gt;
  &lt;li&gt;c’est la validation de ces contraintes qui prend le plus de temps dans la fenêtre de maintenance ;&lt;/li&gt;
  &lt;li&gt;ces validations peuvent être effectuées en amont.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour valider les contraintes en amont :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;ajouter une contrainte NOT NULL dès la création de la colonne ;&lt;/li&gt;
  &lt;li&gt;créer un index UNIQUE de manière concurrente &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-createindex.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Il ne reste plus, comme dernière opération, qu’à créer la PK sur base de l’index UNIQUE.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ALTER TABLE &quot;foo&quot; ADD CONSTRAINT &quot;foo_pkey&quot; PRIMARY KEY USING INDEX &quot;foo_id_unique&quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL détecte lui-même la contrainte NOT NULL.&lt;/p&gt;

&lt;h2 id=&quot;implémentation&quot;&gt;Implémentation&lt;/h2&gt;

&lt;p&gt;Le code présenté a été modifié pour les besoins de l’article, mais un lien est fourni vers chaque commit pour les détails.&lt;/p&gt;

&lt;p&gt;A l’état initial, la table contient 700 millions d’enregistrements.
&lt;a href=&quot;https://github.com/1024pix/pix/commit/fd7273f9e47648e4615a8b4af13bd81b1c8c2928#diff-754627687e8d1bd3c4d8f60cf5ed3640a9c104acdccbc20407c16479ad63055d&quot;&gt;fd7273f9&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;             &lt;span class=&quot;nb&quot;&gt;SERIAL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;source&lt;/span&gt;         &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;(...)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;La première étape est l’ajout d’une colonne temporaire, avec une migration de BDD, incluse dans le déploiement de la release.
&lt;a href=&quot;https://github.com/1024pix/pix/pull/3357/files#diff-1eecfd94150cca766655d3786c7dc12e3b6bc8fa60be3a0c0740424ab02c157a&quot;&gt;eecfd941&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;bigintId&quot;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;BIGINT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Puis, un trigger est créé pour alimenter la valeur pour les nouveaux enregistrements.&lt;/p&gt;
&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FUNCTION&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;copy_int_id_to_bigint_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;RETURNS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TRIGGER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$$&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BEGIN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&quot;bigintId&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;BIGINT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;RETURN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NEW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$$&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;LANGUAGE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plpgsql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TRIGGER&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;trg_knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BEFORE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EACH&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ROW&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FUNCTION&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;copy_int_id_to_bigint_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Ensuite, le script de migration inclus dans la PR est lancé manuellement (durée: 48h pour 700 millions de lignes).
&lt;a href=&quot;https://github.com/1024pix/pix/pull/3357/files#diff-d89cb20637cb9b836bce4902b46db958e0209c2a0de96e7dc81a0c43015680b4&quot;&gt;d89cb206&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;SELECT MAX(id) FROM &quot;knowledge-elements&quot;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;startId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;endId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;maxData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rows&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;startId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;endId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chunkSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`
        UPDATE &quot;knowledge-elements&quot;
        SET &quot;bigintId&quot; = id
        WHERE ID BETWEEN &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; AND &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chunkSize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;CREATE UNIQUE INDEX CONCURRENTLY &quot;indexidBigInteger&quot;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour finir, une dernière migration, celle qui expose la colonne BIGINT
&lt;a href=&quot;https://github.com/1024pix/pix/pull/3364/files#diff-9985cafe7c9915c64927036180071c804f75aad26a66686efd1758d242b819d1&quot;&gt;9985cafe&lt;/a&gt;&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;BEGIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TRANSACTION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LOCK&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IN&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ACCESS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;EXCLUSIVE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;MODE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TRIGGER&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;trg_knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FUNCTION&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;copy_int_id_to_bigint_id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SEQUENCE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements_id_seq&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;OWNED&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;&quot;bigintId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SEQUENCE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements_id_seq&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;BIGINT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;bigintId&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nextval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'&quot;knowledge-elements_id_seq&quot;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CONSTRAINT&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements_pkey&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;CONSTRAINT&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements_pkey&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;USING&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INDEX&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements_bigintId_index&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;RENAME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;id&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TO&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;intId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;knowledge-elements&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;RENAME&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;COLUMN&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;bigintId&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TO&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COMMIT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TRANSACTION&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;validation-de-la-solution&quot;&gt;Validation de la solution&lt;/h2&gt;

&lt;p&gt;Cette hypothèse de solution a été mise progressivement en contact avec la réalité, pour garder un feedback rapide :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;exécution sur une table de test, ne comportant qu’une seule colonne, sur une volumétrie comparable à la production ;&lt;/li&gt;
  &lt;li&gt;même test, en soumettant la BDD à des requêtes concurrentes de lecture/écriture sur la table pour estimer l’impact sur les temps de réponse ;&lt;/li&gt;
  &lt;li&gt;exécution sur une copie de la table de production ;&lt;/li&gt;
  &lt;li&gt;même test, en soumettant la BDD à un échantillon des requêtes de production sur la table pour estimer l’impact sur les temps de réponse ;&lt;/li&gt;
  &lt;li&gt;intégration du code dans le processus de migration de BDD (knex) ;&lt;/li&gt;
  &lt;li&gt;déploiement du code, via le processus de migration, sur une instance de reporting de production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Les apprentissages ont été les suivants :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;l’intégration maximale au processus de déploiement augmente la fiabilité ;&lt;/li&gt;
  &lt;li&gt;les tests menés dès le début ont entraîné la mise en place au plus tôt d’un monitoring (extension pg_stat_statements + Datadog) sur :
    &lt;ul&gt;
      &lt;li&gt;la vitesse de la migration et sa date de fin estimée;&lt;/li&gt;
      &lt;li&gt;les temps de réponse BDD et API;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;la pertinence des tests est limitée par:
    &lt;ul&gt;
      &lt;li&gt;la capacité de reproduire un trafic similaire à la production ;&lt;/li&gt;
      &lt;li&gt;le fait que l’environnement soit sur une plateforme dédiée, avec une disponibilité des ressources partagée (ex: I/O) non déterministe.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;exécution-en-production-et-perspectives&quot;&gt;Exécution en production et perspectives&lt;/h2&gt;

&lt;h3 id=&quot;le-plan-se-déroule-sans-accroc&quot;&gt;Le plan se déroule sans accroc&lt;/h3&gt;

&lt;p&gt;La migration préalable s’est déroulée sans erreur.&lt;/p&gt;

&lt;p&gt;La migration des données proprement dite a pris environ 24h.&lt;/p&gt;

&lt;p&gt;L’exposition de la nouvelle colonne, lors du déploiement, a pris un temps négligeable pour les utilisateurs finaux (&amp;lt; 1 seconde).&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2021-08-27 14:10:40.368443479 +0200 CEST &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;postdeploy-2719] &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; pix-api@3.92.0 postdeploy /app
2021-08-27 14:10:40.368393446 +0200 CEST &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;postdeploy-2719]
2021-08-27 14:10:41.566685265 +0200 CEST &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;postdeploy-2719] Batch 188 run: 1 migrations
2021-08-27 14:10:42.176473412 +0200 CEST &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;manager] container &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;postdeploy-2719] &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;6128d63ffcff1013ac9e57c1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; has stopped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;mais&quot;&gt;Mais&lt;/h3&gt;

&lt;h4 id=&quot;un-impact-non-négligeable-du-traitement-de-migration-sur-le-temps-de-réponse&quot;&gt;Un impact non négligeable du traitement de migration sur le temps de réponse&lt;/h4&gt;

&lt;p&gt;Bien que l’opération soit un succès, lors des étapes préparatoires :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;le temps de réponse de l’API a augmenté d’un facteur 10, en passant d’une moyenne de 70 ms à 1 000 ms ;&lt;/li&gt;
  &lt;li&gt;lors de l’étape de migration des données, ont eu lieu 4 pics à 3 secondes, d’une demi-heure chacun ;&lt;/li&gt;
  &lt;li&gt;lors de l’étape de validation des contraintes, a eu lieu 1 pic à 10 secondes, d’une heure et demie.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/change-pk-unreferenced-bigint/api-pc95-response-time.png&quot; alt=&quot;image info&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Malgré cela, l’impact utilisateur a été faible, car :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;cela eu lieu en été, période de faible fréquentation de la plateforme ;&lt;/li&gt;
  &lt;li&gt;le plus long pic a eu lieu à un horaire de faible fréquentation (nuit).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si l’impact utilisateur s’était avéré plus fort, nous aurions ralenti le traitement en réduisant :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;la taille des batchs ;&lt;/li&gt;
  &lt;li&gt;le rythme d’exécution (pauses périodiques).&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;un-impact-sur-lorganisation-des-données-sur-le-disque&quot;&gt;Un impact sur l’organisation des données sur le disque&lt;/h4&gt;

&lt;p&gt;Nous avons constaté que la table est bien plus fragmentée après migration qu’avant migration. Bien que nous n’ayons pas fait de test comparatif, il est possible que cela soit dû à notre solution.&lt;/p&gt;

&lt;p&gt;En effet :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;la solution native réécrit la table en une seule fois ;&lt;/li&gt;
  &lt;li&gt;notre solution la réécrit en plusieurs fois.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cela peut avoir deux effets :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;occupation de disque plus importante ;&lt;/li&gt;
  &lt;li&gt;impact sur les temps de réponse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous monitorons actuellement les temps de réponse pour déterminer s’il y a un impact.&lt;/p&gt;

&lt;h2 id=&quot;perspectives&quot;&gt;Perspectives&lt;/h2&gt;

&lt;p&gt;Nous avons prévu de changer le type d’identifiant sur d’autres tables.
Nous ne pourrons pas appliquer exactement la même approche à l’avenir, pour deux raisons qui seront détaillées. Aussi, nous allons continuer à y travailler et cela fera l’objet d’un nouvel article.&lt;/p&gt;

&lt;h3 id=&quot;diminuer-les-impacts-possibles&quot;&gt;Diminuer les impacts possibles&lt;/h3&gt;

&lt;p&gt;Le contexte de faible fréquentation de la plateforme ne peut pas être pris comme un pré-requis.&lt;/p&gt;

&lt;p&gt;Nous pensons à :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;rendre le traitement de migration paramétrable à chaud, en passant de variables d’environnement à une table de paramétrage.&lt;/li&gt;
  &lt;li&gt;modifier notre approche en passant d’une colonne temporaire à une table temporaire .&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;généraliser-la-solution&quot;&gt;Généraliser la solution&lt;/h3&gt;

&lt;p&gt;“Rudy’s Rutabaga rule” de Gerald Weinberg nous dit&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Once you eliminate your number one problem, number two gets a promotion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Notre problème n°2 est une table dont l’identifiant :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;dont le type doit aussi être modifié en BIGINT ;&lt;/li&gt;
  &lt;li&gt;est référencé par une autre table (FK).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous allons réutiliser le principe de “faire le maximum en amont” sur cette nouvelle situation :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;deux colonnes à mettre à jour au lieu d’une ;&lt;/li&gt;
  &lt;li&gt;une contrainte de type FK en plus de la contrainte de type PK.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;des-suggestions-damélioration-&quot;&gt;Des suggestions d’amélioration ?&lt;/h2&gt;

&lt;p&gt;Afin de rester sur un format court, cet article :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;prend comme pré-requis le MVCC de PostgreSQL, et ses conséquences lors d’ajout et de suppression de colonne (bloat) ;&lt;/li&gt;
  &lt;li&gt;ne précise pas la raison des détails d’implémentation, notamment du script de migration des données.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si vous avez des questions, &lt;a href=&quot;https://github.com/1024pix/pix-engineering/issues&quot;&gt;contactez-nous&lt;/a&gt; pour que nous puissions améliorer cet article !&lt;/p&gt;</content><author><name></name></author><category term="bdd" /><summary type="html">Sur une table volumineuse en PostgreSQL</summary></entry><entry><title type="html">Gestion des environnements</title><link href="https://engineering.pix.fr/infrastructure/2021/08/31/gestion-des-environnements.html" rel="alternate" type="text/html" title="Gestion des environnements" /><published>2021-08-31T13:00:00+00:00</published><updated>2021-08-31T13:00:00+00:00</updated><id>https://engineering.pix.fr/infrastructure/2021/08/31/gestion-des-environnements</id><content type="html" xml:base="https://engineering.pix.fr/infrastructure/2021/08/31/gestion-des-environnements.html">&lt;p&gt;Une bonne gestion de production ne va pas sans une bonne gestion des environnements.&lt;/p&gt;

&lt;p&gt;Avec le temps, nous en sommes venus à considérer et gérer au quotidien 6 types d’environnements, aux besoins, contraintes et cahier des charges bien définis :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;local (ou localhost)&lt;/li&gt;
  &lt;li&gt;review app&lt;/li&gt;
  &lt;li&gt;intégration&lt;/li&gt;
  &lt;li&gt;recette&lt;/li&gt;
  &lt;li&gt;production&lt;/li&gt;
  &lt;li&gt;autres / ad hoc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/gestion-des-environnements/environnements.png&quot; alt=&quot;Environnements applicatifs&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;1-localhost&quot;&gt;1. Localhost&lt;/h2&gt;

&lt;p&gt;Il s’agit de l’environnement de développement propre à chaque développeur, sur son poste de travail professionnel individuel.&lt;/p&gt;

&lt;p&gt;Chaque développeur est libre de travailler dans l’environnement qui lui convient le mieux (Mac OS, Linux, Windows), avec les outils qu’il préfère (WebStorm, Visual Studio, Vim).&lt;/p&gt;

&lt;p&gt;Des outils sont développés, fournis et automatiquement mis en œuvre pour s’assurer que l’agent respecte les standards de l’organisation (versions de logiciel, format du code, etc.).&lt;/p&gt;

&lt;p&gt;C’est le seul environnement qui n’est pas hébergé sur notre PaaS.&lt;/p&gt;

&lt;h2 id=&quot;2-review-app-ra&quot;&gt;2. Review app (RA)&lt;/h2&gt;

&lt;p&gt;Il s’agit d’un environnement d’exécution (du code) éphémère, généré à chaque ouverture d’une pull request (PR) sur GitHub. Le but de ces environnements est de valider en autonomie un changement de code, qu’il soit lié à une fonctionnalité, une correction de bug, une modification technique ou une procédure / script / manipulation particulière.&lt;/p&gt;

&lt;p&gt;Pour ce faire, nous utilisons la fonctionnalité Review Apps de Scalingo. Lorsque la PR est fermée (fusionnée ou annulée), l’environnement est automatiquement détruit.&lt;/p&gt;

&lt;p&gt;Chaque environnement de RA reprend les spécifications techniques de la production, modulo certaines caractéristiques telles que la taille des ressources mobilisées, les données qui l’alimentent ou encore la présence / mutualisation ou non de serveurs front / CDN.&lt;/p&gt;

&lt;p&gt;À chaque création d’une RA, des jeux de données sont générés, afin de faciliter, accélérer et fiabiliser les tests accomplis sur la version déployée.&lt;/p&gt;

&lt;p&gt;À noter que nous avons déjà utilisé ce type d’environnement afin de tester rapidement une idée ou un prototype auprès d’un panel utilisateurs ou lors de phase de test terrain.&lt;/p&gt;

&lt;p&gt;Autre remarque : par soucis d’économie et d’écologie, nous faisons en sorte (via Pix Bot) d’éteindre automatiquement les RA les soirs et week-end. Nous avons développé une petite bibliothèque (Node.js) Open Source pour l’occasion – scalingo-review-app-manager.&lt;/p&gt;

&lt;h2 id=&quot;3-intégration&quot;&gt;3. Intégration&lt;/h2&gt;

&lt;p&gt;Il s’agit d’un environnement unique, dont le but est de tester et valider la bonne intégration d’un changement sur l’ensemble du code.&lt;/p&gt;

&lt;p&gt;Dans les faits, nous gardons un œil et une vigilance plutôt technique sur cet environnement. Mais les Product Owners (PO), de même que n’importe qui chez Pix, peuvent être susceptibles de l’utiliser pour remonter un problème, une erreur ou un feedback.&lt;/p&gt;

&lt;p&gt;En ce sens, nous faisons en sorte que cet environnement soit disponible à tout moment.&lt;/p&gt;

&lt;p&gt;Les données présentes au sein de cet environnement sont exclusivement des données de test. Il nous arrive quelquefois de les réinitialiser complètement ou d’en générer aléatoirement.&lt;/p&gt;

&lt;p&gt;L’environnement d’intégration peut contenir plusieurs changements prêts à être propagés en recette.&lt;/p&gt;

&lt;p&gt;Cet environnement est directement rattaché à la branche &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dev&lt;/code&gt; (sorte de branche tronc-commun) et donc automatiquement mis à jour à chaque fusion de branche / fermeture de PR.&lt;/p&gt;

&lt;h2 id=&quot;4-recette&quot;&gt;4. Recette&lt;/h2&gt;

&lt;p&gt;Autre environnement unique, le but de l’environnement de recette est de contrôler plus finement les changements apportés récemment à la plateforme. Il s’agit de la dernière étape pour un changement avant la production.&lt;/p&gt;

&lt;p&gt;Cet environnement peut aussi nous servir d’environnement de démonstration lors d’événements (notamment externes) ou de rituels (démonstration de fin d’itération), ou encore en interne pour tester les épreuves conçues par l’équipe de créateurs d’évaluations pédagogiques (postes ouverts).&lt;/p&gt;

&lt;p&gt;Les principaux utilisateurs de la recette sont les PO qui valident les impacts fonctionnels et ergonomiques. Mais les développeurs ne sont pas en reste et contrôlent assidûment les autres points critiques de la plateforme : performance, sécurité, autres répercussions techniques ou organisationnelles.&lt;/p&gt;

&lt;p&gt;Encore plus que l’intégration, cet environnement doit être opérationnel à chaque instant.&lt;/p&gt;

&lt;p&gt;De même que pour l’intégration, les données sont exclusivement des données de test. Celles-ci ne sont pas réinitialisées et conservées telles quelles depuis le début, afin de se préparer et valider une dernière fois d’éventuelles manipulations ou attentions à avoir pour la production.&lt;/p&gt;

&lt;p&gt;Contrairement à l’intégration, la recette n’est attachée à aucune branche Git. Sa mise à jour, c’est-à-dire la fusion de changements issus de la branche &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dev&lt;/code&gt;, est manuelle, déclenchée – la plupart du temps par les PO – via une procédure dite de “mise en recette” (ou MER).&lt;/p&gt;

&lt;h2 id=&quot;5-production&quot;&gt;5. Production&lt;/h2&gt;

&lt;p&gt;Il s’agit de l’environnement mis à la disposition des utilisateurs finaux de Pix (citoyens, partenaires, prescripteurs, certificateurs, etc.). C’est donc l’environnement le plus important de la plateforme.&lt;/p&gt;

&lt;p&gt;En tant que tel, il se doit d’être fonctionnel, performant, sécurisé, disponible à tous, partout, tout le temps, en toutes circonstances, dans les meilleures conditions d’exploitation possibles.&lt;/p&gt;

&lt;p&gt;Au plus fort de l’activité, l’environnement de production doit être capable d’accueillir efficacement plus d’une centaine de milliers d’utilisateurs par jour.&lt;/p&gt;

&lt;p&gt;L’idée de cet article n’est pas de rentrer dans tous les détails opérationnels concernant la production et sa mise en œuvre. Certains éléments sont toutefois notables et intéressants pour la suite.&lt;/p&gt;

&lt;p&gt;D’un point de vue “sécurité”, les applications sont hébergées sur des serveurs hébergés sur la zone SecNumCloud de Scalingo / Outscale. Tout est mis en œuvre pour dissocier le plus strictement possible l’environnement de production des autres environnements. Les données sont sauvegardées très régulièrement. De nombreux outils de monitoring / alerting sont développés et déployés pour assurer le parfait maintien opérationnel de cet environnement et chacun de ses composants.&lt;/p&gt;

&lt;p&gt;Les ressources sont évidemment boostées, dupliquées et configurées en conséquence. A noter que nous utilisons pour les applications en production la fonctionnalité auto-scaling de Scalingo.&lt;/p&gt;

&lt;p&gt;De même que la recette, la production n’est rattachée à aucune branche Git. Là aussi, le déploiement de changements en production est déclenché manuellement mais traité automatiquement via une procédure dédiée dite de “mise en production” (MEP).&lt;/p&gt;

&lt;h2 id=&quot;6-ad-hoc&quot;&gt;6. Ad hoc&lt;/h2&gt;

&lt;p&gt;Il s’agit d’un dernier type d’environnement très particulier dont le but est de tester ou valider un changement, une opération, une procédure ou une idée dans un contexte particulier.&lt;/p&gt;

&lt;p&gt;Par exemple, il nous est arrivé de monter un environnement ad hoc en zone SecNumCloud  afin de descendre certaines informations de production pour évaluer le comportement et les impacts d’un script de migration sur une table de plus de 700 millions de lignes.&lt;/p&gt;

&lt;p&gt;Nous pouvons aussi être amenés à créer un environnement ad hoc (en zone standard) pour nous aider à configurer un outil ou un service.&lt;/p&gt;

&lt;p&gt;Ces environnements sont toujours temporaires et détruits lorsque nous n’en avons plus l’usage.&lt;/p&gt;

&lt;h2 id=&quot;remarques&quot;&gt;Remarques&lt;/h2&gt;

&lt;p&gt;Les données utilisateurs (de production) ne sont jamais descendues vers un autre environnement.&lt;/p&gt;

&lt;p&gt;Lorsque les travaux l’obligent (ex : grosse migration de données), nous préférons créer un environnement ad hoc en zone SecNumCloud, avec des droits et des accès restreints et contrôlés.&lt;/p&gt;</content><author><name></name></author><category term="infrastructure" /><summary type="html">Une bonne gestion des phases d'un projet (une fonctionnalité, un changement technique, etc.) passe par une bonne gestion des environnements, depuis le poste du développeur jusqu'à la production.</summary></entry><entry><title type="html">Poisson d’avril !</title><link href="https://engineering.pix.fr/architecture/2021/05/11/poisson-avril-serieux.html" rel="alternate" type="text/html" title="Poisson d’avril !" /><published>2021-05-11T08:10:42+00:00</published><updated>2021-05-11T08:10:42+00:00</updated><id>https://engineering.pix.fr/architecture/2021/05/11/poisson-avril-serieux</id><content type="html" xml:base="https://engineering.pix.fr/architecture/2021/05/11/poisson-avril-serieux.html">&lt;p&gt;Parce que nous aimons rire, pour le 1er avril, nous avons décidé de faire une blague à nos utilisateurs. J’aimerais vous raconter comment cette blague est née, codée et déployée, car à l’échelle de Pix, une blague doit être réalisée sérieusement.&lt;/p&gt;

&lt;p&gt;Tout a été imaginé et implémenté en une après-midi du 31 mars 2021, lors d’un “Tech Time”. Le “Tech Time” c’est une après-midi toutes les 2 semaines dans laquelle l’équipe de développement se retrouve pour travailler sur des sujets non fonctionnels, et pas forcément en rapport avec Pix.&lt;/p&gt;

&lt;h4 id=&quot;prise-de-décision&quot;&gt;Prise de décision&lt;/h4&gt;

&lt;p&gt;Nous nous retrouvons vers 14h30 pour discuter de quoi faire. Rapidement, nous envisageons de faire quelque chose avec le compteur de Pix, élément central de l’interface. Décision est prise de le faire inexorablement baisser.&lt;/p&gt;

&lt;p&gt;Voilà à quoi ressemble le compteur à la base :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/poisson-avril/compteur-pix.png&quot; alt=&quot;Le compteur de Pix&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;implémentation&quot;&gt;Implémentation&lt;/h4&gt;

&lt;p&gt;Nous implémentons rapidement la première itération en décrémentant le compteur avec comme valeur minimale -999. Tout se passe en TDD et en mob programming, par plage de 15 minutes. Tests unitaires, tests d’intégration tout y passe.&lt;/p&gt;

&lt;p&gt;Nous nous rendons compte que nous devons gérer le cycle de vie de la blague. Il va falloir l’activer le 1er avril mais surtout l’arrêter dans la nuit. Pas question de déployer une version spécifiquement pour cela. Nous utilisons alors un mécanisme présent dans l’API de bascule de fonctionnalités (feature toggle) pour pouvoir le faire à chaud.&lt;/p&gt;

&lt;p&gt;Les comportements sont testés, l’API est modifiée et nous faisons &lt;a href=&quot;https://github.com/1024pix/pix/pull/2794&quot;&gt;la pull request sur le dépôt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Le spectacle&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A 17h, c’est l’heure du spectacle. Nous montrons le résultat à l’équipe. Le voici:&lt;/p&gt;

&lt;video src=&quot;/assets/images/posts/poisson-avril/spectacle.mp4&quot; controls=&quot;&quot;&gt;&lt;/video&gt;

&lt;p&gt;Hilarant n’est-ce pas ?&lt;/p&gt;

&lt;h4 id=&quot;la-communication-interne&quot;&gt;La communication interne&lt;/h4&gt;

&lt;p&gt;Vers 18h, il est temps d’informer le reste de Pix de cette “fonctionnalité”, et surtout de valider que nous pouvons la déployer et l’activer.&lt;/p&gt;

&lt;p&gt;Pour ne pas perturber les sessions de certifications du matin, il est décider de n’activer le poisson que le 1er Avril à 16h30.&lt;/p&gt;

&lt;h4 id=&quot;le-déroulé&quot;&gt;Le déroulé&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;31 mars - 19h30 : La PR est fusionnée.&lt;/li&gt;
  &lt;li&gt;1er Avril - 10h20 : Une mise en recette est faite.&lt;/li&gt;
  &lt;li&gt;1er Avril - 17h10 : La mise en production est effectuée. La blague est ensuite activée en ajoutant la variable d’environnement &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FT_IS_APRIL_FOOL_ENABLED=true&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;1er Avril - 22h30 : La variable d’environnement est supprimée. La blague n’est plus active :(&lt;/li&gt;
  &lt;li&gt;6 avril : la blague est supprimée du code&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h4&gt;

&lt;p&gt;Nous remercions tout d’abord le support qui a géré les retours d’utilisateurs, certains amusés, d’autres moins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qu’avons-nous appris ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si la fonctionnalité a été rapidement codée, la plus grosse partie du boulot a été de faciliter le déploiement pour que tout se passe bien. La bascule à chaud de fonctionnalité est bien précieuse pour cela.&lt;/p&gt;

&lt;p&gt;Imaginer, coder, tester et déployer une fonctionnalité non prévue en ~24h, c’est possible, et c’est bien sympa.&lt;/p&gt;

&lt;p&gt;Si tu aimes le service public, coder et surtout t’amuser, &lt;a href=&quot;https://www.welcometothejungle.com/fr/companies/pix&quot;&gt;nous recrutons&lt;/a&gt;.&lt;/p&gt;</content><author><name></name></author><category term="architecture" /><summary type="html">La vie et la mort d'une blague pour le 1er Avril.</summary></entry></feed>