#import "../template.typ" : * #pagebreak(weak: true) = Réalisation == Introduction Je présente dans cette section le travail réalisé durant les neuf mois au sein de l'entreprise Unit Solutions. Portant sur le sujet d'une migration de base de données dans le but de maintenir les technologies de l'application à jour, je présente tout d'abord les différents éléments qui justifient le choix d'effectuer cette migration de base de données, par la suite, j'expose l'analyse préliminaire réalisée pour s'assurer que la migration reste pertinente, j'aborde plus tard les changements réalisés dans le code et enfin, je présente les résultats obtenus post-migration. == Pertinence et philosophie Lorsque le sujet de mettre à jour le SGBDR a été présenté, il n'a pas été décidé de remplacer PostgreSQL. Comme expliqué précédemment, PostgreSQL excelle dans la gestion de données géographiques et attributaires, il est complet et surtout très bien maintenu par ses développeurs. Ces différentes raisons en font un candidat parfait pour l'environnement InfSuite plus que tout autre système proposé par la concurrence. Maintenir PostgreSQL en tant que SGBDR de l'application écarte déjà un bon nombre de solutions au niveau logiciel. Je peux d'ores et déjà écarter les logiciels payants et longs à maîtriser autres que PgAdmin pour effectuer cette migration, qui reste le logiciel le plus adéquat puisque nous ne cherchons pas à changer d'environnement. L'application InfSuite souhaite utiliser une fonctionnalité importante de PostgreSQL : les timestamps, ou horodatages en français. En effet, InfSuite utilise un système de version basé sur de l'horodatage. Quand un utilisateur met à jour les informations d'un document dans l'application (inspection, restauration, etc.), il crée une nouvelle version du document. Si deux utilisateurs récupèrent la même version d'un document, qu'ils effectuent tous deux des mises à jour sur ce document, que l'utilisateur A sauvegarde ses changements en premier, que l'utilisateur B essaye de sauvegarder le document avec ses changements après que la version ait changé, alors le serveur lui signalera qu'il ne possède pas la dernière version du document qui a été modifiée entre-temps et donc qu'il ne peut pas sauvegarder ses modifications sans mettre à jour la version de son document. Pour assurer ce suivi de version, le document contient une version en base, qui est un timestamp sans fuseau horaire. Un problème peut alors apparaître si deux utilisateurs, sur des fuseaux horaires différents, effectuent des modifications au même instant. Le serveur ne pourra pas vérifier l'information de fuseau horaire puisqu'elle n'est pas sauvegardée en base et donc, si l'utilisateur effectue une sauvegarde même avec une ancienne version du document, cette dernière écrasera les derniers changements. Dans le schéma @file-versionning-schema, deux utilisateurs génèrent des versions de fichiers différentes. Un utilisateur enregistre ses changements en premier et génère la version AB du document. Quand le second utilisateur souhaite enregistrer ses changements C, un conflit de fichier l'en empêche. C a comme origine la version A et non pas AB, l'utilisateur doit donc récupérer les changements de la version AB avant d'enregistrer ses changements. #figure( image("../assets/images/file-versionning.png"), caption: [Schéma d'un conflit de version entre deux version d'un document] ) De plus, un point important pour lequel le souhait de mettre à jour la base de données a été exprimé est de pouvoir profiter des améliorations du #ref-glossary(term: "SGBDR")[SGBDR] PostgreSQL. Il y a par exemple des mises à jour de sécurité du système. Mettre à jour une application et ses différents composants permet de profiter des mises à jour de sécurité afin d'éviter des vulnérabilités qui pourraient mettre en péril les données des clients et l'intégrité de l'entreprise. Les vulnérabilités sont connues sous le nom de Common Vulnerabilities and Exposures et, en prenant pour exemple la mise à jour pour corriger la faille CVE-2023-5869, PostgreSQL résout un problème permettant d'écrire des octets arbitraires en mémoire, qui facilitait l'exécution de code arbitraire. Normalement, les ORM permettent de faire automatiquement de la paramétrisation de requêtes pour prévenir d'injections potentielles. Mais il se peut que des failles supplémentaires se cachent dans le code et permettent donc, malgré tout, d'exploiter ces failles. Les corriger sur le système permet alors d'éliminer en grande partie le risque d'attaque. En complément des vulnérabilités, les mises à jour apportent des améliorations de performances cruciales. En théorie, les ORM jouent un rôle essentiel dans les performances d'une application, car ils utilisent un cache pour éviter d'effectuer des requêtes vers la base de données à trop haute fréquence. Dans le cas de la base PostgreSQL, elle est aussi utilisée pour le système d'information GIS, il y a donc beaucoup de données à délivrer et avec un grand nombre de requêtes. Améliorer les performances permet de réduire la latence ressentie par l'utilisateur lorsqu'il cherche à charger des données dans l'application. Sur la version 16 de PostgreSQL, il est promis une amélioration de 10 % des performances du #ref-glossary(term: "SGBDR")[SGBDR]. Un tel gain de performance permet d'améliorer grandement l'expérience utilisateur. Il faut maintenant déterminer quel est le type de migration le plus adéquat dans ce cas de figure pour éviter les coûts supplémentaires qui peuvent être facilement évités. Je dois dans un premier temps chercher les technologies utilisées dans l'application et m'assurer que celles-ci soient compatibles avec la nouvelle base. Par la suite, je vais devoir valider la compatibilité entre la version de la base source et de la base cible. Avec cette confirmation, je pourrai alors chercher la meilleure manière d'effectuer cette migration parmi les différentes options disponibles que j'ai pu exposer précédemment. === Analyse préliminaire En regardant côté architecture, je cherche quelles sont les applications qui risquent de rencontrer un problème lors de la migration. Les seuls systèmes qui établissent une relation directe avec la base de données sont côté #ref-glossary(term: "Back-End")[back-end]. On limite donc l'impact sur l'application globale s'il y a des changements et des mises à jour à réaliser pour permettre à l'application de continuer à fonctionner avec la base de données. Le projet #ref-glossary(term: "Back-End")[back-end] de l'application est développé en C\# avec .NET qui gère la partie web de l'application. Un certain nombre de librairies sont utilisées par le projet InfSuite pour permettre d'étendre les fonctionnalités de l'application sans devoir dépenser de temps de développement. Il existe des paquets alimentés par Microsoft, par exemple comme .NET, #ref-glossary(term: "EF")[#display-def("EF")], ... et d'autres librairies qui sont elles développées par des développeurs tiers qui ont souhaité ajouter une nouvelle fonctionnalité à l'écosystème. Quand on souhaite effectuer une mise à jour de l'application, l'environnement Microsoft nous propose d'effectuer la mise à jour de ces librairies nommées NuGet Packages dans l'écosystème C\# via NuGet, le gestionnaire de dépôts associé. Il permet de faire du versionnement des librairies, sur le même principe que PostgreSQL qui utilise le concept de versionnement pour livrer ses mises à jour. Chaque version d'une librairie peut notifier des incompatibilités avec certaines versions d'autres librairies. Je dois donc étudier les composants qui permettent de faire la liaison à la base de données et également les librairies dont peut ou qui peuvent dépendre de ces composants. Il faut prêter attention à chaque composant mis à jour indépendamment qui pourrait causer d'autres problèmes de compatibilité avec l'application et casser des fonctionnalités. Un projet C\# dans l'environnement Microsoft est composé d'une solution, de sous-solutions et de projets. Les sous-solutions d'un projet sont des solutions indépendantes intégrées au projet. Un peu à la manière de librairies, je peux créer du code qui pourra être réutilisé dans différents projets indépendamment, sans devoir le dupliquer. Chaque projet possède ses propres NuGet packages, mais la solution peut gérer des dépendances, c'est-à-dire que le package sera ajouté à la liste des dépendances globales et sera installé dans tous les projets de la solution. Dans mon cas, la solution #ref-glossary(term: "Back-End")[back-end] d'InfSuite est composée de 65 projets. Il y a le projet "WebApi" qui gère les routes et les interactions avec l'application #ref-glossary(term: "Front-End")[front-end] qui est disponible pour le client et qui dépend du projet "Server". Le projet "Server", lui, contient la logique de certaines interactions simples, par exemple la recherche d'IO via la barre de recherche dans l'application. Le projet "Server" dépend du projet "Core" qui gère certaines fonctionnalités plus poussées de l'application autant pour la partie web que pour d'autres composants. Il existe de nombreuses autres dépendances dans l'application qui complexifient la gestion des packages. Par exemple, dans le diagramme @ik-webapi-dependencies, on remarque rapidement que les dépendances internes au projet peuvent être relativement complexes. #figure( image("../assets/images/ik-webapi-dependencies.png"), caption: [Arbre de dépendances du projet WebApi d'InfSuite] ) Si je souhaite par exemple mettre à jour un package dans le projet "Geometry", tous les projets dépendants sont impactés par cette mise à jour, il faut donc s'assurer que rien ne vient casser le bon fonctionnement de l'application en profondeur. Je m'intéresse donc aux liens entre les différents projets ainsi qu'aux librairies que je souhaite mettre à jour pour m'assurer de leur compatibilité pour toute l'application. === Compatibilité et décision La première étape est de valider la compatibilité entre la version 14 et 16 de PostgreSQL. Une première manière de valider cela est d'aller sur les notes de mise à jour sur le site officiel et de chercher les changements qui peuvent affecter la compatibilité entre les deux bases. Après avoir analysé en détail les changements, il y a trois grandes catégories. Il y a l'impact sur l'administration système, celui sur les requêtes SQL et celui sur l'incompatibilité des données. La partie administration système n'est pas gérée par les développeurs d'InfSuite, elle est gérée par d'autres employés de l'entreprise, je peux donc me concentrer sur la partie requêtes et données. Pour la partie des données stockées, en analysant les données actuelles, je peux relever quelques changements qu'il faut surveiller : - Les jsonb peuvent être utilisés par les systèmes d'informations géographiques pour stocker les informations géographiques et attributaires. Il faut donc surveiller le comportement de l'application à ce niveau. - Les uuid qui ont subi des optimisations pour mieux gérer les identifiants uniques universels et qui sont utilisés comme identifiants uniques pour les tables dans le projet. - Les types numeric qui sont également utilisés dans l'application et qui ont subi des optimisations de performances. Il n'existe pas d'autre changement majeur qui pourrait porter atteinte à l'intégrité des données lors de la migration. Une fois la première étape validée pour l'intégrité théorique des données, il faut valider la seconde étape qui est la réalisation de la migration. Le seul moyen de s'assurer de la complète compatibilité entre les deux systèmes est d'effectuer une première migration dite "à blanc". Elle a pour but de mettre en place un système séparé de l'environnement de production, pour s'assurer de ne pas impacter l'application accessible par les clients. L'objectif de ce système est d'avoir une base de tests sur laquelle je peux m'assurer que l'application fonctionne toujours après la migration de données et que si des problèmes s'annoncent, il n'y ait aucun autre impact que de les constater. Avec la validation obtenue précédemment sur la compatibilité des deux systèmes, j'ai pu valider, toujours avec l'approbation de mon chef de projet et de l'architecte d'application, que la migration la plus adaptée dans notre cas serait une migration hybride. Le but de la migration hybride est de combiner deux types de migration de données que j'ai pu citer précédemment dans ce document. Comme je veux ajouter un fuseau horaire aux horodatages qui servent de versions, je dois modifier les données de la base source. Cependant, cela implique de modifier les données et les modèles de données associés. Pour éviter de devoir utiliser un logiciel tel que PgAdmin et de passer du temps à manipuler le modèle de données qui pourrait causer des problèmes, je préfère utiliser des outils bien maîtrisés et intégrés sur le projet InfSuite. Pour l'import de données, cette tâche a été réalisée par un administrateur système de l'entreprise. Il a utilisé la méthode d'import d'un fichier #ref-glossary(term: "Dump")[dump] de la base source dans la nouvelle base. Je l'ai présenté ci-dessus, n'ayant aucun souci de compatibilité sur les types de données, l'intégration de ces données s'est faite sans accroc dans la base de test. Pour mettre à jour le modèle de données de manière synchrone avec les mises à jour de l'application et éviter des soucis de compatibilités, l'environnement InfSuite utilise un système de fichiers scripts. Rédigés en SQL, ces scripts viennent mettre à jour le schéma de base de données et sont exécutés automatiquement par le serveur lors de la mise à jour de l'environnement ciblé (dev, staging, production, etc.). Pour l'exemple des horodatages, je rédige un script SQL que je pourrais exécuter manuellement sur l'environnement de développement avec la nouvelle version de PostgreSQL et effectuer mes tests. Cependant, si je veux intégrer mes modifications sur les autres environnements, il faut que je sauvegarde mes changements sur Azure DevOps, je n'ai pas la permission de faire ces changements manuellement. Enfin, pour mettre à jour les données dans la base une fois le schéma de base modifié, je décide de faire cette migration en temps réel. La compatibilité entre les types de données timestamp et timestamptz permet de garder en base l'ancien format de version, de mettre à jour le schéma de base puis de mettre à jour les données en y rajoutant le fuseau horaire. Comme la colonne "version" est utilisée à de nombreux endroits, créer un type de donnée côté serveur qui va s'occuper de les traiter rend la tâche moins complexe et chronophage. Avant de passer à la réalisation, je dois m'assurer de la compatibilité des dépendances externes pour effectuer la migration. Ce qui m'intéresse particulièrement ici, est de mettre à jour le package Npgsql car il permet de communiquer avec la base de données. Dans une application .NET, il y a la possibilité d'utiliser un #ref-glossary(term: "ORM")[#display-def("ORM")] pour simplifier les interactions avec les bases de données. Le projet InfSuite utilise historiquement l'#ref-glossary(term: "ORM")[ORM] #ref-glossary(term: "EF")[EntityFramework]. Il faut bien différencier #ref-glossary(term: "EF")[EntityFramework] qui n'est plus activement développé, de son successeur EntityFrameworkCore qui offre de nouvelles fonctionnalités qui ne seront plus implémentées dans #ref-glossary(term: "EF")[EntityFramework]. #ref-glossary(term: "EF")[EntityFramework] utilise le connecteur Npgsql pour effectuer les requêtes vers la base de données. En recherchant les versions disponibles de Npgsql pour EntityFramework, je me suis rendu compte qu'il fallait prêter attention à une fonctionnalité importante de PostgreSQL que le projet InfSuite veut maintenant exploiter : les horodatages avec des fuseaux horaires. La version post mise à jour de Npgsql était la version 4.1.13 et la dernière version disponible qui utilise les horodatages avec fuseau horaire est la version 8.0.3. En essayant de le mettre à jour avec cette version, je me suis rendu compte qu'#ref-glossary(term: "EF")[EF] n'était pas compatible avec la version 8.0.3. La dernière version du paquet Npgsql disponible pour #ref-glossary(term: "EF")[EF] étant la version 6.4.3, je dois donc me contenter de cette version. Avant de valider l'utilisation de ce dernier dans toute l'application, je dois valider qu'elle supporte les horodatages avec fuseau horaire et qu'elle est également compatible avec le reste de l'application. En se référant à la documentation fournie, la première version à intégrer les horodatages avec fuseau horaire est bien la version 6.X.X d'Npgsql, elle est donc compatible avec le besoin de sauvegarder les fuseaux horaires pour les versions. Si on souhaite passer sur la nouvelle version du paquet Npgsql, la version 8.X.X, il n'y a pas d'autre choix que de passer d'EF à EFCore. Faire le choix de mettre à jour l'application EFCore permettrait de compléter l'objectif de maintenir l'application à jour et saine. En effet, pour rappel, EF n'est maintenant plus mis à jour régulièrement. L'étude d'impact des coûts et des changements impliqués par une telle mise à jour a déjà été réalisée à priori de mon analyse et il en est ressorti que pour l'année courante, un tel budget ne pouvait pas être accordé pour cette mise à jour. Si je veux mettre à jour l'application pour utiliser les dernières fonctionnalités disponibles sans passer par une mise à jour d'EF, le seul choix qu'il reste est de mettre à jour Npgsql vers la version 6.X.X. Le schéma @npgsql-versions-incompatibilty ci-dessous résume la situation d'incompatibilités entre les versions des trois éléments à savoir respectivement : à gauche Entity Framework, au centre le paquet Npgsql et à droite la base de données PostgreSQL. #figure( image("../assets/images/npgsql-versions-incompatibilty.png"), caption: [Problématique d'incompatibilité entre les différents éléments permettant au serveur et à la BDD d'échanger des informations.] ) === Tests de performance La dernière étape permettant de valider cette étape de migration est de s'assurer que la nouvelle version de PostgreSQL installée à de meilleures performances que la version remplacée. Pour valider cela, il a été développé un système en interne de test de performance. L'architecture complexe de la base et la relation avec des données géographiques et attributaires peut rendre les requêtes parfois plus longues d'exécution. Pour effectuer un test de performance, le système utilise une fonctionnalité gourmande en ressource, les groupes. Dans l'environnement InfSuite, il est possible d'afficher sur la carte les objets d'infrastructure. Par défaut tous les objets d'un dataowner sont affichés. Pour permettre d'axer son travail sur des objets d'infrastructures particuliers, alors il est possible d'appliquer un filtre à toutes ces entités. Le mode "Groupe" de l'application permet de créer alors deux types différents de filtres. Le premier type de filtres est un filtre statique. Peu gourmand en ressource et très simple d'utilisation, on peut y ajouter des entités manuellement et cela va permettre de n'afficher que les objets sélectionnés dans l'application. Fonctionnellement parlant ce n'est qu'une relation de base de données. Le second type de filtre, plus complexe, est le filtre dynamique. Les filtres dynamiques permettent de gérer automatiquement les entités qui seront incluses ou exclues de l'affichage. Pour ce type de filtre, il est possible de créer des règles de filtrage avancées pour permettre à l'utilisateur plus de flexibilité.\ Un peu à la manière d'une requête SQL, il est possible de construire une requête qui va être exécutée pour permettre de retrouver les entités à partir de critères. Il est par exemple possible de demander de filtrer les ouvrages par position géographique en ne prenant en compte que ceux au-dessus d'une certaine latitude et de rajouter à ce filtre uniquement les ouvrages qui contiennent un certain numéro dans leur nom.\ Il est possible de créer des règles avec chaque propriété d'un objet d'infrastructure, d'appliquer un ordre pour les conditions, de faire des agrégats, des jointures,... Les utilisateurs ont la liberté de créer leur propre requête, une requête pouvant devenir rapidement complexe, elle peut donc prendre plus de temps à filtrer les objets d'infrastructures pour les retourner à l'utilisateur.\ Étant utilisé par les services GIS de l'application, s'assurer de la performance de ce système est primordial. Ce que l'outil permet de tester est ce système de groupe/filtres. Il va chercher les différents filtres existants en base de données, peu importe le client qui a pu les créer, et les exécuter.\ Cette méthode permet de connaître les performances réelles de la base dans un cas pratique spécifique à l'application et non pas d'avoir des performances théoriques fournies par les développeurs du #ref-glossary(term: "SGBDR")[SGBDR] qui peuvent être tournées en leur faveur. Nous cherchons donc à savoir si le chiffre de 10% en gain de performances est réel ou non. L'objectif est donc de comparer les performances de la base de production encore sous PostgreSQL 14, avec les performances d'une base de développement installée pour l'occasion, elle, sous PostgreSQL 16. Après avoir exécuté l'outil de benchmark sur les deux bases, je récupère les données brutes en sortie de programme pour les analyser. Ci-dessous un exemple de données de sorties fournies par le programme après exécution. ```csv eruid,description batman,uses technology superman,flies through the air spiderman,uses a web ghostrider, rides a motorcycle #GROUP_OBJECT_PROFILE#accessgroupGroupProfile cn,description daredevil,this group represents daredevils superhero,this group represents superheroes #GROUP_OBJECT_PROFILE#aixaccessgroupGroupProfile aixgroupadminlist,ibm-aixprojectnamelist eadmins,eadmingroup eguests,eguestgroup ``` Les données sont au format CSV, je peux ainsi les importer dans un logiciel tableur tel qu'Excel pour les manipuler et les comparer. L'application de production étant accessibles aux clients de manière continue, les données stockées peuvent changer très rapidement et présenter un delta avec les données sauvegardées à posteriori lors la mise en place de la base de développement.\ Cette différence doit être prise en compte dans la comparaison des données puisque je dois alors utiliser des fonctions plus poussées d'Excel pour retrouver les données similaires entre les deux tables et en comparer les résultats.\ J'obtiens dans les données les informations d'identification des groupes testés, le client à qui ils appartienent, le nombres d'ouvrages que les groupes contiennent après filtrage, le temps total d'exécution du groupe et le temps moyen calculée pour le chargement d'un objet d'infrastructure. L'unité de temps est donnée en millisecondes. Pour comparer les performances de la base, je commence par récupérer les données globales des deux bases, à savoir le nombre total d'objets d'infrastructures qui ont étés traités et le temps global d'exécution de traitement. Pour connaître le temps total qu'a mis la base pour traiter tous les groupes, j'utilise la requête ``` =SUM(pg14[Load_Duration_In_Ms])``` qui fait une simple somme de tous les résultats de la colonne ``` Load_Duration_In_Ms```. Je fais la même chose pour les résultats des deux bases et pour la colonne ``` Count_Ios``` que je réutilise avec un simple calcul de différence dans la requête ``` =Total_Ios_Count_14 - Total_Ios_Count_16```. Comme le présente le tableau des résultats @result_sheet_benchmark_1, les données dans base de production ont déjà changées, il faut donc que je tienne compte dans mes résultats. #figure( table( columns: (auto, auto, auto, auto), align: horizon, table.header([], [*Postgres_14*], [*Postgres_16*], [*Gap_From_14_To_16*]), [*Total_Io_Count*], [323527], [318634], [-4893], [*Total_Duration*], [3577961], [3450208], [-127753], [*Identical_Ios_Duration*], [3499476], [3438261], [-61215], [*Average_Ios_Load_Duration*], [11,05923462], [10,82812255], [-0,231112075] ) ) Structure du tableau de résultats: - La colonne *``` Gap_From_14_To_16```* contient les resultats des comparaisons des données obtenues entre Postgres 14 et Postgres 16. - La ligne *``` Total_Io_Count```* indique le nombre total d'objets d'infrastructures qui ont été filtrés par les différents groupes traités. - La ligne *``` Total_Duration```* indique le temps total de traitement (en millisecondes) de tous les groupes. - La ligne *``` Identical_Ios_Duration```* indique le temps de traitement total (en millisecondes) uniquement pour les OIs identiques entre les deux bases. - La ligne *``` Average_Ios_Load_Duration```* indique le temps de traitement moyen écoulé (en millisecondes) par objet d'infrastructure. Dans les réultats obtenus, je remarque rapidement que le temps de traitement entre les deux bases n'est pas drastiquement différent. Pour le temps de traitement global des objets d'infrastructure identiques entre les deux bases, on ne gagne que 0.23 milliseconde après la migration. Pour filtrer mes résultats j'utilise la méthode ``` =SUM(SUMIF(pg14[Group_Id]; VLOOKUP(pg14[Group_Id]; pg16[Group_Id]; 1;FALSE); pg14[Load_Duration_In_Ms]))``` qui va exclusivement récupérer les lignes où les groupes anaylsés sont présents dans les deux bases puis faire la somme des résultats de performances. Pour terminer mon analyse, je transforme les résultats en pourcentages dans le tableau @perf_gain_sheet_1. Avec ça je peux comparer les 10% de performances annoncées avec les résultats réels obtenus. #figure( table( columns: (auto, auto), align: horizon, [*Identical_Group_Count*], [3028], [*Identical_Ios_Perf_Gain*], [1.78%], [*Total_Perf_Gain*], [3.70%], ) ) Comme je l'ai remarqué rapidement, le gain réel de performances entre les deux bases n'est que de 3.7%. Le score affiché pour les données anaylsées identiques entre les deux bases est encore plus faible avec uniquement 1.78% de perfomances gagnées. Ce résultat peut être le résultats de nombreux facteurs. Une première hypothèse qu'on peut emettre est que l'analyse de performances donnée par PostgreSQL concerne d'autre types de requêtes ou de perfomances de base. Un autre cas de figure est que le temps de traitement le plus long dans notre cas est obtenu par l'interface entre le code et la base: EntityFramework. En effet il se pourrait que la gestion des requêtes côté ORM soit la plus couteuse en ressource que tout le reste du système ce qui impacte le résultat final. Une dernière hypothèse serait un biais dans mon analyse. Une erreur humaine est plausible et il n'est pas exclu que je me sois trompé dans mes requêtes ou que j'ai oublié de prendre en compte tout autre élément ayant un impact direct sur les performances obtenues. J'ai donc pu rendre mon rapport sur les comparatifs de perfomances à mon chef de projet pour qu'il valide, ou non, la suite de la migration de base de données. Les résultats de perfomances obtenus seuls, ne permettent pas de justifier un tel investissement pour cette migration. Cependant, comme évoqué plus haut, d'autres points importants tel que la sécurité ou la pérennité de l'application en mettant à jour la base on permit de soutenir la décision de continuer la migration. J'ai donc pu continuer les étapes de migration en passant sur la partie d'adaptation du code pour permettre à l'application de fonctionner avec la nouvelle base. #pagebreak(weak: true) == Adaptation du code Dans l'application, 24 projets au total utilisent le paquet Npgsql. Ces projets ne sont pas uniquement dédiés à la partie #ref-glossary(term: "Back-End")[back-end] de l'application, mais sont également des outils tiers développés pour des besoins spécifiques. Je peux par exemple citer l'outil IkCoordToRvg que j'ai pu aborder dans mon mémoire de licence, qui était le projet sur lequel j'ai pu travailler durant toute une année. Comme pour tout autre projet, il utilise des dépendances au projet Core d'InfSuite, qui nécessite lui-même le paquet Npgsql. Lorsque j'ai tenté une première fois de mettre à jour le paquet, je me suis retrouvé confronté à de nombreuses erreurs de compatibilité avec des dépendances externes. Pour régler les conflits, une simple mise à jour vers une version plus récente des paquets concernés a suffi. Tout comme pour la mise à jour Npgsql, je m'assure dans un premier temps qu'il n'y ai pas de changements majeurs qui risque de casser le bon fonctionnement de l'application, puis j'effectue la mise à jour. Je dois maintenant adapter le code concerné. Pour gérer les formats de certaines données, l'application utilise une inférence du type de données. Pour le cas de la mise à jour de la colonne version, on la met à jour pour utiliser des horodatages avec fuseau horaire. Cependant, le reste des modèles utilisant des horodatages dans d'autres parties de l'application n'ont pas été mis à jour. Ils utilisent toujoursla versions sans fuseau horaire. Il faut donc préciser à notre application que pour le type de données DateTime dans l'application InfSuite, cela correspond au type DateTime2 en base de donnée (horodatage sans fuseau horaire). Dans l'autre sens, il n'y a pas la même problématique puisque les DateTime en C\# contiennent un fuseau horaire.\ Ainsi, si au moment de la conversion l'horodatage ne contient pas cette information, alors le serveur utilise par défaut le fuseau horaire de sa propre localisation. Pour gérer l'inférence des données, je dois rajouter une nouvelle étape de vérification pour le convertisseur JSON en lui précisant comment gérer le nouveau format. Un simple ajout de la ligne ci-dessous dans les types personnalisé permet cette inférence. Elle récupère simplement le contenu textuel de la requête et essayer de valider que c'est une date. Si c'est le cas alors, il infère le type Date pour l'application, sinon il exécute le reste du programme comme normalement. ```cs JsonTokenType.String when reader.TryGetDateTime(out DateTime datetime) => DateTime.SpecifyKind(datetime, DateTimeKind.Unspecified), ``` Pour préciser quels sont les entitées en base de données qui n'utilisent pas le format par défaut DateTime2, je rajoute à la variable ``` Version``` dans les entitées concernées l'anotation ``` [JsonConverter(typeof(DateTimeUtcConverter))]```. De la sorte je le force à utiliser un convertisseur différents. Pour permettre de gérer ces dates différement j'ai pu créer deux convertisseurs de dates. Ils ont un fonctionnement similaire, il ne varient que par le type de date retourné. Dans le bloc de code @codebloc-dt-unspecified les dates sont retournée sans fuseau horaire s'il n'est pas présent tandis que dans le bloc de code @codebloc-dt-utc il prends en valeur par défaut le fuseau horaire UTC.\ Il est bon de remarquer que j'utilise dans mon anotation de variable pour mon entité ci-avant, le convertisseur que j'expose dans le bloc de code @codebloc-dt-utc. #figure(image("../assets/images/codebloc-dt-unspecified.png"), caption: [Bloc de code d'un convertisseur de date sans fuseau horaire]) #figure(image("../assets/images/codebloc-dt-utc.png"), caption: [Bloc de code d'un convertisseur de date avec fuseau horaire]) Pour m'assurer que mes changements sont corrects et que je n'ajoute pas de code qui pourrait mettre en défaut l'application, je créé pour toute l'application des tests qui permettent de vérifier que la conversion fonctionne comme attendu. J'ai rajouté des tests manquants, avec le test d'inférence des types comme dans le bloc de code @codebloc-dt-test-infer, pour vérifier qu'en fonction du continu le convertisseur JSON retrouve le bon type de données. J'ai également ajouté des tests pour les deux types de convertisseurs de données en lui fournissant différentes formes de dates et en m'assurant que ce qu'il me retourne, correspond à une date bien formée avec ou sans fuseau horaire en fonction du contexte. #figure(image("../assets/images/codebloc-dt-test-infer.png"), caption: [Bloc de code d'un test d'inférence JSON]) Mes derniers ajouts portent sur des modifications mineures comme des changements dans les logs de l'application pour facilier le déboguage, des ajout de type de date dans les différentes énumérations concernées, etc.\ #pagebreak(weak: true) == Résultats === Performances finales Durant le processus de modification du code, je me suis rendu compte que j'avais omis un détail lors de mon analyse de performances. Le serveur de production est dimensionné pour supporter la charge de plusieurs dizaines, voire centaines d'utilisateurs connectés simultanément. Les performances du serveur sont donc capables d'absorber le choc et de fournir des performances optimales lorsque nécessaire. Les environnements de développement, eux, ne comptent qu'une petite dizaine d'utilisateurs connectés simultanément tout au plus. Les serveurs sont donc dimensionnés de manière à délivrer des performances raisonnables en essayant de les garder les plus petits possible. De cette sorte, il devient possible de démultiplier les environnements de développement sur les mêmes systèmes pour les différents projets de l'entreprise et ainsi réduire les coûts. C'est en me rappelant cela que j'ai réalisé que les tests ont été réalisés sur des environnements inégaux au niveau des performances. La migration ayant déjà été validée, je vais relancer un test de performance sur la nouvelle base pour valider les performances réelles obtenues une fois la migration terminée. La mise en production étant un environnement sensible et n'ayant pas les compétences d'administrateur système nécessaires, je n'ai pas pu participer à la mise en place de la mise en production des changements. J'ai cependant pu vérifier que tout fonctionnait comme attendu en inspectant l'environnement de production. Une fois le serveur de production mis à jour avec les nouveaux éléments, j'effectue un nouveau test de performance pour valider que le biais de différence de performance entre les environnements est bien la source du problème. Je conserve les données obtenues précédemment pour la base PostgreSQL 14 et je récupère le résultat de l'outil de benchmark une fois exécuté sur la nouvelle base et sur le même environnement pour une comparaison correcte. #figure( table( columns: (auto, auto, auto, auto), align: horizon, table.header([], [*Postgres_14*], [*Postgres_16*], [*Gap_From_14_To_16*]), [*Total_Io_Count*], [323527], [346761], [23234], [*Total_Duration*], [3577961], [4025135], [-447174], [*Identical_Ios_Duration*], [3577257], [3980590], [403333], [*Average_Ios_Load_Duration*], [11,05923462], [10,82812255], [0,54857306] ) ) Dans le tableau @result_sheet_benchmark_2 les résultats bruts ne donnent pas forcement de gros indices sur la différences par rapport à la première analyse comportant potentiellement un biais. Je constate une inversion sur la quantité de données traitées, cette fois-ci la nouvelle base de données à eu plus d'OIs à traiter et par conséquent les temps de traitement on aussi augmentés. Une comparaison avancée me permet d'obtenir un comparatif plus explicite entre les deux base. #figure( table( columns: (auto, auto), align: horizon, [*Identical_Group_Count*], [3044], [*Identical_Ios_Perf_Gain*], [10,13%], [*Total_Perf_Gain*], [11,11%], ) ) Les résultats du tableau @perf_gain_sheet_2 confirment ma supposition. La différence entre les deux environnements a biaisé les résultats des analyses. Sur les OIs identiques, on retrouve cette fois-ci les dix pourcents annoncés par l'équipe de développement de PostgreSQL. Encore mieux, je remarque que pour les OIs analysés au global dans les deux bases, la différence de performances grimpe jusqu'à onze pourcents. Le projet bénéficie donc des améliorations de performances promises par l'équipe PostgreSQL, mais surtout, de performances encore plus accrues que prévues par rapport à l'ancien système. === Problèmes rencontrés Après la mise en place du nouveau système, une erreur de compatibilité dans les transactions entre l'application Observo et InfSuite est appartue. Malgré les nombreuses étapes de tests, les anaylses préliminaires, la mise en place du système sur un serveur intermédiaire nommé staging qui est très similaire à l'environnement de production, le problème n'a pas pu être détécté avant. Il n'est survenu que lors de la mise en place des mises à jour sur le serveur de production. Le problème concernait une erreur de compatibilité avec les fameuses date lors de l'échange de données entre les deux application. La différence de format fournie par InfSuite n'était pas valide côté Observo, le système retrouvait donc la transaction en échec.