diff --git a/assets/images/codebloc-dt-test-infer.png b/assets/images/codebloc-dt-test-infer.png new file mode 100644 index 0000000..280e8d2 Binary files /dev/null and b/assets/images/codebloc-dt-test-infer.png differ diff --git a/assets/images/codebloc-dt-unspecified.png b/assets/images/codebloc-dt-unspecified.png new file mode 100644 index 0000000..447fbf6 Binary files /dev/null and b/assets/images/codebloc-dt-unspecified.png differ diff --git a/assets/images/codebloc-dt-utc.png b/assets/images/codebloc-dt-utc.png new file mode 100644 index 0000000..d6b3e08 Binary files /dev/null and b/assets/images/codebloc-dt-utc.png differ diff --git a/chapters/final-opening.typ b/chapters/final-opening.typ new file mode 100644 index 0000000..44a04b6 --- /dev/null +++ b/chapters/final-opening.typ @@ -0,0 +1,2 @@ +#pagebreak(weak: true) +== Ouverture \ No newline at end of file diff --git a/chapters/réalisation.typ b/chapters/réalisation.typ index caec8fc..f0f90b4 100644 --- a/chapters/réalisation.typ +++ b/chapters/réalisation.typ @@ -69,7 +69,7 @@ Il n'existe pas d'autre changement majeur qui pourrait porter atteinte à l'int 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, sous 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.\ +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èle 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. @@ -113,7 +113,7 @@ Il est possible de créer des règles avec chaque propriété d'un objet d'infra 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 execution. +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 @@ -131,17 +131,127 @@ 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 en continu, 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 exploiter les résultats. +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 développer durant ma troisième année de licence. 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 paque, jee me suis retrouvé confronté à de nombreuses erreurs +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 -=== Problèmes rencontrés \ No newline at end of file +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 plusieures dizaines voir centaines d'utilisateurs connectés simultanément. Les performances du serveur sont donc capable d'absorber le choc et de fournir des performance optimales lorsque necessaire.\ +Les environnements de développements, eux, ne compte qu'une petite dizaine d'utilisateurs connectés simultanément tout au plus. Les serveurs sont donc dimensionné de manière à délivrer des performances raisonnables en essayant de le garder le plus petit possible. De la sorte, il devient possible de démultiplier les environnements de développement sur le même systèmes pour les différents projet de l'entreprise et ainsi réduire le couts.\ +C'est en me rappelant de ça que j'ai réalisé que les tests ont étés réalisés sur des environnements inégaux au niveau performances. La migration ayant déjà été validé, 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étence d'administrateur système necessaire, 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édement pour la base PostgreSQL 14 et je récupère le résultat de l'outil de benchmark une fois exéctuée 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. \ No newline at end of file diff --git a/main.typ b/main.typ index 32f8a02..f46105a 100644 --- a/main.typ +++ b/main.typ @@ -55,4 +55,6 @@ Dans ce document, je commencerai par présenter ce qui m'a amené à rejoindre l #include "chapters/etat-de-l_art.typ" -#include "chapters/réalisation.typ" \ No newline at end of file +#include "chapters/réalisation.typ" + +#include "chapters/final-opening.typ" \ No newline at end of file