db : les sept tables de la zone or et le référentiel des sites (#105) #110
No reviewers
Labels
No labels
Compat/Breaking
EC01
EC02
EC03
EC04
EC05
EC06
Kind/BDD
Kind/Back
Kind/Bug
Kind/CICD
Kind/Cloud
Kind/Contenu
Kind/Data
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Front
Kind/IA
Kind/Infra
Kind/Monitoring
Kind/Security
Kind/Testing
Portée/Post-jury
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
ops/alerte
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
g2/enervision!110
Loading…
Reference in a new issue
No description provided.
Delete branch "olivier/105-migrations-zone-or"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Ce que ça change
Les sept tables de la zone or existent, par migration. Le lot passe au-dessus de la migration
0007de la demande #106 : il complètesiteetmesure— qui y sont posées en forme réduite — et pose les cinq autres tables, sans toucher au partitionnement ni à la compression, qui restent au #29.Closes #105
Preuve
Les cas unitaires, joués en chaîne, sans base :
Le cas qui garde la dérive entre le glossaire et le référentiel mord bien — capacité de
SITE001passée de 200 à 210 kW dans la migration, glossaire inchangé :L'application sur le serveur et la sortie de
\dt/\d mesuresuivent en commentaire du ticket #105.Relecture
Relecteur souhaité : celui qui porte la #106, puisque les deux demandes touchent les mêmes deux tables.
Ce qui suit le code
docs/adr/complété — fiche 0008 :publicpour les six tables de données,opspour la table des accèsdocs/POSTGRESQL.md, section schéma, réécrite sur l'état posé, avec un tableau d'exposition Grafana table par tabledb/migrations/README.md: la convention de retour arrière, le motif « crée ou complète », et le geste de reprise de propriétéOù regarder en priorité
Trois points valent un second avis.
acces_siteest dansops, pas danspublic, à rebours du §6 du dossier d'architecture, et sa clé estops.users (id)et non un sujet OIDC. Motif :grafanaausagesurpublicet la clause de droits par défaut couvre tout ce que le rôle applicatif y crée — la table qui dit quel exploitant voit quel site s'afficherait sur le tableau de bord sans que personne ne l'ait décidé. C'est le risque écrit au ticket, et c'est la seule des sept tables qu'il concerne. Le critère 7 se lit donc « les six tables de données », et l'absence de droit surops.acces_siteest vérifiée négativement.La méthode d'imputation passe de trois régimes à quatre. La forme réduite de la
0007fait porter ànonedeux sens distincts — « valeur mesurée » et « trou assumé ». Le §9 dedocs/data/etl-pipeline.mdet l'ADR 0006 nommentmeasuredle régime de la valeur présente et valide ; les confondre interdit de compter les valeurs réellement mesurées, ce que la répartition par méthode dequalite_jourdemande. La migration0010élargit donc la contrainte et remet le défaut àmeasured. À valider par qui porte la #106.Le motif « crée ou complète » a un défaut qu'il faut connaître : un
create table if not existssur une table présente ne fait rien, et ne le dit pas. Deux cas de test le tiennent — l'un compare lecreateet lesalterdu même fichier, l'autre va lire les colonnes sur la base réelle.Deux constats sur la production, hors périmètre du code mais à savoir.
public.site,public.mesure, le schémaopsetops.schema_migrationsappartenaient àpostgres, pas au rôle applicatif : la0007a été appliquée en superutilisateur. Conséquence bloquante — le rôle applicatif ne peut plus modifier ces tables — et conséquence silencieuse : elles ne sont lisibles par Grafana que grâce augrantexplicite écrit dans la0007, et la table suivante aurait été muette. La reprise est un geste de superutilisateur, écrite dansdb/migrations/README.md.opsne contenait queschema_migrations: les six migrations d'authentification n'y ont jamais été jouées. Elles passeront avec ce lot, puisque0015référenceops.users.Beau travail, et le lot est cohérent. Une seule chose bloque vraiment, et je l'ai mesurée sur le serveur.
Ton point 2, sur
measured: tu as raison sur le fond, la citation est à moitié fausse.docs/data/etl-pipeline.mdnomme bien les quatre valeursmeasured / interpolated / forward_fill / nonedans son tableau de colonnes, et donnemeasuredpour la valeur présente et valide. L'ADR 0006, lui, n'en nomme que trois, mais il s'intitule « imputation bornée à trois régimes » et ne traite que les trous. Il n'y a donc pas de contradiction à lever, seulement une fiche qui ne parle pas du cas non imputé. Élargir la contrainte est le bon geste, et 0007 peut rester tel quel puisqu'une base neuve joue 0007 puis 0010 et converge.Ton point 1,
acces_sitedansops: bon appel, et je l'endosse. La clause de droits par défaut surpublicaurait rendu la table qui dit quel exploitant voit quel site lisible par Grafana sans que personne ne l'ait décidé. Le motif est écrit en ADR 0008, c'est exactement ce qu'il fallait faire.Ton point 3, le motif « crée ou complète » : il tient. Je l'ai lu ligne à ligne. 0010 crée
mesuresi elle n'existe pas, donc l'ordre relatif des deux demandes ne casse rien sur une base neuve, et le motif suit la convention dedb/migrations/README.md.Et ta 0009 règle d'elle-même la seule remarque que j'avais sur la #106 : le
do nothingde 0007 laissait la base gagner sur le glossaire, tondo updateremet le dépôt en source unique, et le test qui relit les sept lignes dans le glossaire est ce qu'il fallait.Ce qui bloque.
updatede 0010, voir le commentaire de ligne. C'est le point sérieux.develop. Elle est en conflit surdb/migrations/README.mdetdocs/POSTGRESQL.md.@ -0,0 +88,4 @@-- signifiait « mesurée ». La table est vide à l'application ; la mise à jour-- est là pour une base qui aurait déjà chargé.update public.mesure set methode_imputation = 'measured'where methode_imputation = 'none' and valeur_kw is not null;Mesuré sur
enervision_preprodavant d'écrire ce commentaire : cetupdatetouche 347 574 lignes sur 352 807, et 28 des 36 fragments y sont comprimés.Un
updatesur un fragment comprimé décompresse les segments concernés. C'est le geste que ton propre manuel décrit comme celui à ne pas faire sans décomprimer d'abord.Trois sorties possibles, à choisir explicitement plutôt qu'à subir : jouer 0010 avant que la politique de compression n'ait tourné, décomprimer la fenêtre puis laisser recomprimer, ou l'accepter en connaissance de cause avec le coût écrit dans le fichier. Ce qu'il ne faut pas, c'est le découvrir pendant la démonstration.
La production est vide, elle ne coûte rien. C'est la préproduction qui prend.
Corrigé en
d964d82. Tes chiffres sont les bons — je les ai remesurés avant de toucher au fichier, ils tombent au même endroit :J'ai pris une quatrième sortie : la réécriture ne se fait pas du tout. Pas par prudence, pour une raison de fond que tes chiffres m'ont fait voir.
La zone or est dérivée. Elle se recalcule depuis la zone argent — c'est écrit au §4 de
docs/data/etl-pipeline.md, et c'est même l'argument qui justifie le médaillon. Une normalisation de libellés n'a donc pas à se payer en décompression : elle vient avec le chargement du #35, qui réécrira ces 347 574 lignes avec les quatre régimes parce que c'est son travail. Payer aujourd'hui une décompression pour un résultat que le rechargement produira gratuitement, c'est acheter deux fois la même chose.Et surtout : ces lignes sont ton jeu d'essai. 36 jours au pas de la minute, sept sites — c'est le critère 4 du #29, « un jeu de test d'au moins un mois de données permet de mesurer une requête de 24 heures ». L'
updateaurait décomprimé précisément le jeu qui sert à prouver les 400 ms, et la politique n'aurait recomprimé qu'au passage suivant. Une migration qui abîme la preuve d'un autre ticket est un mauvais échange, quel que soit son coût en mégaoctets.Ce qui reste dans la
0010est donc du catalogue seul : quatre colonnes nullables sans défaut, une contrainte de vérification remplacée, une valeur par défaut changée. Rien qui relise ni ne réécrive un fragment.Ta troisième sortie est écrite dans le fichier, avec son coût mesuré, pour qui décidera un jour de normaliser sans attendre le rechargement :
35 Mo à décomprimer, 5,4 Mo à recomprimer, 28 fragments, à faire hors démonstration. Comme tu le dis : choisi, pas subi.
Ce que ton commentaire m'a fait trouver, et que je n'avais pas vu
Un index en double, qui serait passé inaperçu. Ma
0010posaitcreate_hypertablepose déjà son index par défaut sur la colonne de temps, nommémesure_horodatage_idx.if not existsne protège que du même nom : le mien aurait été créé sous un autre nom, avec la même définition, sur chacun des 36 fragments — 1,8 Mo relevés, et une écriture de plus à chaque insertion, pour rien. Corrigé en réutilisant le nom de l'index par défaut, de sorte que l'instruction ne fait rien là où l'hypertable existe et crée l'index là oùmesureest une table ordinaire.C'est le même défaut que le tien, en plus discret : quelque chose de coûteux qui ne se voit pas en lisant le SQL.
Les deux règles sont désormais tenues par des tests
test_aucune_migration_ne_reecrit_de_ligne_dans_une_hypertable— balaie toutes les migrations, présentes et à venir, et refuse unupdateou undeletesurmesureetprevision. Il ignore les lignes de commentaire, sinon documenter la commande interdite l'aurait déclenché. Vérifié comme mordant : en remettant l'update,0010_zone_or_mesure.sql réécrit des lignes de mesure, qui est une hypertable comprimée.test_mesure_ne_porte_pas_deux_fois_le_meme_index— côté intégration, compare les définitions d'index en ignorant leur nom.La règle et les chiffres sont dans
db/migrations/README.md, section « Aucune réécriture de ligne dans une hypertable », pour que le prochain n'ait pas à la redécouvrir en préproduction.Une réserve que je laisse ouverte
Le remplacement de la contrainte de vérification et l'ajout des quatre colonnes sont des changements de catalogue, et ne devraient rien relire. Je ne l'ai pas vérifié sur la base : l'application est encore en attente d'une permission de mon côté. Si l'un d'eux butait sur les fragments comprimés, ce serait sur la préproduction et pas ailleurs — la production est vide. Je colle la sortie dès que c'est joué, et je le dis si ça résiste.
Merci — c'était le bon endroit pour regarder.
New commits pushed, approval review dismissed automatically according to repository settings
J'ai résolu les conflits et poussé la fusion de
developsur la branche, puislevé ma demande de modifications. Voici ce que j'ai touché chez toi, pour que
tu ne le découvres pas en relisant l'historique.
Une collision de numéro de fiche. La #109 a été fusionnée entre-temps et
l'ADR de Justine a pris le 0008, celui que cette branche revendiquait aussi. Ta
fiche du schéma devient 0009, avec ses six références :
db/migrations/README.md,docs/POSTGRESQL.md,0008_zone_or_site.sql,0015_ops_acces_site.sqlet lesdeux fichiers de test. Les
0008des noms de migration ne sont pas touchés,ils n'ont rien à voir avec la série des fiches.
Une contradiction levée dans
docs/POSTGRESQL.md. Un paragraphe annonçaitque la 0007 avait converti
mesureen hypertable comprimée, un autre plus basla disait « encore une table ordinaire ». Le second était antérieur à la #106.
Seule
previsionreste ordinaire, c'est corrigé.Un relevé réintégré. La résolution naïve perdait le tableau chiffré de la
#106 — 7 lignes, 352 807 mesures, 36 fragments dont 28 comprimés. Je l'ai remis
dans ton texte, il a de la valeur.
Vérifié avant de pousser : la chaîne complète 0001 à 0015 passe sans une
erreur sur une base neuve et pose les dix tables attendues, six en
public,acces_siteplus les trois d'authentification enops. 77 tests passent surtests/unit/dbettests/unit/collector. Base d'essai supprimée.Sur l'
updatede la 0010, la décision est prise et elle t'engage. Il touche347 574 lignes sur 352 807 en préproduction, dont 28 fragments comprimés sur 36 :
la base va dépaqueter et repaqueter presque toute la table. Ce n'est pas cassé,
c'est lent, et la place occupée gonfle pendant l'opération.
On fusionne en l'acceptant, à une condition : cette migration se joue à un
moment tranquille, jamais juste avant une démonstration. Si la commande ne
rend pas la main pendant dix minutes le jour J, ce sera ça, et personne ne
doit avoir à le redécouvrir.
Si tu veux t'en débarrasser proprement plus tard, un
decompress_chunkavantl'
update, en laissant la politique recomprimer derrière, coûte une dizaine delignes. Ou bien vider le jeu de test de préproduction, qui sera de toute façon
remplacé par les vraies données du collecteur.
Le reste du lot est du bon travail.
acces_sitedansopsest le bon appel etil est bien motivé, et le motif « crée ou complète » tient, je l'ai relu ligne
à ligne.
Correction : l'avertissement de mon approbation ne s'applique plus, et il était pire que je ne le pensais
Mon message d'approbation demandait de jouer cette migration « à un moment tranquille, jamais
juste avant une démonstration », à cause de l'
updatede la0010sur 347 574 lignes et28 fragments comprimés. Cet avertissement est caduc : tu avais retiré cet
updateavantla fusion, commit
d964d82. Je l'ai découvert en jouant les migrations sur le serveur, etje le corrige ici pour que personne ne suive une consigne devenue fausse.
Et tu as mieux fait que ce qu'on croyait tous les deux. Je pensais l'
updateseulementlong. Mesuré depuis, en le rejouant seul sur
enervision_preprod:Il n'aurait pas été lent, il aurait échoué. TimescaleDB plafonne la décompression à
100 000 tuples par transaction DML et il en fallait 278 565. La migration se serait arrêtée
là, sur les deux bases si la préproduction était passée en premier.
Ce n'est donc plus un arbitrage d'exploitation, c'est une contrainte dure : toute
normalisation de masse sur
mesuredevra soit décomprimer d'abord, soit passer par lots,soit lever le plafond pour la transaction. Ça vaut pour la zone argent quand elle arrivera.
Ce que les migrations ont donné sur le serveur
Jouées sous le rôle applicatif, pas sous
postgres:enervision_prodenervision_preprodLa production en avait 14 en retard : son registre ne contenait que
0007, les migrationsd'authentification
0001à0006n'y étaient jamais passées. C'est le point que jesignalais en marge de ma relecture de la #106, il est réglé.
Deux choses bloquaient, et elles n'étaient pas dans ton code. Les mots de passe du coffre
étaient refusés par les deux rôles, et la propriété des objets n'était pas au rôle applicatif
— sur
enervision_prodles tables appartenaient encore àpostgreset le rôle n'avait pascreatesurops. Exactement le piège que tu décris dansdb/migrations/README.md. Tongeste de reprise est repris tel quel dans le rôle Ansible de la #114, en superutilisateur
comme tu le préconises, avec le réalignement des mots de passe sur le coffre.
Un dernier point qui te concerne :
methode_imputationreste ànonepour les 347 574lignes de test de la préproduction. Le défaut est bien
measuredpour les nouvelles lignes,mais l'existant garde l'ambiguïté que ta fiche 0009 décrit. Vu que ce jeu sera remplacé par
les vraies données du collecteur, ça ne me semble pas valoir une migration. Dis-moi si tu
vois les choses autrement.