OS : Debian12
Objectif : Maintenir sa base de données Postgresql SYNAPSE
J'administre un serveur Matrix-Synapse depuis maintenant 4 ans environs. Sa base de données grossie gentiment pour atteindre 300GB aujourd'hui, et ce malgré quelques gestes d’hygiènes basiques (Rétention à 180 jours, Purge des caches médias locaux, distants, Rooms vides, etc...). Il faut donc passer au niveau supérieur.
Si vous êtes pressé, mon script de maintenance complet est en bas de page.
Relevons le poids des différentes table de la base de données:
psql -t -A -h localhost --dbname=$DBNAME --username=$DBUSER --command="SELECT nspname || '.' || relname AS "relation",
pg_size_pretty(pg_total_relation_size(C.oid)) AS "total_size"
FROM pg_class C
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind <> 'i'
AND nspname !~ '^pg_toast'
ORDER BY pg_total_relation_size(C.oid) DESC
LIMIT 20;"
La raison de ce poids important semble provenir d'une accumulation "naturelle" (car progressive dans le temps suivant mon monitoring disque #zabbix) d’événements d’état (state_groups_state) depuis la naissance de l'instance:
public.state_groups_state|297 GB
public.device_lists_changes_in_room|4229 MB
public.received_transactions|1960 MB
public.event_json|1521 MB
public.current_state_delta_stream|1165 MB
public.events|684 MB
public.e2e_cross_signing_keys|646 MB
public.event_auth|475 MB
public.event_edges|423 MB
public.device_lists_remote_cache|371 MB
public.device_lists_stream|262 MB
public.room_memberships|246 MB
public.event_search|183 MB
public.state_events|162 MB
public.event_auth_chains|144 MB
public.device_inbox|138 MB
public.event_auth_chain_links|133 MB
public.event_to_state_groups|100 MB
public.state_group_edges|96 MB
public.cache_invalidation_stream_by_instance|80 MB
On creuse un peu plus pour connaitre la répartition de ces "state_groups_state" entre toutes les rooms:
psql -t -A -h localhost --dbname=$DBNAME --username=$DBUSER --command="SELECT room_id, count(*) AS count FROM state_groups_state GROUP BY room_id HAVING count(*) > 99999 ORDER BY count DESC;"
Avec comme résultat la liste des rooms ayant plus de 99999 de "state_groups_state", par ordre décroissant:
!OGEhHVWSdvArJzumhm:matrix.org|175103593
!zAeWUNBoXONgkVtgqn:mozilla.org|96728651
!yUHxXrvmurdnsPyhiH:sakura.ci|87619662
!wOlkWNmgkAZFxbTaqj:matrix.org|28272082
!PpSxdCZfUIFXMZXivf:waifuhunter.club|9488267
!mKkDdfCLCtkTyqcxEc:nerdsin.space|5182764
!fzfHhoTplYBEXfWOaI:matrix.org|4386744
!UxQFGskJBlUowxdIxQ:tapenet.org|2827290
!GtIfdsfQtQIgbQSxwJ:archlinux.org|2072471
Notons ici que "!OGEhHVWSdvArJzumhm:matrix.org" est la room la plus riches en events "state_groups_state". Retenons ce RoomID.
A savoir que ce type d’événement n'est jamais purgé par les mécanisme de rétention (https://github.com/matrix-org/synapse/blob/develop/docs/message_retention_policies.md) ou de purge d'historique (https://matrix-org.github.io/synapse/latest/admin_api/purge_history_api.html).
Du coup, que fait-on si on ne peut pas les purger? On va les compresser!
C'est un outil disponible ici (https://github.com/matrix-org/rust-synapse-compress-state#compress-synapse-state-tables).
Après avoir installé une version récente de RUST/CARGO (https://www.rust-lang.org/tools/install) comme ceci:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Et après avoir installé quelques dépendances comme celles-ci:
apt install git cmake pkg-config libssl-dev
La compilation du binaire s’effectue sans heurt (https://github.com/matrix-org/rust-synapse-compress-state#building):
su monuser
cd monsuer
git clone https://github.com/matrix-org/rust-synapse-compress-state.git
cd rust-synapse-compress-state
/monuser/.cargo/bin/cargo build
cd rust-synapse-compress-state/synapse_auto_compressor
/monuser/.cargo/bin/cargo build
cp /monuser/rust-synapse-compress-state/target/debug/synapse_auto_compressor /monuser/
cp /monuser/rust-synapse-compress-state/target/debug/synapse_compress_state /monuser/
Au final, on obtient deux binaires => synapse_auto_compressor et synapse_compress_state
synapse_auto_compressor: effectue une compression sur l'ensemble des state_groups_state suivant 2 paramètres synapse_compress_state: effectue une compression sur une room spécifique
Mon approche a été de d'abord cibler manuellement le TOP 10 des rooms riches en state_groups_state avec synapse_compress_state, puis de mettre en place une tache régulière avec synapse_auto_compressor une fois le plus gros déjà compressé.
La compression (manuelle et/ou auto) est une tache qui a nécessité de disposer de:
L’exécution, qui peut s'effectuer pendant que synapse est démarré, peu durée longtemps, plusieurs heures. S'agissant de traitement sur la base de données, il serait dommageable de se faire déconnecter durant l'opération.
On va donc utiliser Screen comme filet de sécurité:
apt install screen
On ouvre une session Screen:
screen -S compressor
L'action se fait en 2 étapes. La première consiste en la génération d'un fichier .sql comportant les modifications à effectuer dans le but de compresser. La seconde étape consiste en l'application de ce fichier dans Postgresql.
C'est assez simple (mais probablement très long) car on a déjà fait toute le travail préparatoire. Il suffit d'indiquer l'ID de la room à compresser:
/monsuser/synapse_compress_state -t -o /monuser/synapse_compress_state_OGEhHVWSdvArJzumhm.sql -p "host=localhost user=${DBUSER} password=${PGPASSWORD} dbname=${DBNAME}" -r "!OGEhHVWSdvArJzumhm:matrix.org"
Une fois le fichier .sql généré, il faut l'appliquer à la base de données:
psql -t -A -h localhost --dbname=$DBNAME --username=$DBUSER < /monuser/synapse_compress_state_OGEhHVWSdvArJzumhm.sql
Effectuez cela sur le TOP5/TOP10 selon votre analyse de la base de données de votre Synapse.
Contrôlez via les commandes du chapitre 1 l'évolution de la taille de votre base de données.
NOTA: Postgresql ne libèrera l'espace disque qu'il utilise que s'il est effectué un FULL VACUUM. Cette action nécessite un arrêt de la base de données et un espace disque libre d'au moins l'espace occupé. On aborde ce sujet plus bas.
L’exécution, qui peut s'effectuer pendant que synapse est démarré, peu durée longtemps, plusieurs heures, jours. S'agissant de traitement sur la base de données, il serait dommageable de se faire déconnecter durant l'opération.
On va donc utiliser Screen comme filet de sécurité:
apt install screen
On ouvre une session Screen:
screen -S compressor
Et dans ce "Screen" on exécute:
synapse_auto_compressor -p postgresql://$DBUSER:$PGPASSWORD@localhost/$DBNAME -c 100000 -n 10000000
NOTA2: -c 100000 peut être réduit jusqu’à 500 si vous n'avez pas assez de RAM. Plus -c est élevé, meilleur est la compression. A vous de trouver/tester la valeur qui vous convient
Relevons le poids des différentes table de la base de données:
psql -t -A -h localhost --dbname=$DBNAME --username=$DBUSER --command="SELECT nspname || '.' || relname AS "relation",
pg_size_pretty(pg_total_relation_size(C.oid)) AS "total_size"
FROM pg_class C
LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind <> 'i'
AND nspname !~ '^pg_toast'
ORDER BY pg_total_relation_size(C.oid) DESC
LIMIT 20;"
Résultat:
public.state_groups_state|91 GB
public.device_lists_changes_in_room|4229 MB
public.received_transactions|1965 MB
public.event_json|1522 MB
public.current_state_delta_stream|1165 MB
public.events|684 MB
public.e2e_cross_signing_keys|646 MB
public.event_auth|475 MB
public.event_edges|423 MB
public.device_lists_remote_cache|372 MB
public.device_lists_stream|262 MB
public.room_memberships|246 MB
public.event_search|183 MB
public.state_events|162 MB
public.event_auth_chains|144 MB
public.device_inbox|138 MB
public.event_auth_chain_links|133 MB
public.event_to_state_groups|100 MB
public.state_group_edges|96 MB
public.cache_invalidation_stream_by_instance|81 MB
On observe dans mon cas une baisse significative: public.state_groups_state|91 GB
Cette action nécessite un arrêt de la base de données et un espace disque libre d'au moins l'espace occupé.
systemctl stop matrix-synapse
psql -t -A -h localhost --dbname=$DBNAME --username=$DBUSER --command="VACUUM FULL VERBOSE;"
systemctl start matrix-synapse
Un grand MERCI à -> @thomas:lgy.fr [Matrix]
https://quentin.dufour.io/blog/2021-07-12/chroniques-administration-synapse/
https://levans.fr/shrink-synapse-database.html
https://www.tedomum.net/dev/service/matrix/administration/#nettoyage-du-serveur
https://gitea.prk.st/romain/synapse-tools/src/branch/master/synapse-purge.sh
https://github.com/matrix-org/synapse/issues/3364
https://github.com/matrix-org/rust-synapse-compress-state
https://wiki.chatons.org/doku.php/services/messagerie_instantanee/matrix#nettoyage_d_un_serveur_synapse
https://foss-notes.blog.nomagic.uk/2021/03/matrix-database-house-cleaning/
https://www.underworld.fr/blog/matrix-synapse-script-maintenance-de-la-base-de-donnee-postgresql
Avec le temps, ma base a continué de grossir et ce malgré l'application régulière des préceptes/règles d’hygiènes ci-dessus, qui permettaient quand même de limiter les dégâts.
En cherchant... encore... et encore, je suis tombé sur ce blog : https://blog.gelesneeuw.nl/technology/2024/04/20/matrix-maintenance/
Il est fait référence à un "bug" de jeunesse de synapse, qui perdure, dont voici l'issue : https://github.com/matrix-org/synapse/issues/3364
Et dont voici le remède : https://github.com/erikjohnston/synapse-find-unreferenced-state-groups
C'est EXACTEMENT ce qui causait l’embonpoint de ma base de donnée. Après application du traitement, celle-ci est passée de 250Go à 18Go... oui, DIX-HUIT Giga.
Pour faire simple, ARRÊTEZ SYNAPSE, ARRÊTEZ SYNAPSE et ARRÊTEZ SYNAPSE, puis générez le fichier csv qui va contenir la liste des "événements orphelins":
rust-synapse-find-unreferenced-state-groups -p "postgresql://$DBUSER:$PGPASSWORD@localhost/$DBNAME" -o "/home/user/unreferenced.csv"
Enfin, dans le cli psql, une fois connecté à votre base de donnée:
CREATE TEMPORARY TABLE unreffed(id BIGINT PRIMARY KEY);
COPY unreffed FROM '/home/user/unreferenced.csv' WITH (FORMAT 'csv');
DELETE FROM state_groups_state WHERE state_group IN (SELECT id FROM unreffed);
DELETE FROM state_group_edges WHERE state_group IN (SELECT id FROM unreffed);
DELETE FROM state_groups WHERE id IN (SELECT id FROM unreffed);
Ce bug m'aura pourri la vie durant 3 ans, m'imposant des plans de remédiations lourds, des arbitrages de ressources, avec indispo. bref, je suis content :)