🔝 Retour au Sommaire
pg_repack est une extension PostgreSQL qui permet de réorganiser et compacter des tables et index en ligne, c'est-à-dire sans bloquer les opérations de lecture et d'écriture pendant le processus. C'est une alternative moderne et sûre à VACUUM FULL et CLUSTER.
Au fil du temps, les tables PostgreSQL peuvent devenir gonflées (bloated) à cause du fonctionnement de MVCC (Multiversion Concurrency Control).
Le bloat est l'accumulation d'espace mort (dead space) dans une table ou un index :
Table au départ (10 000 lignes, 1 Mo)
├─ Données utiles : 100%
└─ Espace mort : 0%
Après 1 an d'UPDATE/DELETE (toujours 10 000 lignes visibles)
├─ Données utiles : 40% ← Seulement 400 Ko de données utiles
└─ Espace mort : 60% ← 600 Ko d'anciennes versions et trous
Taille totale : 2.5 Mo au lieu de 1 Mo
Pourquoi le bloat se produit-il ?
PostgreSQL utilise MVCC (voir tutoriel sur les transactions). Quand vous faites un UPDATE ou DELETE :
- UPDATE : L'ancienne ligne n'est pas supprimée immédiatement, une nouvelle version est créée
- DELETE : La ligne est marquée comme supprimée mais reste physiquement présente
- VACUUM : Nettoie l'espace mort, mais ne le rend pas au système d'exploitation
Conséquence : La table occupe plus d'espace que nécessaire, ce qui ralentit :
- Les scans séquentiels (plus de pages à lire)
- Les sauvegardes (plus de données à copier)
- La réplication (plus de WAL à transférer)
Analogie : Imaginez une bibliothèque où vous ne jetez jamais les vieux livres, vous les marquez juste comme "obsolète" et ajoutez les nouvelles versions sur de nouvelles étagères. Avec le temps, la bibliothèque devient énorme et il faut marcher longtemps pour trouver les livres actuels.
VACUUM customers;Ce que ça fait :
- Nettoie l'espace mort
- Marque l'espace comme réutilisable
- Ne rend PAS l'espace au système d'exploitation
- N'optimise pas l'ordre des données
Avantage : Pas de verrous exclusifs, fonctionne en ligne
Inconvénient : Ne réduit pas la taille du fichier de la table
VACUUM FULL customers;Ce que ça fait :
- Reconstruit complètement la table
- Élimine tout le bloat
- Rend l'espace au système d'exploitation
Avantage : Compactage maximal
Inconvénients :
- ❌ Verrou exclusif (ACCESS EXCLUSIVE) : Bloque TOUTES les opérations (SELECT, INSERT, UPDATE, DELETE)
- ❌ Très lent sur grandes tables (peut prendre des heures)
- ❌ Nécessite 2× l'espace disque temporairement
- ❌ Inutilisable en production pour des tables actives
CLUSTER customers USING idx_customers_pk;Ce que ça fait :
- Réorganise physiquement la table selon un index
- Élimine le bloat
- Optimise l'ordre des données (améliore les scans séquentiels)
Avantages : Compactage + optimisation de l'ordre
Inconvénients :
- ❌ Verrou exclusif : Même problème que VACUUM FULL
- ❌ Très lent
- ❌ Inutilisable en production
pg_repack fait la même chose que VACUUM FULL ou CLUSTER, mais sans bloquer l'application :
- ✅ Pas de verrou exclusif : Les SELECT, INSERT, UPDATE, DELETE continuent pendant le repack
- ✅ En ligne : Transparence totale pour l'application
- ✅ Sûr : Mécanisme transactionnel, rollback en cas d'erreur
- ✅ Efficace : Compactage complet et réorganisation
- ✅ Flexible : Peut réorganiser tables, index, ou les deux
Limitation : Nécessite un verrou exclusif très bref (quelques millisecondes) au début et à la fin.
pg_repack utilise une approche en plusieurs phases pour réorganiser une table tout en maintenant la disponibilité.
┌─────────────────────────────────┐
│ Table Originale: customers │
│ (10 000 lignes, 2.5 Mo bloat) │
└─────────────────────────────────┘
↓
[Verrou bref ~1-2 ms]
↓
┌─────────────────────────────────┐
│ Création de triggers │
│ - INSERT trigger │
│ - UPDATE trigger │
│ - DELETE trigger │
└─────────────────────────────────┘
Objectif : Installer des triggers pour capturer toutes les modifications pendant la copie.
┌─────────────────────────────────┐
│ Table Originale: customers │
│ (L'application continue à │
│ lire/écrire normalement) │
└─────────────────────────────────┘
↓ [Copie progressive]
┌─────────────────────────────────┐
│ Table Temporaire: repack_xxx │
│ (Données compactées) │
└─────────────────────────────────┘
+
┌─────────────────────────────────┐
│ Table de Log: repack_log_xxx │
│ (Capture toutes les modifs │
│ pendant la copie via triggers)│
└─────────────────────────────────┘
Pendant cette phase :
- pg_repack copie toutes les données de la table originale vers une table temporaire
- Les triggers capturent toutes les modifications (INSERT/UPDATE/DELETE) dans une table de log
- Aucun verrou exclusif : L'application fonctionne normalement
┌──────────────────────────────────┐
│ Table Temporaire: repack_xxx │
│ (Données compactées + initiales)│
└──────────────────────────────────┘
↓ [Application du log]
┌─────────────────────────────────┐
│ Table Temporaire: repack_xxx │
│ (Données à jour et compactées) │
└─────────────────────────────────┘
Objectif : Rejouer toutes les modifications capturées dans le log pour que la table temporaire soit à jour.
[Verrou exclusif bref ~10-100 ms]
↓
┌─────────────────────────────────┐
│ Swap des tables │
│ - Table temporaire → customers │
│ - customers → old_customers │
└─────────────────────────────────┘
↓
┌──────────────────────────────────┐
│ Nouvelle Table: customers │
│ (10 000 lignes, 1 Mo, compactée)│
└──────────────────────────────────┘
↓
[Suppression de l'ancienne table]
Objectif : Échanger atomiquement les tables. L'ancienne table devient la nouvelle.
Verrou exclusif : Nécessaire pour garantir la cohérence, mais très bref (quelques dizaines de ms).
Timeline:
│
├─ T0: Début pg_repack
│ └─ Verrou exclusif: 1-2 ms (création triggers)
│
├─ T0+1s à T0+1h: Copie des données
│ └─ Aucun verrou, application fonctionne normalement
│ └─ Les modifications sont loguées via triggers
│
├─ T0+1h: Application des logs
│ └─ Aucun verrou, rattrapage des modifications
│
├─ T0+1h+5min: Swap final
│ └─ Verrou exclusif: 10-100 ms (échange des tables)
│
└─ T0+1h+6min: Fin, nettoyage
Triggers créés par pg_repack :
-- Trigger pour capturer les INSERT
CREATE TRIGGER repack_trigger_insert
AFTER INSERT ON customers
FOR EACH ROW EXECUTE FUNCTION repack_log_insert();
-- Trigger pour capturer les UPDATE
CREATE TRIGGER repack_trigger_update
AFTER UPDATE ON customers
FOR EACH ROW EXECUTE FUNCTION repack_log_update();
-- Trigger pour capturer les DELETE
CREATE TRIGGER repack_trigger_delete
AFTER DELETE ON customers
FOR EACH ROW EXECUTE FUNCTION repack_log_delete(); Ces triggers capturent toutes les modifications dans une table de log qui sera rejouée sur la table temporaire.
- PostgreSQL 9.4+ (idéalement PostgreSQL 18)
- Droits superutilisateur : Pour l'installation de l'extension
- Espace disque : Environ 1.5× la taille de la table à réorganiser
- Extension disponible : Selon votre distribution PostgreSQL
pg_repack est un outil en ligne de commande, pas juste une extension SQL.
Sur Linux (Debian/Ubuntu) :
sudo apt-get install postgresql-18-repackSur Linux (RHEL/CentOS) :
sudo yum install pg_repack18Compilation depuis les sources :
git clone https://github.com/reorg/pg_repack.git
cd pg_repack
make
sudo make install -- Se connecter à la base de données cible
CREATE EXTENSION pg_repack;Vérification :
-- Vérifier que l'extension est active
SELECT * FROM pg_extension WHERE extname = 'pg_repack';
-- Vérifier que le binaire est accessible
-- (depuis le shell)
pg_repack --versionL'utilisateur qui exécute pg_repack doit avoir :
-- Option 1 : Superutilisateur (recommandé pour simplicité)
ALTER USER repack_user WITH SUPERUSER;
-- Option 2 : Permissions minimales (plus sécurisé)
GRANT USAGE ON SCHEMA public TO repack_user;
GRANT ALL ON ALL TABLES IN SCHEMA public TO repack_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO repack_user;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO repack_user; Note : pg_repack nécessite des permissions élevées car il crée des objets temporaires et modifie des métadonnées.
pg_repack est un outil en ligne de commande, pas une commande SQL.
Format :
pg_repack [OPTIONS] [dbname]# Syntaxe de base
pg_repack -d mydatabase -t customers
# Avec utilisateur et hôte
pg_repack -h localhost -p 5432 -U postgres -d mydatabase -t customers
# Avec schéma explicite
pg_repack -d mydatabase -t public.customersOptions principales :
| Option | Description |
|---|---|
-d, --dbname |
Nom de la base de données |
-t, --table |
Nom de la table à réorganiser |
-h, --host |
Hôte PostgreSQL (défaut: localhost) |
-p, --port |
Port PostgreSQL (défaut: 5432) |
-U, --username |
Utilisateur PostgreSQL |
-n, --schema |
Schéma spécifique |
-k, --no-superuser-check |
Ignorer la vérification superutilisateur |
# Tables spécifiques (séparées par des virgules)
pg_repack -d mydatabase -t customers,orders,products
# Toutes les tables d'un schéma
pg_repack -d mydatabase -n public
# Toutes les tables de la base (attention : long !)
pg_repack -d mydatabase --allPar défaut, pg_repack réorganise la table ET ses index. Pour réorganiser uniquement les index :
# Réorganiser uniquement les index d'une table
pg_repack -d mydatabase -t customers --only-indexes
# Réorganiser un index spécifique
pg_repack -d mydatabase -i idx_customers_emailPour réorganiser une table selon l'ordre d'un index (équivalent de CLUSTER) :
# Réorganiser selon un index
pg_repack -d mydatabase -t orders -o order_date
# Équivalent de : CLUSTER orders USING idx_orders_date;
# Mais sans verrou exclusif !Avantage : Améliore les performances des scans séquentiels et des requêtes range-based.
# Mode verbeux (affiche les détails)
pg_repack -d mydatabase -t customers --verbose
# Exécution à vide (dry-run, pour tester)
pg_repack -d mydatabase -t customers --dry-run
# Spécifier un tablespace pour les tables temporaires
pg_repack -d mydatabase -t customers --tablespace fast_ssd
# Limiter le nombre de connexions
pg_repack -d mydatabase -t customers --jobs 2
# Attendre la fin des transactions en cours (au lieu d'échouer)
pg_repack -d mydatabase -t customers --wait-timeout 600 # 10 minutesScénario : Table orders avec 50% de bloat, utilisée 24/7.
Diagnostic :
-- Vérifier le bloat (requête simplifiée)
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as total_size,
pg_size_pretty(pg_relation_size(schemaname||'.'||tablename)) as table_size,
ROUND(100 * (pg_relation_size(schemaname||'.'||tablename)::numeric /
NULLIF(pg_total_relation_size(schemaname||'.'||tablename), 0)), 2) as table_percent
FROM pg_tables
WHERE tablename = 'orders';
-- Estimer le bloat (extension pgstattuple recommandée)
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT
schemaname,
tablename,
pg_size_pretty(table_len) as table_size,
ROUND(dead_tuple_percent, 2) as dead_tuple_percent,
pg_size_pretty(dead_tuple_len) as wasted_space
FROM pgstattuple('orders')
JOIN pg_tables ON pg_tables.schemaname = 'public' AND pg_tables.tablename = 'orders'; Résultat hypothétique :
table_size: 5 GB
dead_tuple_percent: 48%
wasted_space: 2.4 GB
Action :
# Réorganiser la table avec pg_repack
pg_repack -h localhost -U postgres -d production -t orders --verbose
# Résultat attendu après repack :
# table_size: 2.6 GB (réduction de ~50%)
# dead_tuple_percent: 0%Vérification post-repack :
-- Vérifier la taille après
SELECT pg_size_pretty(pg_total_relation_size('orders'));
-- Vérifier qu'il n'y a plus de bloat
SELECT * FROM pgstattuple('orders');Scénario : Table events avec des requêtes fréquentes par plage de dates.
-- Requête typique
SELECT * FROM events
WHERE event_date >= '2025-01-01'
AND event_date < '2025-02-01';Problème : Les données ne sont pas ordonnées physiquement par event_date, causant des scans inefficaces.
Solution : Réorganiser la table selon l'ordre de event_date.
# Créer d'abord un index si nécessaire
psql -d production -c "CREATE INDEX IF NOT EXISTS idx_events_date ON events(event_date);"
# Réorganiser selon cet index
pg_repack -d production -t events -o event_date --verboseImpact :
- Avant : Scan séquentiel avec beaucoup d'I/O random
- Après : Scan séquentiel optimisé, I/O séquentiel (plus rapide)
Vérification :
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM events
WHERE event_date >= '2025-01-01'
AND event_date < '2025-02-01';
-- Vérifier le nombre de buffers lus
-- Avant: shared buffers read: 50000
-- Après: shared buffers read: 15000 (amélioration ~70%)Scénario : Maintenance hebdomadaire de tables critiques.
#!/bin/bash
# Script: weekly_repack.sh
DBNAME="production"
TABLES=("orders" "customers" "products")
for table in "${TABLES[@]}"; do
echo "Repacking $table..."
pg_repack -h localhost -U postgres -d $DBNAME -t $table --verbose
if [ $? -eq 0 ]; then
echo "$table: SUCCESS"
else
echo "$table: FAILED"
# Envoyer une alerte
send_alert "pg_repack failed for $table"
fi
donePlanification avec cron :
# Exécuter tous les dimanches à 2h du matin
0 2 * * 0 /scripts/weekly_repack.sh >> /var/log/pg_repack.log 2>&1Ou avec pg_cron (depuis PostgreSQL) :
-- Job pg_cron pour exécuter pg_repack
SELECT cron.schedule(
'weekly_repack_orders',
'0 2 * * 0', -- Dimanches à 2h
$$
-- Nécessite une fonction wrapper pour appeler pg_repack depuis SQL
SELECT run_pg_repack_for_table('orders');
$$
);Scénario : Après un import massif ou une migration, les tables ont un mauvais facteur de remplissage.
# Après un gros import
psql -d newdb -c "COPY orders FROM '/data/orders.csv' WITH CSV HEADER;"
# Statistiques obsolètes et bloat potentiel
psql -d newdb -c "ANALYZE orders;"
# Réorganiser pour optimiser
pg_repack -d newdb -t orders --verboseImportant : pg_repack peut réorganiser chaque partition individuellement.
-- Lister les partitions
SELECT
schemaname,
tablename
FROM pg_tables
WHERE tablename LIKE 'orders_p%'
ORDER BY tablename; # Réorganiser chaque partition
pg_repack -d production -t orders_p2024_01 --verbose
pg_repack -d production -t orders_p2024_02 --verbose
pg_repack -d production -t orders_p2024_03 --verbose
# Ou via un script
for partition in $(psql -d production -t -c "SELECT tablename FROM pg_tables WHERE tablename LIKE 'orders_p%'"); do
pg_repack -d production -t $partition --verbose
doneNote : Ne pas réorganiser la table parent, seulement les partitions.
Pendant l'exécution, pg_repack affiche sa progression (si --verbose) :
INFO: repacking table "public.customers"
INFO: Setting up workers...
INFO: [1/6] creating table to copy data
INFO: [2/6] copying rows
INFO: 500000 rows copied (5% complete)
INFO: 1000000 rows copied (10% complete)
...
INFO: [3/6] creating indexes
INFO: [4/6] applying log
INFO: [5/6] swapping relations
INFO: [6/6] dropping old tables
INFO: repack complete
Pendant le repack, observer l'activité :
-- Voir les processus pg_repack actifs
SELECT
pid,
usename,
application_name,
state,
query,
query_start,
NOW() - query_start as duration
FROM pg_stat_activity
WHERE application_name LIKE '%repack%'
OR query LIKE '%repack%';
-- Voir les verrous en attente
SELECT
locktype,
mode,
granted,
pid
FROM pg_locks
WHERE NOT granted;
-- Voir la taille des tables temporaires créées par pg_repack
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename))
FROM pg_tables
WHERE tablename LIKE 'repack%'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC; | Métrique | Commande | Alerte si |
|---|---|---|
| Durée d'exécution | Via --verbose | > 4h (selon table) |
| Espace disque | df -h |
< 20% libre |
| Connexions actives | SELECT COUNT(*) FROM pg_stat_activity |
Proche de max_connections |
| Verrous bloquants | SELECT * FROM pg_locks WHERE NOT granted |
> 0 pendant longtemps |
| I/O | iostat -x 1 |
Saturation disque |
✅ Pourquoi : Bien que pg_repack soit "online", il consomme des ressources (CPU, I/O, mémoire).
✅ Quand : Pendant les heures de faible activité (nuit, week-end).
Exception : Si le bloat est critique et affecte les performances, réorganiser immédiatement.
✅ Règle : Avoir au moins 1.5× la taille de la table en espace libre.
# Vérifier l'espace avant
df -h /var/lib/postgresql
# Vérifier la taille de la table
psql -d production -c "SELECT pg_size_pretty(pg_total_relation_size('orders'));"Exemple :
- Table
orders: 100 GB - Espace libre requis : ≥ 150 GB
Si insuffisant : Réorganiser les tables les plus petites d'abord, ou libérer de l'espace.
✅ Workflow recommandé :
1. Cloner la base de production (pg_dump/pg_restore)
2. Tester pg_repack sur le clone
3. Mesurer la durée et l'impact
4. Valider que tout fonctionne après
5. Planifier l'exécution en production
# Test à blanc (ne fait rien, juste vérifie la faisabilité)
pg_repack -d production -t orders --dry-run
# Si succès : Exécuter réellement
pg_repack -d production -t orders --verbosepg_repack crée plusieurs connexions. Vérifier max_connections :
SHOW max_connections;
-- Si proche de la limite, augmenter ou limiter --jobs
-- Voir les connexions utilisées
SELECT COUNT(*) FROM pg_stat_activity;Ajuster --jobs :
# Par défaut : 1 job (1 connexion principale + quelques auxiliaires)
pg_repack -d production -t orders --jobs 1
# Pour aller plus vite (si ressources disponibles)
pg_repack -d production -t orders --jobs 4✅ Stratégie préventive : Réorganiser avant que le bloat ne devienne critique.
Exemple de calendrier :
| Type de Table | Fréquence | Méthode |
|---|---|---|
| Très actives (OLTP) | Mensuelle | pg_repack |
| Modérément actives | Trimestrielle | pg_repack |
| Peu actives | Annuelle | VACUUM FULL acceptable |
Après un repack, mettre à jour les statistiques :
# Repack puis ANALYZE
pg_repack -d production -t orders --verbose
psql -d production -c "ANALYZE orders;"Ou :
# pg_repack analyse automatiquement à la fin (depuis v1.4.6)
# Pas besoin d'ANALYZE manuel supplémentaire-- Créer une table de logs
CREATE TABLE monitoring.repack_log (
log_id SERIAL PRIMARY KEY,
table_name TEXT,
start_time TIMESTAMPTZ,
end_time TIMESTAMPTZ,
size_before BIGINT,
size_after BIGINT,
duration INTERVAL,
success BOOLEAN,
error_message TEXT
);
-- Logger manuellement ou via script wrapper
INSERT INTO monitoring.repack_log
(table_name, start_time, end_time, size_before, size_after, duration, success)
VALUES
('orders',
'2025-11-23 02:00:00',
'2025-11-23 03:45:00',
107374182400, -- 100 GB
53687091200, -- 50 GB
'1 hour 45 minutes',
TRUE);- Verrous exclusifs brefs
pg_repack nécessite deux verrous exclusifs très brefs :
- Au début : ~1-10 ms (création des triggers)
- À la fin : ~10-100 ms (swap des tables)
Impact : Pendant ces quelques ms, toutes les requêtes sont bloquées.
Mitigation : Utiliser --wait-timeout pour attendre la fin des transactions longues.
- Nécessite de l'espace disque
pg_repack crée une copie complète de la table (1.5× la taille).
Solution : Vérifier l'espace avant, ou utiliser un tablespace alternatif (--tablespace).
- Ne fonctionne pas avec toutes les tables
pg_repack ne peut pas réorganiser :
- Tables sans clé primaire ou UNIQUE
- Tables temporaires (TEMP)
- Tables non-loggées (UNLOGGED) avant PostgreSQL 9.5
- Tables système (pg_catalog)
Vérification :
-- Identifier les tables sans clé primaire
SELECT
schemaname,
tablename
FROM pg_tables t
WHERE NOT EXISTS (
SELECT 1
FROM pg_constraint c
WHERE c.conrelid = (t.schemaname||'.'||t.tablename)::regclass
AND c.contype IN ('p', 'u') -- PRIMARY KEY ou UNIQUE
)
AND schemaname NOT IN ('pg_catalog', 'information_schema');Solution : Ajouter une clé primaire avant de repack.
-- Ajouter une clé primaire
ALTER TABLE my_table ADD PRIMARY KEY (id);
-- Puis réorganiser
pg_repack -d production -t my_table- Impact sur les Replicas
Le repack génère beaucoup de WAL (Write-Ahead Log), ce qui peut :
- Saturer la bande passante réseau (réplication streaming)
- Créer un retard (lag) sur les replicas
Mitigation :
- Exécuter pendant les heures creuses
- Augmenter
max_wal_sizetemporairement - Surveiller le lag de réplication :
SELECT * FROM pg_stat_replication;
- Tables avec beaucoup d'écritures
Si la table est très active en écriture pendant le repack, la table de log peut devenir énorme.
Symptôme : Le repack ne finit jamais (il n'arrive pas à rattraper les modifications).
Solution : Réduire le trafic en écriture temporairement, ou planifier pendant une fenêtre plus calme.
- Pas de rollback automatique
Si pg_repack échoue, il ne rollback pas automatiquement. Les objets temporaires peuvent rester.
Nettoyage manuel :
-- Identifier les objets résiduels
SELECT
schemaname,
tablename
FROM pg_tables
WHERE tablename LIKE 'repack%';
-- Supprimer manuellement
DROP TABLE IF EXISTS repack_123456;Cause : La table n'a pas de clé primaire ou d'index UNIQUE.
Solution :
-- Option 1 : Ajouter une clé primaire
ALTER TABLE my_table ADD PRIMARY KEY (id);
-- Option 2 : Ajouter un index UNIQUE
CREATE UNIQUE INDEX idx_my_table_unique ON my_table(id);
-- Puis réessayer pg_repackCause : La colonne choisie pour clé primaire contient des doublons ou des NULL.
Diagnostic :
-- Vérifier les doublons
SELECT id, COUNT(*)
FROM my_table
GROUP BY id
HAVING COUNT(*) > 1;
-- Vérifier les NULL
SELECT COUNT(*)
FROM my_table
WHERE id IS NULL; Solution :
-- Nettoyer les doublons et NULL
-- Puis ajouter la clé primaireCause : Pas assez de mémoire partagée (shared_buffers ou max_locks_per_transaction).
Solution :
-- Augmenter temporairement
ALTER SYSTEM SET max_locks_per_transaction = 256; -- Défaut: 64
SELECT pg_reload_conf();
-- Ou dans postgresql.conf
max_locks_per_transaction = 256
# Puis redémarrer PostgreSQLCauses possibles :
- Table très volumineuse
- Beaucoup d'écritures concurrentes
- I/O lent
Diagnostics :
-- Vérifier l'activité I/O
SELECT * FROM pg_stat_database WHERE datname = 'production';
-- Vérifier les écritures sur la table
SELECT
schemaname,
tablename,
n_tup_ins + n_tup_upd + n_tup_del as write_activity
FROM pg_stat_user_tables
WHERE tablename = 'orders'; Solutions :
- Planifier pendant une période plus calme
- Augmenter
--jobspour paralléliser (si ressources disponibles) - Utiliser un tablespace sur SSD rapide
Symptôme : pg_repack bloqué en attente d'un verrou.
WARNING: Waiting for a transaction to complete...
Cause : Une transaction longue empêche pg_repack d'acquérir le verrou exclusif final.
Diagnostic :
-- Identifier la transaction bloquante
SELECT
pid,
usename,
state,
NOW() - xact_start as xact_duration,
query
FROM pg_stat_activity
WHERE state != 'idle'
AND xact_start IS NOT NULL
ORDER BY xact_start;Solutions :
# Option 1 : Attendre avec un timeout
pg_repack -d production -t orders --wait-timeout 600 # 10 minutes
# Option 2 : Terminer la transaction bloquante (attention !)
psql -d production -c "SELECT pg_terminate_backend(12345);" -- PID de la transactionSymptôme : ERROR: could not extend file
Cause : Plus d'espace disque pendant la copie.
Solution :
# Libérer de l'espace
# Ou utiliser un tablespace alternatif
pg_repack -d production -t orders --tablespace fast_storage| Critère | pg_repack | VACUUM FULL |
|---|---|---|
| Verrou exclusif | Très bref (~100 ms) | Complet (heures) |
| Disponibilité | Application continue | Hors ligne |
| Vitesse | Rapide | Lent |
| Espace disque | 1.5× | 2× (temporairement) |
| Sécurité | Haute (rollback possible) | Moyenne |
| Complexité | Extension externe | Commande native |
Verdict : pg_repack pour production, VACUUM FULL uniquement si repack impossible.
| Critère | pg_repack | CLUSTER |
|---|---|---|
| Verrou exclusif | Très bref | Complet |
| Disponibilité | Online | Offline |
| Ordre physique | Oui (avec -o) | Oui |
| Optimisation | Complète | Complète |
Verdict : pg_repack avec -o remplace CLUSTER pour la production.
pg_squeeze : Alternative plus récente, similaire à pg_repack.
| Critère | pg_repack | pg_squeeze |
|---|---|---|
| Maturité | Très mature | Plus récent |
| Adoption | Large | Émergente |
| Fonctionnalités | Complètes | Similaires |
| Maintenance | Active | Active |
Verdict : pg_repack est plus éprouvé et recommandé pour la production critique.
-- Créer une fonction wrapper pour pg_repack
CREATE OR REPLACE FUNCTION run_pg_repack(table_name TEXT)
RETURNS BOOLEAN AS $$
DECLARE
cmd TEXT;
BEGIN
-- Construire la commande shell
cmd := format('pg_repack -h localhost -U postgres -d production -t %s --verbose', table_name);
-- Exécuter via COPY PROGRAM (nécessite superutilisateur)
EXECUTE format('COPY (SELECT 1) TO PROGRAM %L', cmd);
RETURN TRUE;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'pg_repack failed for %: %', table_name, SQLERRM;
RETURN FALSE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Planifier avec pg_cron
SELECT cron.schedule(
'monthly_repack_orders',
'0 2 1 * *', -- Le 1er de chaque mois à 2h
'SELECT run_pg_repack(''orders'');'
);Exporter des métriques de bloat et déclencher des alertes :
-- Vue pour métriques de bloat
CREATE OR REPLACE VIEW monitoring.table_bloat AS
SELECT
schemaname,
tablename,
pg_relation_size(schemaname||'.'||tablename) as table_bytes,
(SELECT dead_tuple_percent FROM pgstattuple(schemaname||'.'||tablename)) as bloat_percent
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema');
-- Alerte si bloat > 30%Dans une configuration Patroni, exécuter pg_repack uniquement sur le primaire :
#!/bin/bash
# Script: repack_if_primary.sh
# Vérifier si ce nœud est le primaire
ROLE=$(patronictl list -f json | jq -r '.[] | select(.Member == "'$HOSTNAME'") | .Role')
if [ "$ROLE" == "Leader" ]; then
echo "This node is primary, running pg_repack..."
pg_repack -d production -t orders --verbose
else
echo "This node is standby, skipping pg_repack"
fiPour éviter de saturer le disque principal :
-- Créer un tablespace sur un disque rapide
CREATE TABLESPACE fast_ssd LOCATION '/mnt/ssd/pg_tablespace';# Utiliser ce tablespace pour les tables temporaires
pg_repack -d production -t orders --tablespace fast_ssdPour des tables très volumineuses (>1 TB) :
# Utiliser plusieurs jobs (si assez de CPU/I/O)
pg_repack -d production -t huge_table --jobs 8 --verboseAttention : Augmente la consommation de ressources (CPU, mémoire, connexions).
#!/bin/bash
# Script: repack_bloated_tables.sh
DBNAME="production"
BLOAT_THRESHOLD=30
# Requête pour identifier les tables avec bloat > 30%
TABLES=$(psql -d $DBNAME -t -c "
SELECT tablename
FROM pgstattuple_approx('public.' || tablename)
JOIN pg_tables ON pg_tables.tablename = pgstattuple_approx.tablename
WHERE approx_dead_tuple_percent > $BLOAT_THRESHOLD
AND schemaname = 'public';
")
for table in $TABLES; do
echo "Repacking $table (bloat > ${BLOAT_THRESHOLD}%)..."
pg_repack -d $DBNAME -t $table --verbose
done- GitHub pg_repack : https://github.com/reorg/pg_repack
- Documentation PostgreSQL - Routine Maintenance : https://www.postgresql.org/docs/current/maintenance.html
- pgstattuple : Extension pour mesurer le bloat
- check_postgres : Script Nagios pour surveiller le bloat
- pg_squeeze : Alternative à pg_repack
- PostgreSQL: Up and Running (Regina Obe, Leo Hsu) - Chapitre sur la maintenance
- PostgreSQL Administration Cookbook - Recettes de maintenance
- GitHub Issues : Signaler des bugs ou demander des fonctionnalités
- PostgreSQL Mailing Lists : pgsql-admin@postgresql.org
- Reddit : r/PostgreSQL
pg_repack est un outil essentiel pour maintenir les performances PostgreSQL en production en permettant de réorganiser les tables sans interruption de service.
- ✅ Réorganisation online : Pas de downtime, application continue
- ✅ Réduction du bloat : Récupère l'espace perdu par MVCC
- ✅ Optimisation physique : Réorganise selon un ordre optimal (CLUSTER-like)
- ✅ Sûr et transactionnel : Rollback possible en cas d'erreur
- ✅ Production-ready : Utilisé par des milliers d'entreprises
- Bloat élevé : Table avec >30% de dead tuples
- Performances dégradées : Scans séquentiels lents
- Après migrations : Optimiser après imports massifs
- Maintenance régulière : Prévenir l'accumulation de bloat
- Alternative à VACUUM FULL : Réorganiser sans verrou exclusif
1. Détecter le bloat (pgstattuple, pg_stat_user_tables)
2. Évaluer l'impact (taille, activité, disponibilité)
3. Tester sur un clone (dry-run, mesurer durée)
4. Vérifier l'espace disque (1.5× taille table)
5. Planifier pendant heures creuses
6. Exécuter pg_repack avec monitoring
7. Valider post-repack (taille, performances)
8. Documenter l'opération
| Situation | Outil Recommandé |
|---|---|
| Production 24/7 | pg_repack |
| Table < 1 GB, downtime acceptable | VACUUM FULL |
| Besoin d'ordre physique | pg_repack -o |
| Table sans PK | Ajouter PK, puis pg_repack |
| Bloat critique (>80%) | pg_repack immédiatement |
Une opération pg_repack réussie devrait :
- ✅ Réduire la taille : Table compactée (50-80% de réduction typique)
- ✅ Améliorer les performances : Scans plus rapides
- ✅ Pas de downtime : Application ininterrompue
- ✅ Pas d'erreurs : Aucune transaction perdue
- Installer pg_repack sur votre environnement de test
- Mesurer le bloat actuel de vos tables (pgstattuple)
- Tester pg_repack sur une petite table
- Observer le processus et les verrous
- Planifier la maintenance régulière
- Automatiser avec des scripts
En Conclusion : pg_repack est un outil indispensable pour maintenir des bases de données PostgreSQL performantes et saines en production. Il résout le problème du bloat sans les contraintes des outils natifs (VACUUM FULL, CLUSTER), permettant une maintenance transparente et sûre. Pour tout DBA, DevOps ou développeur gérant PostgreSQL en production, maîtriser pg_repack est essentiel pour garantir des performances optimales dans le temps.
Principe d'or : "Maintenez régulièrement, prévenez les problèmes" - pg_repack transforme la maintenance d'une opération critique et risquée en un processus routinier et sûr.