Contexte et première remontées
Le paramètre recovery_prefetch de PostgreSQL, introduit pour accélérer la lecture du WAL sur les serveurs de secours, a été signalé comme source d’incidents depuis 2021. Des courriels internes (février 2021) évoquaient déjà des pré‑fetchs inutiles, mais ce n’est qu’en mars 2025 qu’un client a constaté plusieurs heures de retard de réplication sur des serveurs bare‑metal puissants. L’analyse de top, de traces perf et strace a montré que la désactivation de recovery_prefetch éliminait le problème.
Mécanisme de recovery_prefetch
Lorsqu’un standby lit le WAL, le code de pré‑fetch (dans src/backend/access/transam/xlogrecovery.c) lance des appels fadvise pour charger les blocs de 8 kB avant qu’ils ne soient requis. Deux défauts sont identifiés :
WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
bool fetching_ckpt, XLogRecPtr tliRecPtr,
TimeLineID replayTLI, XLogRecPtr replayLSN,
bool nonblocking)
Premièrement, le code ne vérifie pas si le bloc est déjà présent dans le cache partagé, ce qui entraîne des pré‑fetchs redondants. Deuxièmement, lors d’opérations d’E/S séquentielles massives sur le primaire, le pré‑fetch transforme ces accès en lectures aléatoires de 8 kB sur le standby, multipliant le nombre d’IOPS de façon proportionnelle au facteur de fragmentation (jusqu’à 16 fois plus d’IOPS pour le même volume de données).
Impact sur les réplicas synchrones
La plupart des installations PostgreSQL modernes activent les Full Page Writes (FPW) pour protéger contre les écritures tronquées. En mode réplication synchrone, chaque transaction attend la confirmation d’au moins un standby. Si le standby subit une amplification d’IOPS qui dépasse sa capacité (exemple : disque cloud limité à 500 IOPS, 2 CPU, 8 Go RAM), il ne peut plus suivre le flux WAL. Le standby reste bloqué dans l’état « XLOG_FROM_STREAM », ne repasse à la lecture du WAL que lorsqu’il a rattrapé le retard, ce qui peut prendre des dizaines de minutes voire des heures. Le scénario décrit reproduit l’incident : après une mise à jour de 5 % d’une table pgbench (≈10 Go), la perte du processus walreceiver entraîne une chute à zéro du TPS et un blocage des écritures pendant plus de 30 minutes.
Reproduction et mesures d’atténuation
Les étapes suivantes permettent de reproduire l’incident sur un petit serveur :
# Lancer PostgreSQL avec réplication synchrone
# Utiliser un disque limité à 500 IOPS
pgbench -i -s 640 mydb # ≈10 GB de données
psql -c "ALTER TABLE pgbench_accounts SET (autovacuum_enabled = false);"
pgbench -c 1 -T 60 -j 1 -P 5 -r -S -t 5 mydb # 5 TPS
# Appliquer 5 % de mises à jour
psql -c "UPDATE pgbench_accounts SET balance = balance + 1 WHERE aid % 20 = 0;"
# Simuler une perte de connexion
kill -9 $(pidof walreceiver)
Après la perte du walreceiver, le standby ne reprend jamais le flux tant que le pic d’IOPS n’est pas résorbé. La solution immédiate consiste à désactiver recovery_prefetch (recovery_prefetch = off) tout en conservant les FPW activés. Les tests montrent que le standby reste à jour et que la pause post‑UPDATE disparaît. À plus long terme, il faut envisager des disques à IOPS plus élevées ou un réglage fin du pré‑fetch (par ex. limiter le nombre de blocs en cours).