Project

General

Profile

Development #94087

Réduire la consommation d'espace en base de données pour wcs

Added by Pierre Ducroquet about 2 months ago. Updated 23 days ago.

Status:
En cours
Priority:
Normal
Target version:
-
Start date:
14 August 2024
Due date:
% Done:

0%

Estimated time:
Patch proposed:
No
Planning:
No

Description

Aujourd'hui wcs représente plus de 650GB en bases de données, contre 162GB pour l'ensemble des autres applications.
Sur beaucoup de bases, la majorité de l'espace disque utilisé l'est par les evolutions et la colonne parts.
Cette colonne contient des objets python sérialisés en pickle. Je ne peux donc pas aisément les analyser en PostgreSQL.
Si l'ensemble des données contenues dans ces blobs est réellement utile, serait-il possible de compresser à la volée (avec zstd par exemple) ces colonnes ? PostgreSQL (14) permettrait de compresser de manière transparente en LZ4 en remplacement du "pglz" historique, mais je pense qu'on peut se permettre une compression plus forte côté python avec ZSTD (paquet python3-zstandard, disponible dans debian stable), le prix en CPU sera faible par rapport aux gains en stockage et en bande passante.

History

#2

Updated by Frédéric Péters about 2 months ago

Cette colonne contient des objets python sérialisés en pickle. Je ne peux donc pas aisément les analyser en PostgreSQL.

Si tu me pointes un tenant / une démarche particulière, je peux regarder; c'est possible qu'il y ait des appels webservice qui retournent beaucoup et que ça se trouve enregistré. (mais normalement ça ne devrait alors pas se faire si souvent, vs les webservices qui demandent un statut, qui vont stocker peu mais souvent).

#3

Updated by Frédéric Péters about 2 months ago

  • Status changed from Nouveau to Information nécessaire
  • Assignee set to Pierre Ducroquet
#6

Updated by Pierre Ducroquet about 2 months ago

  • Assignee changed from Pierre Ducroquet to Frédéric Péters
#8

Updated by Frédéric Péters about 2 months ago

  • Status changed from Information nécessaire to En cours
#11

Updated by Frédéric Péters about 2 months ago

  • Assignee changed from Frédéric Péters to Pierre Ducroquet
#13

Updated by Pierre Ducroquet 23 days ago

  • Assignee changed from Pierre Ducroquet to Frédéric Péters

J'ai modifié le script comme suit pour le rendre plus efficace et pouvoir le lancer sur toute la prod, je veux bien un avis avant de le faire tourner. Au lieu de regarder les 1000 premières demandes, je cible immédiatement les demandes avec une evolution dont le parts fait plus de 1MB.

from quixote import get_publisher

import re
import itertools
import wcs.sql
from wcs.sql_criterias import Null, NotEqual, Contains
from wcs.formdef import FormDef
from wcs.carddef import CardDef
from wcs.workflows import ContentSnapshotPart

legacy_re = re.compile(r'(bo|)(\d+)(_raw|_display|_structured|)$')
uuid_re = re.compile(r'(bo|)[0-9a-f]{8}-')

conn, cur = wcs.sql.get_connection_and_cursor()

for formdef in itertools.chain(FormDef.select(ignore_errors=True), CardDef.select(ignore_errors=True)):
    print('==', get_publisher().tenant.hostname, formdef)
    # make sure there are some 'big' evolutions available
    cur.execute("SELECT distinct formdata_id FROM %s WHERE length(parts) > 1024*1024" % (wcs.sql.get_formdef_table_name(formdef) + '_evolutions'))
    ids = [x[0] for x in cur.fetchall()]
    if len(ids) == 0:
        continue

    criterias = [NotEqual('status', 'draft'), Null('anonymised'), Contains('id', ids)]
    for i, formdata in enumerate(formdef.data_class().select_iterator(criterias, itersize=200)):
        try:
            formdata.evolution
            content_snapshot_part = formdata.evolution[0].parts[0]
            if not isinstance(content_snapshot_part, ContentSnapshotPart):
                continue
        except (TypeError, IndexError):
            continue
        new_data_field_ids = [x for x in content_snapshot_part.new_data.keys()
                              if not legacy_re.match(x) and not uuid_re.match(x)]
        if new_data_field_ids:
            print(get_publisher().tenant.hostname, formdata, new_data_field_ids)
            for field_id in new_data_field_ids:
                del content_snapshot_part.new_data[field_id]
            formdata._store_all_evolution = True
            formdata.store()

Par ailleurs, en analysant un autre cas, j'ai remarqué que des pièces jointes peuvent être stockées dans des objets wcs.wf.jump.WorkflowTriggeredEvolutionPart. C'est le cas sur l'évolution 351698, demande 1-66394 de demarches.toodego.com. L'évolution en question pèse 7MB à cause d'une photo en pièce jointe dans cet objet, est-ce normal que le fichier soit stocké en base au lieu du disque ?

#14

Updated by Frédéric Péters 23 days ago

  • Assignee changed from Frédéric Péters to Pierre Ducroquet

J'ai modifié le script comme suit pour le rendre plus efficace et pouvoir le lancer sur toute la prod, je veux bien un avis avant de le faire tourner. Au lieu de regarder les 1000 premières demandes, je cible immédiatement les demandes avec une evolution dont le parts fait plus de 1MB.

En pratique j'avais donc déjà lancé mon code et l'heuristique "si aucun cas dans les 1000 premiers" était une bonne optimisation. Ici je viens de lancer une variante du code proposé sur demarches.toodego.com et ça m'a semblé beaucoup plus long, mais ça a détecté quelques formdatas (23-5602, 85-33655 → 85-33687) qui n'avaient pas été traités (mais pour les 85 c'est plutôt parce qu'ils sont arrivés après le traitement qu'à cause de l'heuristique).

Quoiqu'il en soit, si tu veux, ça doit marcher de lancer ça :

sudo -u wcs wcs-manage runscript --all-tenants ~fred/check_too_much_initial_data_2.py

(assez proche de ta proposition, mais tu peux tout aussi bien lancer ton script).

L'évolution en question pèse 7MB à cause d'une photo en pièce jointe dans cet objet, est-ce normal que le fichier soit stocké en base au lieu du disque ?

En pratique ça n'est pas un fichier qui est stocké dans le WorkflowTriggeredEvolutionPart mais le contenu JSON de la requête appelante. Qui dans le cas que tu pointes est la sérialisation complète d'une demande, ce qui reprend donc les fichiers en base64. La demande en question date de novembre 2023, il faudrait voir au niveau du projet si le déroulé a été modifié depuis.

#15

Updated by Frédéric Péters 23 days ago

La demande en question date de novembre 2023, il faudrait voir au niveau du projet si le déroulé a été modifié depuis.

Ça me semble toujours possible, et inutile, j'ai créé #95234.

Also available in: Atom PDF