Projet

Général

Profil

Development #60552

Remplacer la vue wcs_all_forms par une table

Ajouté par Pierre Ducroquet il y a plus de 2 ans. Mis à jour il y a environ 2 ans.

Statut:
Fermé
Priorité:
Normal
Assigné à:
Version cible:
-
Début:
12 janvier 2022
Echéance:
% réalisé:

0%

Temps estimé:
Patch proposed:
Oui
Planning:
Non

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

0001-First-version-of-wcs_all_forms-as-a-table-maintained.patch (10,6 ko) 0001-First-version-of-wcs_all_forms-as-a-table-maintained.patch Pierre Ducroquet, 16 janvier 2022 20:52
0001-First-version-of-wcs_all_forms-as-a-table-maintained.patch (10,9 ko) 0001-First-version-of-wcs_all_forms-as-a-table-maintained.patch Pierre Ducroquet, 17 janvier 2022 09:04
0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch (20 ko) 0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch Pierre Ducroquet, 18 janvier 2022 10:09
0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch (21,4 ko) 0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch Pierre Ducroquet, 19 janvier 2022 14:14
0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch (21,8 ko) 0001-Switch-wcs_all_forms-to-a-table-maintained-by-trigge.patch Pierre Ducroquet, 19 janvier 2022 23:49
0009-sql-update-user_name-column-of-wcs_all_forms-on-name.patch (4 ko) 0009-sql-update-user_name-column-of-wcs_all_forms-on-name.patch Frédéric Péters, 25 mars 2022 14:53
0008-sql-do-not-include-carddata-in-wcs_all_forms-60552.patch (9,3 ko) 0008-sql-do-not-include-carddata-in-wcs_all_forms-60552.patch Frédéric Péters, 25 mars 2022 14:53
0007-tests-check-global-table-is-created-in-convert-to-sq.patch (1,25 ko) 0007-tests-check-global-table-is-created-in-convert-to-sq.patch Frédéric Péters, 25 mars 2022 14:53
0006-sql-allow-init_global_table-to-be-called-multiple-ti.patch (1,14 ko) 0006-sql-allow-init_global_table-to-be-called-multiple-ti.patch Frédéric Péters, 25 mars 2022 14:53
0005-tests-only-truncate-if-table-exists-60552.patch (936 octets) 0005-tests-only-truncate-if-table-exists-60552.patch Frédéric Péters, 25 mars 2022 14:53
0004-sql-always-init-global-forms-table-in-tests-60552.patch (1,52 ko) 0004-sql-always-init-global-forms-table-in-tests-60552.patch Frédéric Péters, 25 mars 2022 14:53
0003-sql-give-global-wcs_all_forms-table-creation-its-own.patch (8,2 ko) 0003-sql-give-global-wcs_all_forms-table-creation-its-own.patch Frédéric Péters, 25 mars 2022 14:53
0002-sql-remove-dead-code-60552.patch (1,43 ko) 0002-sql-remove-dead-code-60552.patch Frédéric Péters, 25 mars 2022 14:53
0001-sql-switch-wcs_all_forms-to-a-table-maintained-by-tr.patch (21,9 ko) 0001-sql-switch-wcs_all_forms-to-a-table-maintained-by-tr.patch Frédéric Péters, 25 mars 2022 14:53

Révisions associées

Révision 07fc2464 (diff)
Ajouté par Pierre Ducroquet il y a environ 2 ans

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)

Révision f5fb8367 (diff)
Ajouté par Frédéric Péters il y a environ 2 ans

sql: remove dead code (#60552)

Révision ea89af9a (diff)
Ajouté par Frédéric Péters il y a environ 2 ans

sql: give global wcs_all_forms table creation its own function (#60552)

Révision e9e4376f (diff)
Ajouté par Frédéric Péters il y a environ 2 ans

sql: always init global forms table in tests (#60552)

Révision 9a4990c5 (diff)
Ajouté par Frédéric Péters il y a environ 2 ans

tests: only truncate if table exists (#60552)

This allows for test_select_any_formdata and others to pass without
relying on previous tests.

Révision a0d0e103 (diff)
Ajouté par Frédéric Péters il y a environ 2 ans

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.

Révision 563773d9 (diff)
Ajouté par Frédéric Péters il y a environ 2 ans

tests: check global table is created in convert to sql (#60552)

Révision 5e081960 (diff)
Ajouté par Frédéric Péters il y a environ 2 ans

sql: do not include carddata in wcs_all_forms (#60552)

Révision efb37891 (diff)
Ajouté par Frédéric Péters il y a environ 2 ans

sql: update user_name column of wcs_all_forms on name changes (#60552)

Historique

#1

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).

#2

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...

#3

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.

#4

Mis à jour par Pierre Ducroquet il y a plus de 2 ans

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)

#5

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.

#6

Mis à jour par Pierre Ducroquet il y a plus de 2 ans

Mea culpa, debian testing est en PG14...

#7

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 ?

#8

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...

#9

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 ?

#10

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.

#11

Mis à jour par Pierre Ducroquet il y a plus de 2 ans

J'ai corrigé ça, on va attendre le jenkins... Nouvelle version ci-jointe.

#12

Mis à jour par Pierre Ducroquet il y a environ 2 ans

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...

#13

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.".

#14

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.

#15

Mis à jour par Pierre Ducroquet il y a environ 2 ans

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...

#16

Mis à jour par Benjamin Dauvergne il y a environ 2 ans

  • Assigné à changé de Benjamin Dauvergne à Pierre Ducroquet
#17

Mis à jour par Frédéric Péters il y a environ 2 ans

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.

#18

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

#19

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é.

#20

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 :)

#21

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)
#22

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 (
#23

Mis à jour par Transition automatique il y a environ 2 ans

  • Statut changé de Résolu (à déployer) à Solution déployée
#24

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.

#25

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.

#26

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)

#27

Mis à jour par Transition automatique il y a presque 2 ans

Automatic expiration

Formats disponibles : Atom PDF