db : les sept tables de la zone or et le référentiel des sites (#105) #110

Merged
lenaic merged 8 commits from olivier/105-migrations-zone-or into develop 2026-09-03 11:06:37 +00:00
Member

Ce que ça change

Les sept tables de la zone or existent, par migration. Le lot passe au-dessus de la migration 0007 de la demande #106 : il complète site et mesure — 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 :

$ pytest tests/unit/db -q
............                                                             [100%]
12 passed

Le cas qui garde la dérive entre le glossaire et le référentiel mord bien — capacité de SITE001 passée de 200 à 210 kW dans la migration, glossaire inchangé :

E           AssertionError: SITE001 : 210.0 kW au lieu de 200.0
1 failed, 9 passed

L'application sur le serveur et la sortie de \dt / \d mesure suivent en commentaire du ticket #105.

Relecture

  • Un pair a relu et laissé un commentaire, même court
  • Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas

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 : public pour les six tables de données, ops pour la table des accès
  • docs/POSTGRESQL.md, section schéma, réécrite sur l'état posé, avec un tableau d'exposition Grafana table par table
  • db/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.

  1. acces_site est dans ops, pas dans public, à rebours du §6 du dossier d'architecture, et sa clé est ops.users (id) et non un sujet OIDC. Motif : grafana a usage sur public et 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 sur ops.acces_site est vérifiée négativement.

  2. La méthode d'imputation passe de trois régimes à quatre. La forme réduite de la 0007 fait porter à none deux sens distincts — « valeur mesurée » et « trou assumé ». Le §9 de docs/data/etl-pipeline.md et l'ADR 0006 nomment measured le 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 de qualite_jour demande. La migration 0010 élargit donc la contrainte et remet le défaut à measured. À valider par qui porte la #106.

  3. Le motif « crée ou complète » a un défaut qu'il faut connaître : un create table if not exists sur une table présente ne fait rien, et ne le dit pas. Deux cas de test le tiennent — l'un compare le create et les alter du 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éma ops et ops.schema_migrations appartenaient à postgres, pas au rôle applicatif : la 0007 a é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 au grant explicite écrit dans la 0007, et la table suivante aurait été muette. La reprise est un geste de superutilisateur, écrite dans db/migrations/README.md.
  • Sur la production, ops ne contenait que schema_migrations : les six migrations d'authentification n'y ont jamais été jouées. Elles passeront avec ce lot, puisque 0015 référence ops.users.
## Ce que ça change Les sept tables de la zone or existent, par migration. Le lot passe **au-dessus** de la migration `0007` de la demande #106 : il complète `site` et `mesure` — 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 : ``` $ pytest tests/unit/db -q ............ [100%] 12 passed ``` Le cas qui garde la dérive entre le glossaire et le référentiel mord bien — capacité de `SITE001` passée de 200 à 210 kW dans la migration, glossaire inchangé : ``` E AssertionError: SITE001 : 210.0 kW au lieu de 200.0 1 failed, 9 passed ``` L'application sur le serveur et la sortie de `\dt` / `\d mesure` suivent en commentaire du ticket #105. ## Relecture - [ ] Un pair a relu et laissé un commentaire, même court - [ ] Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas Relecteur souhaité : celui qui porte la #106, puisque les deux demandes touchent les mêmes deux tables. ## Ce qui suit le code - [x] `docs/adr/` complété — fiche **0008** : `public` pour les six tables de données, `ops` pour la table des accès - [x] `docs/POSTGRESQL.md`, section schéma, réécrite sur l'état posé, avec un tableau d'exposition Grafana table par table - [x] `db/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.** 1. **`acces_site` est dans `ops`, pas dans `public`**, à rebours du §6 du dossier d'architecture, et sa clé est `ops.users (id)` et non un sujet OIDC. Motif : `grafana` a `usage` sur `public` et 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 sur `ops.acces_site` est vérifiée négativement. 2. **La méthode d'imputation passe de trois régimes à quatre.** La forme réduite de la `0007` fait porter à `none` deux sens distincts — « valeur mesurée » et « trou assumé ». Le §9 de `docs/data/etl-pipeline.md` et l'ADR 0006 nomment `measured` le 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 de `qualite_jour` demande. La migration `0010` élargit donc la contrainte et remet le défaut à `measured`. À valider par qui porte la #106. 3. **Le motif « crée ou complète » a un défaut qu'il faut connaître** : un `create table if not exists` sur une table présente ne fait rien, et ne le dit pas. Deux cas de test le tiennent — l'un compare le `create` et les `alter` du 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éma `ops` et `ops.schema_migrations` appartenaient à **`postgres`**, pas au rôle applicatif : la `0007` a é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 au `grant` explicite écrit dans la `0007`, et la table suivante aurait été muette. La reprise est un geste de superutilisateur, écrite dans `db/migrations/README.md`. - Sur la production, `ops` ne contenait que `schema_migrations` : les six migrations d'authentification n'y ont jamais été jouées. Elles passeront avec ce lot, puisque `0015` référence `ops.users`.
Huit migrations : les six tables de données dans public, la table
d'autorisation dans ops, et le référentiel des sept sites relu du glossaire.

Refs #105
Un cas unitaire sans base, joué en chaîne : les sept tables, le sens de
retour de chaque migration, et le référentiel relu dans docs/GLOSSAIRE.md.
Un cas d'intégration joué sur le serveur : propriétaire des tables, droits
de grafana sur les six tables de données, et absence de droit sur
ops.acces_site.

Refs #105
La demande de fusion #106 pose déjà site et mesure sur les deux bases, dans
une forme réduite à ce dont l'hypertable avait besoin. Le lot passe donc en
0008-0015 : il crée les tables sur une base neuve et complète celles du
serveur (localisation, energie_kwh, qualite_source, cle_bronze, execution_id,
régime « measured »), sans toucher au partitionnement ni à la compression.

Refs #105
Fiche ADR sur le choix de public pour les six tables de données et de ops
pour la table des accès, section schéma du manuel PostgreSQL réécrite sur
l'état posé, et convention de retour arrière dans le README des migrations.

Refs #105
docs: renumérote la fiche en 0008 et raccorde le manuel aux deux tickets
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 43s
Intégration / Tests unitaires et couverture (pull_request) Successful in 1m2s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 9s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m17s
8a0965e361
L'ADR 0007 a été prise par l'état Terraform distant, fusionnée entre-temps.
La fiche de la zone or devient la 0008, et le manuel PostgreSQL dit d'où
vient chaque table : forme réduite du #29, complément du #105.

Refs #105
lenaic requested changes 2026-09-03 09:45:35 +00:00
Dismissed
lenaic left a comment

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.md nomme bien les quatre valeurs measured / interpolated / forward_fill / none dans son tableau de colonnes, et donne measured pour 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_site dans ops : bon appel, et je l'endosse. La clause de droits par défaut sur public aurait 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 mesure si elle n'existe pas, donc l'ordre relatif des deux demandes ne casse rien sur une base neuve, et le motif suit la convention de db/migrations/README.md.

Et ta 0009 règle d'elle-même la seule remarque que j'avais sur la #106 : le do nothing de 0007 laissait la base gagner sur le glossaire, ton do update remet 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.

  1. L'update de 0010, voir le commentaire de ligne. C'est le point sérieux.
  2. La #106 vient d'être fusionnée, donc la branche est à rebaser sur develop. Elle est en conflit sur db/migrations/README.md et docs/POSTGRESQL.md.
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.md` nomme bien les quatre valeurs `measured / interpolated / forward_fill / none` dans son tableau de colonnes, et donne `measured` pour 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_site` dans `ops` : bon appel, et je l'endosse.** La clause de droits par défaut sur `public` aurait 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 `mesure` si elle n'existe pas, donc l'ordre relatif des deux demandes ne casse rien sur une base neuve, et le motif suit la convention de `db/migrations/README.md`. Et ta 0009 règle d'elle-même la seule remarque que j'avais sur la #106 : le `do nothing` de 0007 laissait la base gagner sur le glossaire, ton `do update` remet 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.** 1. L'`update` de 0010, voir le commentaire de ligne. C'est le point sérieux. 2. La #106 vient d'être fusionnée, donc la branche est à rebaser sur `develop`. Elle est en conflit sur `db/migrations/README.md` et `docs/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;
Owner

Mesuré sur enervision_preprod avant d'écrire ce commentaire : cet update touche 347 574 lignes sur 352 807, et 28 des 36 fragments y sont comprimés.

Un update sur 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.

Mesuré sur `enervision_preprod` avant d'écrire ce commentaire : cet `update` touche **347 574 lignes sur 352 807**, et **28 des 36 fragments y sont comprimés**. Un `update` sur 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.
db: retire l'update de normalisation et l'index en double de la migration 0010
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 48s
Intégration / Tests unitaires et couverture (pull_request) Successful in 56s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 8s
Intégration / Images épinglées par version (pull_request) Successful in 2s
Intégration / Aucun secret commité (pull_request) Successful in 2s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m22s
d964d82533
Relevé sur enervision_preprod : l'update touchait 347 574 lignes sur 352 807,
soit 98,5 % d'une hypertable dont 28 des 36 fragments sont comprimés — et une
migration s'applique au démarrage du serveur. La zone or étant dérivée, la
normalisation des libellés vient avec le rechargement du #35.

L'index sur (horodatage desc) portait un nom différent de celui que pose
create_hypertable : « if not exists » ne protégeait de rien et un second
index identique aurait été créé sur chacun des 36 fragments.

Deux cas de test tiennent désormais les deux règles.

Refs #105
Author
Member

Corrigé en d964d82. Tes chiffres sont les bons — je les ai remesurés avant de toucher au fichier, ils tombent au même endroit :

lignes totales                     352 807
visées par l'update                347 574   (98,5 %)
fragments                          36, dont 28 comprimés
taille                             35 Mo -> 5 488 kB
fenêtre                            2026-07-30 -> 2026-09-03
politique de compression           dernier passage 08:43, prochain 20:43
répartition                        none 349 369 (99,0 %) · interpolated 3 438 (1,0 %)
dont « none » avec une valeur      347 574
dont « none » sans valeur          1 795   <- les vrais trous

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'update aurait 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 0010 est 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 :

select decompress_chunk(c, true) from show_chunks('mesure') c;
update mesure set methode_imputation = 'measured'
    where methode_imputation = 'none' and valeur_kw is not null;
-- la politique repasse d'elle-même, 12 h

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 0010 posait

create index if not exists mesure_horodatage on public.mesure (horodatage desc);

create_hypertable pose déjà son index par défaut sur la colonne de temps, nommé mesure_horodatage_idx. if not exists ne 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ù mesure est 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 un update ou un delete sur mesure et prevision. 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.

Corrigé en `d964d82`. Tes chiffres sont les bons — je les ai remesurés avant de toucher au fichier, ils tombent au même endroit : ``` lignes totales 352 807 visées par l'update 347 574 (98,5 %) fragments 36, dont 28 comprimés taille 35 Mo -> 5 488 kB fenêtre 2026-07-30 -> 2026-09-03 politique de compression dernier passage 08:43, prochain 20:43 répartition none 349 369 (99,0 %) · interpolated 3 438 (1,0 %) dont « none » avec une valeur 347 574 dont « none » sans valeur 1 795 <- les vrais trous ``` **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'`update` aurait 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 `0010` est 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 : ```sql select decompress_chunk(c, true) from show_chunks('mesure') c; update mesure set methode_imputation = 'measured' where methode_imputation = 'none' and valeur_kw is not null; -- la politique repasse d'elle-même, 12 h ``` 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 `0010` posait ```sql create index if not exists mesure_horodatage on public.mesure (horodatage desc); ``` `create_hypertable` pose déjà son index par défaut sur la colonne de temps, nommé `mesure_horodatage_idx`. **`if not exists` ne 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ù `mesure` est 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 un `update` ou un `delete` sur `mesure` et `prevision`. 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.
lenaic approved these changes 2026-09-03 10:58:02 +00:00
Dismissed
Merge develop dans olivier/105-migrations-zone-or
All checks were successful
Intégration / Qualité du code Python (pull_request) Successful in 53s
Intégration / Tests unitaires et couverture (pull_request) Successful in 58s
Intégration / Dépendances du tableau de bord (pull_request) Successful in 14s
Intégration / Images épinglées par version (pull_request) Successful in 3s
Intégration / Tableau de bord (pull_request) Successful in 20s
Intégration / Aucun secret commité (pull_request) Successful in 3s
Intégration / Dépendances Python et inventaire applicatif (pull_request) Successful in 1m25s
aa9a19058d
Trois conflits, et une collision de numéro de fiche.

La #109 a été fusionnée entre-temps et sa fiche a pris le numéro 0008, celui
que cette branche revendiquait aussi. La fiche du schéma de la zone or devient
donc 0009, avec ses références : db/migrations/README.md, docs/POSTGRESQL.md,
0008_zone_or_site.sql, 0015_ops_acces_site.sql et les deux fichiers de test.
Les numéros « 0008 » des noms de migration ne sont pas touchés, ils n'ont rien
à voir avec la série des fiches.

docs/POSTGRESQL.md et db/migrations/README.md : la version de la branche est
retenue, elle est le sur-ensemble et connaît déjà la migration 0007.

Une contradiction interne est levée au passage. Un paragraphe annonçait que la
0007 avait converti « mesure » en hypertable comprimée, un autre plus bas la
disait « encore une table ordinaire » : le second était antérieur à la #106.
Seule « prevision » reste ordinaire. Le relevé chiffré de la #106 — 7 lignes,
352 807 mesures, 36 fragments dont 28 comprimés — est conservé, il avait été
perdu par la résolution.

Vérifié : la chaîne complète 0001 à 0015 passe sans erreur sur une base neuve
et pose les dix tables attendues, six en public, acces_site plus les trois
tables d'authentification en ops.
lenaic dismissed lenaic's review 2026-09-03 11:04:07 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

lenaic approved these changes 2026-09-03 11:04:49 +00:00
lenaic left a comment

J'ai résolu les conflits et poussé la fusion de develop sur la branche, puis
levé 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.sql et les
deux fichiers de test. Les 0008 des 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çait
que la 0007 avait converti mesure en hypertable comprimée, un autre plus bas
la disait « encore une table ordinaire ». Le second était antérieur à la #106.
Seule prevision reste 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_site plus les trois d'authentification en ops. 77 tests passent sur
tests/unit/db et tests/unit/collector. Base d'essai supprimée.

Sur l'update de la 0010, la décision est prise et elle t'engage. Il touche
347 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_chunk avant
l'update, en laissant la politique recomprimer derrière, coûte une dizaine de
lignes. 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_site dans ops est le bon appel et
il est bien motivé, et le motif « crée ou complète » tient, je l'ai relu ligne
à ligne.

J'ai résolu les conflits et poussé la fusion de `develop` sur la branche, puis levé 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.sql` et les deux fichiers de test. Les `0008` des 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çait que la 0007 avait converti `mesure` en hypertable comprimée, un autre plus bas la disait « encore une table ordinaire ». Le second était antérieur à la #106. Seule `prevision` reste 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_site` plus les trois d'authentification en `ops`. 77 tests passent sur `tests/unit/db` et `tests/unit/collector`. Base d'essai supprimée. **Sur l'`update` de la 0010, la décision est prise et elle t'engage.** Il touche 347 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_chunk` avant l'`update`, en laissant la politique recomprimer derrière, coûte une dizaine de lignes. 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_site` dans `ops` est le bon appel et il est bien motivé, et le motif « crée ou complète » tient, je l'ai relu ligne à ligne.
lenaic merged commit a2f817ecc5 into develop 2026-09-03 11:06:37 +00:00
Owner

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'update de la 0010 sur 347 574 lignes et
28 fragments comprimés. Cet avertissement est caduc : tu avais retiré cet update avant
la fusion, commit d964d82. Je l'ai découvert en jouant les migrations sur le serveur, et
je 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'update seulement
long. Mesuré depuis, en le rejouant seul sur enervision_preprod :

ERREUR : tuple decompression limit exceeded by operation
DÉTAIL : current limit: 100000, tuples decompressed: 278565

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 mesure devra 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_prod enervision_preprod
Migrations appliquées 14 8
Registre 15 15
Tables 11 11
Second passage « Base à jour. » idem

La production en avait 14 en retard : son registre ne contenait que 0007, les migrations
d'authentification 0001 à 0006 n'y étaient jamais passées. C'est le point que je
signalais 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_prod les tables appartenaient encore à postgres et le rôle n'avait pas
create sur ops. Exactement le piège que tu décris dans db/migrations/README.md. Ton
geste 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_imputation reste à none pour les 347 574
lignes de test de la préproduction. Le défaut est bien measured pour 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.

### 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'`update` de la `0010` sur 347 574 lignes et 28 fragments comprimés. **Cet avertissement est caduc** : tu avais retiré cet `update` avant la fusion, commit `d964d82`. Je l'ai découvert en jouant les migrations sur le serveur, et je 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'`update` seulement long. Mesuré depuis, en le rejouant seul sur `enervision_preprod` : ``` ERREUR : tuple decompression limit exceeded by operation DÉTAIL : current limit: 100000, tuples decompressed: 278565 ``` 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 `mesure` devra 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_prod` | `enervision_preprod` | |---|---|---| | Migrations appliquées | 14 | 8 | | Registre | 15 | 15 | | Tables | 11 | 11 | | Second passage | « Base à jour. » | idem | La production en avait 14 en retard : son registre ne contenait que `0007`, les migrations d'authentification `0001` à `0006` n'y étaient jamais passées. C'est le point que je signalais 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_prod` les tables appartenaient encore à `postgres` et le rôle n'avait pas `create` sur `ops`. Exactement le piège que tu décris dans `db/migrations/README.md`. Ton geste 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_imputation` reste à `none` pour les 347 574 lignes de test de la préproduction. Le défaut est bien `measured` pour 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.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
g2/enervision!110
No description provided.