Development #60552
Remplacer la vue wcs_all_forms par une table
0%
Description
Aujourd'hui, wcs_all_forms est une vue. Cela a l'avantage de réduire le nombre d'écritures, mais l'inconvénient d'être lent à lire.
Étant donné l'utilisation de cette vue en lecture pour une page web, il serait préférable de la remplacer par une table, qu'on pourrait maintenir facilement par des triggers.
Je pense écrire un PoC sur le PG de db.test, pour avoir de quoi illustrer la facilité (relative) des triggers dans ce cas.
Gain prévisible : sur un serveur peu chargé comme lille-prod, on passerait de requêtes dans les 400 ms à des requêtes sous les 5 ms, soit largement en dessous du seuil d'appui sur F5 des utilisateurs impatients...
Fichiers
Révisions associées
sql: remove dead code (#60552)
sql: give global wcs_all_forms table creation its own function (#60552)
sql: always init global forms table in tests (#60552)
tests: only truncate if table exists (#60552)
This allows for test_select_any_formdata and others to pass without
relying on previous tests.
sql: allow init_global_table to be called multiple times (#60552)
And recreate wcs_all_forms table anew.
This will be required to keep simple migrations when additional columns
will need to be added to the table.
tests: check global table is created in convert to sql (#60552)
sql: do not include carddata in wcs_all_forms (#60552)
sql: update user_name column of wcs_all_forms on name changes (#60552)
Historique
Mis à jour par Benjamin Dauvergne il y a plus de 2 ans
Est-ce que ce ne serait pas plus simple d'utiliser l'héritage entre tables de PG ? Vu que c'est une vue sur un "UNION ALL" d'un sous-ensemble des colonnes des tables formdata_* ? J'entrevois un souci pour conserver la gestion des primary key au niveau des tables filles (peut-être possible d'avoir deux colonnes mais peut-être connais-tu une solution (peut-être que c'est aussi simple que d'avoir un global_id GENERATED AS IDENTITY au niveau table parent, et un "id GENERATED AS IDENTITY" au niveau de chaque table fille).
Mis à jour par Pierre Ducroquet il y a plus de 2 ans
L'héritage des tables aurait un résultat qui ne serait guère différent de la solution actuelle avec une vue.
On retrouve des traces de la nomenclature objet de PostgreSQL sur certaines tables système. Par exemple la liste des relations est dans la table système pg_class.
Chaque table est une classe, chaque enregistrement est une instance de la classe. L'héritage des tables n'est pas différent de l'héritage des classes en C++, Java ou Python : quand les tables formdata_42 et formdata_73 héritent de la table formdata_generic, une sélection sur formdata_generic va faire l'union entre formdata_42 et formdata_73 et les "caster" en formdata_generic. On peut aussi stocker des objets directement dans formdata_generic.
La vue actuelle est une sorte d'héritage fait main...
Mis à jour par Benjamin Dauvergne il y a plus de 2 ans
Ok, je pensais que la table parente centralisait les champs communs (comme l'héritage en Django) et qu'il y avait une sorte de FK cachée entre les deux (basé sur la PK ou l'oid ou que sais-je) mais en fait non, c'est juste du sucre syntaxique, chaque table fille contient toute les colonnes définies au niveau du parent.
Mais donc ok si on peut maintenir de façon transparente via des trigger une table commune pourquoi pas; la solution sinon ce serait de faire comme l'héritage Django mais ce serait forcément plus chiant de migrer vers ça que des triggers, on y gagnerait un dédoublonnage des données mais ce ne sont pas ces colonnes qui nous coûtent cher, ce sont les fxxx et autres workflow_data.
Mis à jour par Pierre Ducroquet il y a plus de 2 ans
- Fichier 0001-First-version-of-wcs_all_forms-as-a-table-maintained.patch 0001-First-version-of-wcs_all_forms-as-a-table-maintained.patch ajouté
- Patch proposed changé de Non à Oui
Je trouvais ça fun à faire, du coup j'ai fait un PoC. Ça passe presque les tests unitaires SQL (ça casse sur les tests des migrations, et un que je comprends pas trop, à creuser)
Mis à jour par Frédéric Péters il y a plus de 2 ans
Mille erreurs parce que CREATE OR REPLACE TRIGGER pas encore dispo, j'imagine que ça se remplace facilement par un DROP puis CREATE.
Mis à jour par Pierre Ducroquet il y a plus de 2 ans
- Fichier 0001-First-version-of-wcs_all_forms-as-a-table-maintained.patch 0001-First-version-of-wcs_all_forms-as-a-table-maintained.patch ajouté
- Statut changé de Nouveau à Solution proposée
Mea culpa, debian testing est en PG14...
Mis à jour par Benjamin Dauvergne il y a plus de 2 ans
Juste pour la discussion, plutôt que d'avoir une fonction trigger par table formdata, avoir une table formdata_all_forms alimentée depuis wcs_all_forms (qui ne bougerait pas) via un UPSERT, dans une tâche de fond toutes les 5 secondes ou déclenché par une fonction trigger unique simple, ce ne serait pas plus simple à maintenir ?
Mis à jour par Pierre Ducroquet il y a plus de 2 ans
Juste pour la discussion, plutôt que d'avoir une fonction trigger par table formdata, avoir une table formdata_all_forms alimentée depuis wcs_all_forms (qui ne bougerait pas) via un UPSERT, dans une tâche de fond toutes les 5 secondes ou déclenché par une fonction trigger unique simple, ce ne serait pas plus simple à maintenir ?
Je ne vois pas quelle serait la forme de ce trigger unique, ni la forme de cette table "formdata_all_forms". Le soucis qui nécessite une fonction trigger par table est simple : toutes les données de wcs_all_forms ne sont pas présentes dans une forme exploitable dans la base de données. Par exemple formdef.name me semble introuvable dans la DB, de même pour la liste des status qui constituent un endpoint...
Mis à jour par Pierre Ducroquet il y a plus de 2 ans
Avec la dernière version de la branche, on est sur 8 tests qui échouent. Deux que j'ai du mal à lancer en local pour le moment, et six pour lesquels je n'ai pas encore de solution à part "réécrire toute la gestion des migrations SQL" (pas sûr que ce soit une bonne idée).
Au passage, concernant les migrations SQL : sont-elles exécutées application éteinte ou à chaud ?
Mis à jour par Frédéric Péters il y a plus de 2 ans
Au passage, concernant les migrations SQL : sont-elles exécutées application éteinte ou à chaud ?
Globalement à froid (dans wcs.service, ExecStartPre=/usr/bin/wcs-manage migrate).
~~
Avec la dernière version de la branche, on est sur 8 tests qui échouent. Deux que j'ai du mal à lancer en local pour le moment, et six pour lesquels je n'ai pas encore de solution à part "réécrire toute la gestion des migrations SQL" (pas sûr que ce soit une bonne idée).
Je ne mappe pas vraiment les 8 et 6 dont tu parles.
J'en vois cinq qui ont des explicites cur.execute('DROP VIEW wcs_all_forms CASCADE')
, mais c'est du code de test, il peut être remplacé par n'importe quoi qui ferait pareil.
test_form_category_management_roles qui échoue sur
E psycopg2.errors.UniqueViolation: ERREUR: la valeur d'une clé dupliquée rompt la contrainte unique « wcs_all_forms_pkey » E DETAIL: La clé « (object_type, formdef_id, id)=(formdata, 1, 1) » existe déjà.
On pourrait avoir un explicite DELETE FROM wcs_all_forms dans Formdef.wipe().
Ensuite, celui qui
type = '<class 'psycopg2.errors.DuplicateTable'>', value = 'ERREUR: la relation « wcs_category_foo » existe déjà
je serais à dire qu'on peut dégager les vues sur les catégories.
Mis à jour par Pierre Ducroquet il y a plus de 2 ans
- Fichier 0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch 0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch ajouté
J'ai corrigé ça, on va attendre le jenkins... Nouvelle version ci-jointe.
Mis à jour par Pierre Ducroquet il y a environ 2 ans
- Fichier 0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch 0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch ajouté
Avec ce dernier patch, il reste un test qui échoue dans jenkins, sauf qu'il est complètement opaque pour moi, c'est une erreur 500 à l'appel de http://example.net/backoffice/forms/1/delete, sans log d'erreur derrière...
Mis à jour par Frédéric Péters il y a environ 2 ans
Dans les logs,
/tmp/tox-jenkins/wcs/wip/60552-wcs_all_forms-table/3/py3-django22-codestyle-pylint-coverage/lib/python3.9/site-packages/webtest/app.py:675: AppError ----------------------------- Captured stderr call ----------------------------- [2022-01-19 11:37:56] exception caught Exception: type = '<class 'psycopg2.errors.DuplicateTable'>', value = 'ERREUR: la relation « wcs_category_foo » existe déjà '
Là-dessus je commentais plus haut "je serais à dire qu'on peut dégager les vues sur les catégories.".
Mis à jour par Frédéric Péters il y a environ 2 ans
Stack trace (most recent call first): File "/var/lib/jenkins/workspace/ip_wip_60552-wcs_all_forms-table/wcs/sql.py", line 1579, in do_global_views 1577 for category in wcs.categories.Category.select(): 1578 name = get_name_as_sql_identifier(category.url_name)[:40] > 1579 cur.execute( 1580 '''CREATE VIEW wcs_category_%s AS SELECT * from wcs_all_forms 1581 WHERE category_id = %s'''
ou juste 'CREATE OR REPLACE VIEW' ici, peut-être.
Mis à jour par Pierre Ducroquet il y a environ 2 ans
- Fichier 0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch 0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch ajouté
- Assigné à changé de Pierre Ducroquet à Benjamin Dauvergne
Merci bien, du coup il n'y a plus d'erreurs avec cette dernière version, et elle passe les fourches caudines de pylint/black...
Mis à jour par Benjamin Dauvergne il y a environ 2 ans
- Assigné à changé de Benjamin Dauvergne à Pierre Ducroquet
Mis à jour par Frédéric Péters il y a environ 2 ans
- Fichier 0009-sql-update-user_name-column-of-wcs_all_forms-on-name.patch 0009-sql-update-user_name-column-of-wcs_all_forms-on-name.patch ajouté
- Fichier 0008-sql-do-not-include-carddata-in-wcs_all_forms-60552.patch 0008-sql-do-not-include-carddata-in-wcs_all_forms-60552.patch ajouté
- Fichier 0007-tests-check-global-table-is-created-in-convert-to-sq.patch 0007-tests-check-global-table-is-created-in-convert-to-sq.patch ajouté
- Fichier 0006-sql-allow-init_global_table-to-be-called-multiple-ti.patch 0006-sql-allow-init_global_table-to-be-called-multiple-ti.patch ajouté
- Fichier 0005-tests-only-truncate-if-table-exists-60552.patch 0005-tests-only-truncate-if-table-exists-60552.patch ajouté
- Fichier 0004-sql-always-init-global-forms-table-in-tests-60552.patch 0004-sql-always-init-global-forms-table-in-tests-60552.patch ajouté
- Fichier 0003-sql-give-global-wcs_all_forms-table-creation-its-own.patch 0003-sql-give-global-wcs_all_forms-table-creation-its-own.patch ajouté
- Fichier 0002-sql-remove-dead-code-60552.patch 0002-sql-remove-dead-code-60552.patch ajouté
- Fichier 0001-sql-switch-wcs_all_forms-to-a-table-maintained-by-tr.patch 0001-sql-switch-wcs_all_forms-to-a-table-maintained-by-tr.patch ajouté
Sur le travail de Pierre (0001),
- 0002 pour supprimer du code qui avait été mis en commentaire,
- 0003 pour déplacer la création de la table wcs_all_forms dans sa fonction, plutôt que l'avoir dans la fonction migrate,
- 0004 pour utiliser cette fonction de manière explicite dans les tests, pour assurer la création de la table,
- 0005 pour ne pas faire de TRUNCATE si la table n'existe pas encore (cas rencontré sur l'exécution unitaire d'un test),
- 0006 pour que la fonction init_global_table recrée la table, ça sera nécessaire quand on voudra utiliser cette fonction après l'ajout d'une nouvelle colonne,
- 0007 pour tester que la table est bien créée après convert_to_sql,
- 0008 pour ne pas inclure les données des fiches dans wcs_all_forms,
- 0009 pour mettre à jour la colonne user_name quand le nom d'un usager change.
Tout ça est assez basique, les 2 qui comptent vraiment c'est 0008 et 0009.
J'ai tourné avec tout ça en local cette semaine et ça me semble tourner ok.
Mis à jour par Lauréline Guérin il y a environ 2 ans
Il reste un commentaire # XXX TODO: make me dynamic, please ?
dans wcs/sql.py
Mis à jour par Frédéric Péters il y a environ 2 ans
Ah oui je n'étais pas trop sûr de ce que Pierre entendait là, je me suis dit que c'était peut-être centraliser la liste des colonnes, et utiliser cette liste pour écrire la requête ici mais aussi celle dans recreate_trigger(), mais dans le doute j'ai laissé.
Mis à jour par Lauréline Guérin il y a environ 2 ans
- Statut changé de Solution proposée à Solution validée
allez hop, plouf :)
Mis à jour par Frédéric Péters il y a environ 2 ans
- Statut changé de Solution validée à Résolu (à déployer)
Zou,
commit efb3789131591e0cf35a580b820fba95f38eabf6 Author: Frédéric Péters <fpeters@entrouvert.com> Date: Sat Mar 19 18:07:22 2022 +0100 sql: update user_name column of wcs_all_forms on name changes (#60552) commit 5e0819603daebdd354296e1b0960c240ed33f214 Author: Frédéric Péters <fpeters@entrouvert.com> Date: Sat Mar 19 15:27:32 2022 +0100 sql: do not include carddata in wcs_all_forms (#60552) commit 563773d9bdfcf1a58161e639e395dde48d370bcb Author: Frédéric Péters <fpeters@entrouvert.com> Date: Sat Mar 19 14:59:34 2022 +0100 tests: check global table is created in convert to sql (#60552) commit a0d0e10324cfa16b6eefcf6917e44c60b90e45df Author: Frédéric Péters <fpeters@entrouvert.com> Date: Sat Mar 19 14:54:10 2022 +0100 sql: allow init_global_table to be called multiple times (#60552) And recreate wcs_all_forms table anew. This will be required to keep simple migrations when additional columns will need to be added to the table. commit 9a4990c52de6dc071320611e099daa4abb481d1c Author: Frédéric Péters <fpeters@entrouvert.com> Date: Sat Mar 19 14:35:52 2022 +0100 tests: only truncate if table exists (#60552) This allows for test_select_any_formdata and others to pass without relying on previous tests. commit e9e4376f01fc3a886b6ef4ff67479bcf375f7844 Author: Frédéric Péters <fpeters@entrouvert.com> Date: Sat Mar 19 19:27:13 2022 +0100 sql: always init global forms table in tests (#60552) commit ea89af9a654ab4712bf77dba40a102ff25351083 Author: Frédéric Péters <fpeters@entrouvert.com> Date: Sat Mar 19 14:22:51 2022 +0100 sql: give global wcs_all_forms table creation its own function (#60552) commit f5fb836709b1c0832c40b8c8f0be1012a558ba60 Author: Frédéric Péters <fpeters@entrouvert.com> Date: Sat Mar 19 10:12:34 2022 +0100 sql: remove dead code (#60552) commit 07fc246479ed4556fa7451a8c5e055e04173186a Author: Pierre Ducroquet <pducroquet@entrouvert.com> Date: Sun Jan 16 20:50:59 2022 +0100 sql: switch wcs_all_forms to a table maintained by triggers (#60552)
Mis à jour par Frédéric Péters il y a environ 2 ans
Pour info durée exécution migrate sur saas/test, 2 minutes.
root@db1:~# grep 'CREATE TABLE IF NOT EXISTS wcs_all_forms' /var/log/postgresql/postgresql-11-main.log | wc -l 189 root@db1:~# grep 'CREATE TABLE IF NOT EXISTS wcs_all_forms' /var/log/postgresql/postgresql-11-main.log | head -1 2022-03-26 09:25:01.145 CET [9896] wcs@wcs_beaulieu_test_eservices_montpellier3m_fr LOG: statement: CREATE TABLE IF NOT EXISTS wcs_all_forms ( root@db1:~# grep 'CREATE TABLE IF NOT EXISTS wcs_all_forms' /var/log/postgresql/postgresql-11-main.log | tail -1 2022-03-26 09:26:58.103 CET [13136] wcs@wcs_formulaires_multipref_minint_test_entrouvert_org LOG: statement: CREATE TABLE IF NOT EXISTS wcs_all_forms (
Mis à jour par Transition automatique il y a environ 2 ans
- Statut changé de Résolu (à déployer) à Solution déployée
Mis à jour par Frédéric Péters il y a environ 2 ans
Pour info durée exécution migrate sur saas/test, 2 minutes.
+ la migration qui a suivi pour créer les triggers, 5 minutes.
Mis à jour par Thomas Noël il y a environ 2 ans
Frédéric Péters a écrit :
Pour info durée exécution migrate sur saas/test, 2 minutes.
+ la migration qui a suivi pour créer les triggers, 5 minutes.
J'imagine que ça dépend du volume des données (nombre de demandes) et que donc, en prod, ça risque d'être nettement supérieur.
Mis à jour par Frédéric Péters il y a environ 2 ans
A priori le premier "2 minutes" dépend du nombre de demandes; le deuxième "5 minutes" dépend du nombre de démarches.
(mais oui ça prendra du temps en prod)
sql: switch wcs_all_forms to a table maintained by triggers (#60552)
Instead of a view, wcs_all_forms is now a table.
Direct benefits:
- we will no longer explode the collapse limit of the PG optimizer
- since we won't read every table, we can be much faster
(we aren't yet, I have not done the indexes so far)
- the optimizer will have less work, thus it will be faster (I said it already)