🔝 Retour au Sommaire
La gestion de la mémoire est l'un des aspects les plus critiques des performances de PostgreSQL. Comprendre comment PostgreSQL utilise la RAM vous permettra d'optimiser vos configurations et d'éviter les pièges courants qui dégradent les performances.
PostgreSQL utilise deux types de mémoire distincts :
- Shared Buffers : Mémoire partagée accessible à tous les processus
- Local Memory : Mémoire privée propre à chaque processus backend
Dans cette section, nous allons explorer ces deux types de mémoire, leur rôle, et comment les configurer efficacement.
┌────────────────────────────────────────────────────────────┐
│ SYSTÈME D'EXPLOITATION │
│ (RAM Totale) │
├────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ MÉMOIRE PARTAGÉE (Shared Memory) │ │
│ │ ┌───────────────────────────────────────────────┐ │ │
│ │ │ SHARED BUFFERS (Cache de pages) │ │ │
│ │ │ Configuration: 4 GB │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ │ │ WAL Buffers │ Lock Table │ Autres structures │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↑ ↑ ↑ ↑ │
│ │ │ │ │ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Backend │ │ Backend │ │ Backend │ │ Backend │ │
│ │ Process │ │ Process │ │ Process │ │ Process │ │
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │
│ ├──────────┤ ├──────────┤ ├──────────┤ ├──────────┤ │
│ │ LOCAL │ │ LOCAL │ │ LOCAL │ │ LOCAL │ │
│ │ MEMORY │ │ MEMORY │ │ MEMORY │ │ MEMORY │ │
│ │ work_mem │ │ work_mem │ │ work_mem │ │ work_mem │ │
│ │temp_buffs│ │temp_buffs│ │temp_buffs│ │temp_buffs│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
└────────────────────────────────────────────────────────────┘
Pour mieux comprendre, imaginez une bibliothèque universitaire :
Shared Buffers = Salle de Lecture Commune
- Espace partagé par tous les étudiants
- Contient les livres les plus consultés (cache)
- Tout le monde peut y accéder et lire
- Limitée en taille (il faut choisir quels livres y mettre)
Local Memory = Bureaux Individuels
- Chaque étudiant a son propre bureau
- Espace de travail privé pour prendre des notes, faire des calculs
- Ne peut être utilisé que par un seul étudiant à la fois
- Taille variable selon le travail à effectuer
Les Shared Buffers constituent le cache principal de PostgreSQL. C'est une zone de mémoire RAM où PostgreSQL stocke les pages de données (8 KB chacune) pour éviter des lectures disque coûteuses.
SHARED BUFFERS (en mémoire)
┌─────────────────────────────────────────┐
│ Page 1 : users (id=1-100) │ ◄─── Backend 1 lit ici
│ Page 2 : users (id=101-200) │
│ Page 3 : orders (id=1-50) │ ◄─── Backend 2 lit ici
│ Page 4 : products (id=1-80) │
│ Page 5 : users_email_idx │ ◄─── Backend 3 lit l'index
│ ... │
│ Page N : (vide, disponible) │
└─────────────────────────────────────────┘
↕
DISQUE
/var/lib/postgresql/data/
ÉTAPE 1 : Backend demande une page
Backend → "Je veux la page 12345 de la table users"
ÉTAPE 2 : Recherche dans Shared Buffers
Shared Buffers → "Page 12345 non trouvée" ❌ (CACHE MISS)
ÉTAPE 3 : Lecture depuis le disque
Disque → Lecture de la page 12345 (LENT : ~5-10 ms)
ÉTAPE 4 : Stockage dans Shared Buffers
Page 12345 est mise en cache
ÉTAPE 5 : Retour au Backend
Backend reçoit la page
Coût : ~5-10 millisecondes (latence disque)
ÉTAPE 1 : Backend demande une page
Backend → "Je veux la page 12345 de la table users"
ÉTAPE 2 : Recherche dans Shared Buffers
Shared Buffers → "Page 12345 trouvée !" ✅ (CACHE HIT)
ÉTAPE 3 : Retour direct au Backend
Backend reçoit la page immédiatement
Coût : ~0.001-0.01 millisecondes (accès RAM)
Vitesse : 500 à 1000 fois plus rapide qu'une lecture disque !
Le Cache Hit Ratio mesure le pourcentage de lectures satisfaites depuis le cache :
Cache Hit Ratio = (Lectures en cache / Total lectures) × 100
Objectif : > 95% (idéalement > 99%)
-- Vérifier le cache hit ratio
SELECT
sum(heap_blks_read) as disk_reads,
sum(heap_blks_hit) as cache_reads,
sum(heap_blks_hit) / (sum(heap_blks_hit) + sum(heap_blks_read)) * 100 AS cache_hit_ratio
FROM pg_statio_user_tables;Résultat :
disk_reads | cache_reads | cache_hit_ratio
------------+-------------+-----------------
10000 | 990000 | 99.00% ← Excellent !
Interprétation :
- > 99% : Excellent, les shared_buffers sont bien dimensionnés
- 95-99% : Bon, peut-être augmenter légèrement
- < 95% : Problème, augmenter shared_buffers ou optimiser les requêtes
Quand les Shared Buffers sont pleins et qu'une nouvelle page doit être chargée, PostgreSQL doit évincer une page existante.
PostgreSQL utilise une variante de l'algorithme LRU (Least Recently Used) appelée Clock Sweep :
┌──────────────────────────────────────────────┐
│ Shared Buffers (circulaire) │
│ │
│ [Page A, usage=2] ◄──── Pointeur (horloge) │
│ [Page B, usage=1] │
│ [Page C, usage=0] ← Sera évincée │
│ [Page D, usage=2] │
│ [Page E, usage=1] │
│ ... │
└──────────────────────────────────────────────┘
Principe :
- Chaque page a un compteur d'usage (0 à 5)
- À chaque accès, le compteur augmente (max 5)
- Le "balayeur" (clock sweep) parcourt les pages
- Il décrémente les compteurs
- Quand compteur = 0, la page peut être évincée
Avantage : Les pages fréquemment utilisées restent en cache.
Une page est "sale" (dirty) si elle a été modifiée en mémoire mais pas encore écrite sur disque.
SHARED BUFFERS
┌───────────────────────────────────────┐
│ Page 1 : PROPRE (clean) │ │
│ Page 2 : SALE (dirty) 🔴 │ Modifiée │
│ Page 3 : SALE (dirty) 🔴 │ Modifiée │
│ Page 4 : PROPRE (clean) │ │
└───────────────────────────────────────┘
↓ (Background Writer)
DISQUE
Processus d'écriture :
- Backend modifie la page en mémoire → Page devient "sale"
- Background Writer écrit progressivement les pages sales
- Checkpointer force l'écriture de toutes les pages sales périodiquement
-- Taille des shared buffers (nécessite redémarrage)
shared_buffers = 4GBEnvironnement Serveur Dédié :
shared_buffers = 25% de la RAM totale
Exemples :
- RAM = 16 GB →
shared_buffers = 4GB - RAM = 32 GB →
shared_buffers = 8GB - RAM = 64 GB →
shared_buffers = 16GB
Pourquoi pas 100% ?
- PostgreSQL utilise aussi le cache du système d'exploitation (OS cache)
- Il faut de la RAM pour les processus locaux (work_mem)
- Il faut de la RAM pour le système d'exploitation
LECTURE D'UNE PAGE
│
├─> Recherche dans Shared Buffers
│ │
│ ├─> TROUVÉE ? → Retour rapide ✅
│ │
│ └─> NON TROUVÉE
│ │
└────────────────┴─> Recherche dans OS Cache
│
├─> TROUVÉE ? → Lecture rapide (pas d'I/O disque)
│
└─> NON TROUVÉE → Lecture DISQUE (lent)
Avantage : Deux niveaux de cache augmentent les chances d'éviter le disque.
La Local Memory est la mémoire privée allouée à chaque backend process. Contrairement aux Shared Buffers, cette mémoire n'est pas partagée et est utilisée pour les opérations temporaires propres à chaque connexion.
BACKEND PROCESS
┌──────────────────────────────────────┐
│ LOCAL MEMORY │
│ │
│ ┌────────────────────────────────┐ │
│ │ work_mem │ │ ← Tri, Hash, etc.
│ │ (Opérations temporaires) │ │
│ └────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────┐ │
│ │ maintenance_work_mem │ │ ← VACUUM, CREATE INDEX
│ │ (Opérations maintenance) │ │
│ └────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────┐ │
│ │ temp_buffers │ │ ← Tables temporaires
│ │ (Tables TEMP) │ │
│ └────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────┐ │
│ │ Overhead & Stack │ │ ← ~10 MB
│ └────────────────────────────────┘ │
└──────────────────────────────────────┘
work_mem est la quantité de mémoire qu'un backend peut utiliser pour des opérations temporaires avant de recourir au disque (fichiers temporaires).
- Tri (ORDER BY, DISTINCT)
- Hachage (Hash Joins, Hash Aggregates)
- Opérations Set (UNION, INTERSECT)
- Bitmap Index Scans
- Merge Joins
Requête :
SELECT * FROM users ORDER BY created_at;work_mem = 64 MB
Taille des données à trier = 50 MB
ÉTAPES :
1. Backend charge les données (50 MB)
2. Tri effectué EN MÉMOIRE (rapide)
3. Résultats retournés
Temps : ~100 ms
work_mem = 4 MB
Taille des données à trier = 50 MB
ÉTAPES :
1. Backend commence à charger les données
2. Mémoire saturée (4 MB < 50 MB)
3. PostgreSQL utilise des fichiers TEMPORAIRES sur disque
4. Tri externe (multi-passes) sur disque
5. Résultats retournés
Temps : ~5000 ms (50× plus lent !)
EXPLAIN indiquera :
Sort Method: external merge Disk: 50000kB ← Avertissement !
Objectif : Éviter les sorts sur disque en augmentant work_mem.
-- Valeur par défaut PostgreSQL : 4 MB (souvent insuffisant en production)
-- Valeur recommandée selon le workload :
work_mem = 64MB
-- Modifier pour la session en cours (sans redémarrage)
SET work_mem = '256MB';
-- Modifier pour une requête spécifique
SET LOCAL work_mem = '1GB';
SELECT * FROM huge_table ORDER BY column;
RESET work_mem; ATTENTION : work_mem peut être utilisé plusieurs fois dans une seule requête !
EXPLAIN ANALYZE
SELECT * FROM
(SELECT * FROM users ORDER BY created_at) u
JOIN
(SELECT * FROM orders ORDER BY order_date) o
ON u.id = o.user_id;Mémoire utilisée :
- 1er tri (users) : 1× work_mem
- 2ème tri (orders) : 1× work_mem
- Hash Join : 1× work_mem
- TOTAL : 3× work_mem
100 connexions actives
work_mem = 256 MB
Mémoire potentielle = 100 × 256 MB = 25.6 GB ! 🔥
Risque : Out of Memory (OOM) → Le système tue PostgreSQL !
Approche Conservative :
work_mem = RAM Totale / (max_connections × 2)
Exemples :
- RAM = 16 GB, max_connections = 100
- work_mem = 16 GB / (100 × 2) = 80 MB
Approche Analytique (OLAP) :
- Moins de connexions, requêtes complexes
- work_mem = 256 MB à 1 GB
Approche Transactionnelle (OLTP) :
- Beaucoup de connexions, requêtes simples
- work_mem = 16 MB à 64 MB
Best Practice :
- Démarrer avec une valeur conservatrice globalement
- Augmenter dynamiquement pour des requêtes spécifiques
maintenance_work_mem est utilisée pour les opérations de maintenance :
- VACUUM
- CREATE INDEX
- ALTER TABLE
- FOREIGN KEY checks
work_mem → Requêtes utilisateur (SELECT, JOIN, etc.)
maintenance_work_mem → Opérations admin (VACUUM, INDEX, etc.)
Avantage : On peut allouer beaucoup plus de mémoire sans risque, car ces opérations sont rares et contrôlées.
-- Valeur recommandée : 1-2 GB
maintenance_work_mem = 1GB
-- Maximum par worker autovacuum
autovacuum_work_mem = 512MB -- (défaut: -1 = utilise maintenance_work_mem)Sans maintenance_work_mem suffisant :
CREATE INDEX idx_users_email ON users(email);
Temps : 5 minutes (utilise disque)
Avec maintenance_work_mem = 2GB :
CREATE INDEX idx_users_email ON users(email);
Temps : 30 secondes (tout en mémoire) ✅
temp_buffers est la mémoire allouée pour les tables temporaires (créées avec CREATE TEMP TABLE).
-- Créer une table temporaire
CREATE TEMP TABLE temp_results AS
SELECT * FROM users WHERE active = true;
-- Utiliser la table temporaire
SELECT * FROM temp_results WHERE created_at > '2024-01-01';
-- La table disparaît à la fin de la sessionStockage :
- D'abord en RAM (temp_buffers)
- Si débordement → Fichiers temporaires sur disque
-- Valeur par défaut : 8 MB (généralement suffisant)
temp_buffers = 16MBNote : Rarement besoin d'augmenter, sauf usage intensif de tables temporaires.
effective_cache_size n'alloue AUCUNE mémoire réelle. C'est juste un indicateur pour le planificateur de requêtes sur la quantité de cache disponible (PostgreSQL + OS).
Le planificateur utilise cette valeur pour estimer si les données seront probablement en cache ou sur disque :
effective_cache_size élevé
→ Planificateur pense que les données sont en cache
→ Favorise les Index Scans (rapides si en cache)
effective_cache_size faible
→ Planificateur pense que les données sont sur disque
→ Favorise les Sequential Scans (plus efficaces si disque)
-- Recommandation : 50-75% de la RAM totale
effective_cache_size = 12GB -- Pour un serveur de 16 GB RAMRègle :
effective_cache_size = shared_buffers + (RAM libre pour OS cache)
RAM Totale PostgreSQL =
shared_buffers
+ (max_connections × (work_mem + temp_buffers + overhead))
+ maintenance_work_mem (pour les opérations en cours)
+ WAL buffers
+ 500 MB (overhead système)
Configuration :
shared_buffers = 4GB
work_mem = 64MB
maintenance_work_mem = 1GB
temp_buffers = 8MB
max_connections = 100 Calcul :
Shared Memory = 4 GB
Local Memory par connexion = 64 MB (work_mem)
+ 8 MB (temp_buffers)
+ 10 MB (overhead)
= 82 MB
Total Local Memory (100 connexions) = 100 × 82 MB = 8.2 GB
Maintenance = 1 GB (seulement si VACUUM/INDEX en cours)
Total Maximum = 4 GB + 8.2 GB + 1 GB = 13.2 GB
-- Cache Hit Ratio global
SELECT
sum(blks_hit) / (sum(blks_hit) + sum(blks_read)) * 100 AS cache_hit_ratio
FROM pg_stat_database;Objectif : > 99%
-- Activer le logging des sorts externes
SET log_temp_files = 0; -- Log tous les fichiers temporairesPuis dans les logs :
LOG: temporary file: path "base/pgsql_tmp/pgsql_tmp12345.0", size 52428800
STATEMENT: SELECT * FROM users ORDER BY created_at;
Indication : work_mem trop faible pour cette requête.
-- Backends actifs et leur activité
SELECT
pid,
usename,
application_name,
state,
query_start,
substring(query, 1, 50) as query_preview
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start; -- Activer l'extension
CREATE EXTENSION pg_stat_statements;
-- Requêtes avec le plus d'I/O disque (cache misses)
SELECT
substring(query, 1, 100) as query,
calls,
shared_blks_hit,
shared_blks_read,
shared_blks_read / (shared_blks_hit + shared_blks_read)::float * 100 as cache_miss_ratio
FROM pg_stat_statements
WHERE shared_blks_read > 0
ORDER BY shared_blks_read DESC
LIMIT 10; Caractéristiques :
- Beaucoup de petites transactions
- Nombreuses connexions (100-500)
- Requêtes simples et rapides
Configuration :
shared_buffers = 4GB # 25% RAM
work_mem = 16MB # Petite valeur (beaucoup de connexions)
maintenance_work_mem = 1GB
effective_cache_size = 12GB # 75% RAM
max_connections = 200 Caractéristiques :
- Peu de connexions (5-20)
- Requêtes complexes avec grands sorts/jointures
- Longs traitements analytiques
Configuration :
shared_buffers = 8GB # 50% RAM
work_mem = 512MB # Grande valeur (peu de connexions)
maintenance_work_mem = 4GB
effective_cache_size = 14GB
max_connections = 20 Configuration équilibrée :
shared_buffers = 6GB
work_mem = 64MB
maintenance_work_mem = 2GB
effective_cache_size = 12GB
max_connections = 100 Configuration légère :
shared_buffers = 512MB
work_mem = 4MB
maintenance_work_mem = 256MB
effective_cache_size = 2GB
max_connections = 20 Symptômes :
- PostgreSQL tué par le système (OOM Killer)
- Logs :
Out of memory - Serveur instable
Causes :
work_memtrop élevé × beaucoup de connexionsshared_bufferstrop grand- Trop de connexions simultanées
Solutions :
-- Réduire work_mem
work_mem = 16MB
-- Réduire max_connections + utiliser PgBouncer
max_connections = 50
-- Vérifier shared_buffers (max 40% RAM)
shared_buffers = 4GB -- Pour 16 GB RAMSymptômes :
- Cache hit ratio < 95%
- Requêtes lentes
- I/O disque élevé
Causes :
shared_bufferstrop petit- Base de données plus grande que la RAM
- Requêtes scannant beaucoup de données
Solutions :
-- Augmenter shared_buffers (max 25-40% RAM)
shared_buffers = 8GB
-- Ajouter des index appropriés
CREATE INDEX idx_users_email ON users(email);
-- Limiter les résultats
SELECT * FROM users LIMIT 1000;Symptômes :
- Logs remplis de
temporary file: path ... - Requêtes avec
ORDER BYtrès lentes - EXPLAIN montre
Sort Method: external merge Disk
Causes :
work_memtrop faible- Requêtes triant beaucoup de données
Solutions :
-- Augmenter work_mem pour cette session
SET work_mem = '256MB';
-- Ou globalement (avec prudence)
ALTER SYSTEM SET work_mem = '128MB';
-- Limiter les données à trier
SELECT * FROM users ORDER BY created_at LIMIT 100;Symptômes :
CREATE INDEXprend des heuresVACUUMtrès lent- Table bloat
Cause :
maintenance_work_memtrop faible
Solution :
-- Augmenter maintenance_work_mem
maintenance_work_mem = 2GB
-- Ou temporairement pour une session
SET maintenance_work_mem = '4GB';
CREATE INDEX idx_huge_table ON huge_table(column); # postgresql.conf
# === MÉMOIRE PARTAGÉE ===
shared_buffers = 4GB # 25% RAM
# === MÉMOIRE LOCALE ===
work_mem = 64MB # Ajuster selon workload
maintenance_work_mem = 1GB # Pour VACUUM/INDEX
temp_buffers = 8MB # Rarement modifié
# === PLANIFICATEUR ===
effective_cache_size = 12GB # 75% RAM
# === AUTRES ===
max_connections = 100 # Ajuster + utiliser PgBouncer-- Vérifier les paramètres actuels
SHOW shared_buffers;
SHOW work_mem;
SHOW maintenance_work_mem;
SHOW effective_cache_size;
-- Vérifier le cache hit ratio
SELECT
sum(blks_hit) / (sum(blks_hit) + sum(blks_read)) * 100 AS cache_hit_ratio
FROM pg_stat_database;- ✅ Cache principal de PostgreSQL
- ✅ Partagé entre tous les processus
- ✅ Stocke les pages de données (8 KB)
- ✅ Dimensionnement : 25% de la RAM (serveur dédié)
- ✅ Objectif : Cache Hit Ratio > 99%
- ✅ Privée à chaque backend process
- ✅
work_mem: Sorts, Hash, Joins → 16-64 MB (OLTP), 256 MB-1 GB (OLAP) - ✅
maintenance_work_mem: VACUUM, INDEX → 1-4 GB - ✅
temp_buffers: Tables temporaires → 8-16 MB - ✅
effective_cache_size: Indicateur planificateur → 50-75% RAM
RAM Totale = shared_buffers + (max_connections × work_mem) + overhead
- ❌ Ne pas sous-estimer la multiplication de
work_mem - ❌ Ne pas allouer > 40% RAM aux
shared_buffers - ❌ Ne pas ignorer les sorts sur disque (logs
temporary file) - ❌ Ne pas négliger
maintenance_work_mempour de grosses bases
-
Shared Buffers = Cache : Plus c'est grand (dans la limite), plus c'est rapide.
-
work_mem se multiplie : 100 connexions × 64 MB = 6.4 GB potentiel !
-
Le disque est 1000× plus lent que la RAM : Éviter les sorts/hash sur disque à tout prix.
-
Cache Hit Ratio > 99% : C'est l'indicateur #1 à surveiller.
-
effective_cache_size n'alloue PAS de mémoire : C'est juste un hint pour le planificateur.
-
PgBouncer est votre ami : Connection pooling pour réduire la consommation mémoire.
-
Commencer conservateur : Augmenter progressivement en observant les métriques.
-
Monitorer en continu : Logs, pg_stat_statements, cache hit ratio.
Maintenant que vous comprenez comment PostgreSQL gère sa mémoire, la section suivante explorera la structure physique des données : comment PostgreSQL stocke les données sur disque (Heap files, TOAST, WAL).