Closed adipasquale closed 2 weeks ago
@victormours si tu veux jeter un oeil à cette PR avant que je n’en parle avec RDVI
Merci pour cette refacto @adipasquale ça va être plus clean comme ça !
Ma remarque principale est finalement une remarque que j'avais déjà faite à @victormours qui m'avait suggéré une refacto en Gem : Je trouve que le passage en Gem ne solutionne pas le problème principal de ce projet qui est la dépendance directe avec le code de Rdvsp en tant que "host" de cette Gem.
Plus qu'une Gem (qui doit donc être rattachée à un projet parent, ici rdvsp), je privilégierais plutôt une solution de projet détaché très basique ruby qui puisse être executé en dehors du scope de RDVSP, simplement en ayant un Gemfile avec uniquement les dépendances nécessaire à ce projet et en le lancant de manière isolée genre rake anonymize xx
.
De cette façon on pourrait :
rake anonymize xxx
Ceci étant dit, je trouve ton travail très très cool et ça peut d'ailleurs être un premier pas vers ça ! 🙌
@Michaelvilleneuve Cette extraction en gem est la première étape pour une extraction plus large de l'etl qui va répondre aux problèmes que tu rappelles et qui est débutée dans cette pr : https://github.com/betagouv/rdv-service-public/pull/4357
@adipasquale On a un bug qui s'est glissé dans l'anonymizer qu'on a actuellement en prod : pour lister les tables dans la méthode .all_tables
on fait un RULES[service]::RULES.keys
à la place de ActiveRecord::Base.connection.tables
, donc si on oublie de noter les règles pour une table, elle est juste ignorée. Si on fait le fix sur la prod j'ai peur qu'il soit pas intégré ici, donc c'est sans doute plus simple qu'on l'intègre directement à cette PR
On a discuté hier avec @victormours le dernier gros obstacle que je n’avais pas prévu est que les cron job de scalingo sont limités à 15 minutes 😨
Une piste est d’optimiser pour faire rentrer dans les 15 minutes. Probablement en :
pg_dump, pg_restore, lance l’anonymisation sans attendre le résultat
puis un deuxième qui fait le pg_restore des indexes sans attendre, et éventuellement un troisième qui fait le move schema
sans attendreOn pourra aussi sinon mettre en place un processus toujours up qui s’occupe de faire le cron type GoodJob ou plus basique, mais c’est une moins bonne utilisation des ressources et ça demande aussi pas mal de code en plus.
@victormours dans une vision de petites mises en production, j’ai envie de proposer de quand même déployer en l’état l’ETL en prod en désactivant les cron jobs ! Ça permettra de :
dans une vision de petites mises en production, j’ai envie de proposer de quand même déployer en l’état l’ETL en prod en désactivant les cron jobs !
Grave ! Ça fait déjà un gros pas en avant qu'on puisse faire une synchro manuelle avec beaucoup moins d'étapes qu'avant. Je suis très chaud.
Objectif
Automatiser la mise à jour des données dans Metabase
Objectifs secondaires
Objectifs très secondaires ou découverts en chemin :
Contexte
Le code d’anonymisation est à la fois appelé dans le script d’ETL et dans l’application Rails.
Le script d’ETL actuel utilise une clé d’API Scalingo pour télécharger un backup de la db. On n’a pas envie de stocker ça dans l’environnement de l’app scalingo d’ETL pour des raisons de sécurité.
Solution proposée
Anonymizer
rdv-service-public-etl
Avertissement : GROSSE PR
En déduisant les lignes des fichiers de config, cette PR fait
+582 l, -327 l
auquel il faut rajouter les+255 l
du repo ETL.On peut envisager d’essayer de la découper mais je vais avoir besoin d’aide !
Gem
Le code de cette nouvelle gem vit dans ce repo. Il n’y a pas besoin de publier la gem. On peut utiliser la gem depuis le repo d’ETL avec :
gem "anonymizer", git: "https://github.com/betagouv/rdv-service-public.git
Règles et configuration
Actuellement, les règles d’anonymisation de RDVI et RDVSP sont committées dans ce repo. Dorénavant, on committe uniquement les règles pour RDVSP dans
config/anonymizer.yml
. Les règles de la base RDVI vivront dans le repositoryrdv-insertion
cf cette PR chez RDVI.Le format de la config passe de Ruby à YAML et change de structure :
Le code de l’Anonymizer est amélioré pour charger et injecter une config particulière. Un objet
Anonymizer::Config
est extrait à cette fin. On ne passe plus en paramètre le nom du servicerdvsp / rdvi
mais bien une instance deConfig
.Harmonisation
Aujourd’hui il y a deux méthodes d’anonymisation parallèles :
anonymize_record!
ouanonymize_records_in_scope!(scope)
anonymize_table!
La méthode 1 déclenche une requête du type
La méthode 2 déclenche plusieurs requêtes successives comme
Le résultat va donc être différent pour les valeurs blank comme "" qui dans la méthode 1 vaudront
[anonymisé]
etNULL
dans la méthode 2.Cette PR harmonise la manière de faire vers la méthode 2. Pour les appels d’anonymisation sur un seul record ou scopés, on utilise la méthode 2 avec un
WHERE
. Ça sera donc nettement moins performant pour les scopes avec peu de record (notamment pour un seul record).ActiveRecord vs AREL
Le code actuel implémente la méthode 1 avec
ActiveRecord
:La méthode 2 est elle implémentée via des requêtes SQL construites à la main.
Dans la nouvelle méthode 2 unifiée, on utilise dorénavant des requêtes
Arel
. Par exemple:J’ai fait ce choix pour plusieurs raisons :
ActiveRecord
dans l’ETL ;Les désavantages de ce choix :
Arel
est mal documentéTruncates
On interdit dorénavant d’appeler
anonymize_records!
sur une table avec un scope. Par exemple, si on essaie d’anonymiser lesversions
d’avant 2021, une exception sera levée carversions
est une table tronquée. Cela était possible jusqu’à maintenant, mais à ma connaissance jamais fait.Cela me paraît incohérent de dire qu’une table devrait être tronquée mais de permettre de ne pas la tronquer intégralement. Cela simplifie aussi le code.
Core / Table / Column / Module
La classe
Anonymizer::Core
devientAnonymizer::Table
car elle ne dépend que du nom de la table et de la config injectée.Une deuxième classe
Anonymizer::Column
est extraite deTable
.Les méthodes de classe sont extraites depuis
Core
vers le moduleAnonymizer
lui même. Ces méthodes de classe sont l’API publique de la gem.Validations d’exhaustivité
Une nouvelle méthode
Anonymizer.validate_exhaustivity!
s’assure que toutes les tables et les colonnes ont des règles dans le fichier de config. Auparavant on s’assurait uniquement de l’exhaustivité des colonnes dans les tables déclarées. C’était fait inline dans les méthodes d’anonymisation.Cela m’a permis de découvrir quelques tables qui n’étaient pas déclarées explicitement et que j’ai rajoutées en tables tronquées : https://github.com/betagouv/rdv-service-public/pull/4362/commits/eba4e26e3ce1f9e22ee95b7d8162a8ca1f69aec0.
ETL - Extraction et réécriture
Le script
scripts/etl.sh
est supprimé et complètement réécrit dans un repository à part : https://github.com/betagouv/rdv-service-public-etlLe script est réécrit de
shell
versruby
.ETL - Dumps
Les données sont récupérées via un dump Postgres fait à la volée depuis les bases distantes cibles RDVI, RDVSP, RDVS. Cela remplace le téléchargement actuel d’un backup Scalingo. Il n’y a donc plus besoin de clé d’API Scalingo. Cela permet aussi d’exclure les tables tronquées dès l’étape de la création du dump.
Il y a besoin d’un user Postgres read-only sur chaque base cible. Il faut stocker ces credentials dans les variables d’env des applications Scalingo d’ETL.
ETL - schémas
Chaque appli
rdv_solidarites
,rdv_service_public
etrdv_insertion
a maintenant un schéma éponyme.Le schéma
public
n’est plus utilisé que transitoirement :public
;public
.Ce processus a un avantage intéressant relevé par @victormours : il n’y aura jamais de données non-anonymisées dans les schémas cibles. On fait en sorte que le user Metabase n’ait pas accès à
public
mais uniquement aux schémasrdv_insertion
,rdv_solidarites
et plus tardrdv_service_public
. Cela évite d’avoir à supprimer / recréer le user Postgres Metabase à chaque processus d’import de l’ETL comme fait actuellement.Le renommage de schema n’est plus fait via un
sed s/public/rdvi/
hacky mais par un script SQL. Malheureusement ce script de clonage est lui aussi un peu hacky 🤷, je l’ai trouvé sur internet. Ce n’est pas une fonctionnalité native de Postgres.Tests effectués et déploiement envisagé
J’ai configuré une nouvelle appli Scalingo
rdv-service-public-etl-staging
avec des variables d’environnement pointant sur les applis de demo RDVSP et RDVI. J’ai configuré une nouvelle DB source sur le Metabase existant pointant vers la DB de cette app ETL Staging. Cette source liste les schémas accessibles de manière stricte.Les tables des deux schémas
rdv_insertion
etrdv_solidarites
sont maintenant accessibles sur Metabase 🎉 Je n’ai pas pu testerrdv_service_public
car on n’a pas de demo.Avant le déploiement en production, il faudra s’assurer qu’on pourra déplacer les requêtes existantes sur Metabase depuis le schéma
public
vers le schémardv_solidarites
.Après le merge de cette PR et de celle sur RDVI, on pourra modifier les URLs des fichiers de config dans les variables d’environnement.
TODO
Discussions envisagées
scripts/db_dump_load.sh
?scripts/anonymize_database.rb
?