PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 14.15 » Administration du serveur » Fiabilité et journaux de transaction » Configuration des journaux de transaction

30.5. Configuration des journaux de transaction

Il y a plusieurs paramètres de configuration associés aux journaux de transaction qui affectent les performances de la base. Cette section explique leur utilisation. Consultez Chapitre 20 pour des détails sur la mise en place de ces paramètres.

Dans la séquence des transactions, les checkpoints (ou points de contrôle) sont des points qui garantissent que les fichiers de données des table et des index ont été mis à jour avec toutes les informations enregistrées dans le journal avant le checkpoint. Au moment du checkpoint, toutes les pages de données modifiées (dirty) sont écrites sur le disque et une entrée spéciale, pour le checkpoint, est écrite dans le journal. (Les modifications étaient déjà envoyées dans les journaux de transactions.) En cas de défaillance, la procédure de récupération recherche le dernier enregistrement d'un checkpoint (enregistrement connus sous le nom de « redo log ») pour déterminer le point des journaux à partir duquel il devra lancer l'opération de REDO. Toute modification effectuée sur les fichiers de données avant ce point a la garantie d'avoir été enregistrée sur disque. Du coup, après un checkpoint, tous les segments représentant des journaux de transaction précédant celui contenant le « redo record » ne sont plus nécessaires et peuvent être soit recyclés, soit supprimés (quand l'archivage des journaux de transaction est activé, ces derniers doivent être archivés avant d'être recyclés ou supprimés).

Un checkpoint doit écrire toutes les pages de données modifiées sur disque, ce qui peut causer une charge disque importante. Pour cette raison, l'activité d'un checkpoint est diluée de façon à ce que les entrées/sorties disque commencent au début du checkpoint et se terminent avant le démarrage du checkpoint suivant ; ceci minimise la dégradation des performances lors des checkpoints.

Le processus checkpointer lance automatiquement un checkpoint de temps en temps. Il démarre toutes les checkpoint_timeout secondes ou si max_wal_size risque d'être dépassé, suivant ce qui arrive en premier. La configuration par défaut de ces deux paramètres est, respectivement, 5 minutes et 1 Go. Si aucun enregistrement WAL n'a été écrit depuis le dernier checkpoint, il n'y en aura pas de nouveaux, même si la durée checkpoint_timeout est dépassée. (Si l'archivage des WAL est en place et que vous voulez définir une limite basse correspondant à la fréquence d'archivage des fichiers de manière à limiter la perte potentielle de données, vous devez ajuster le paramètre archive_timeout plutôt que les paramètres affectant les checkpoints.) Il est aussi possible de forcer un checkpoint en utilisant la commande SQL CHECKPOINT.

La réduction de checkpoint_timeout et/ou max_wal_size provoque des checkpoints plus fréquents. Cela permet une récupération plus rapide après arrêt brutal puisqu'il y aura moins d'écritures à refaire. Cependant, il faut équilibrer cela avec le coût d'écritures plus fréquentes des pages modifiées. Si full_page_writes est configuré (ce qui est le défaut), il reste un autre facteur à considérer. Pour s'assurer de la cohérence des pages de données, la première modification d'une page de données après chaque checkpoint résulte dans la journalisation du contenu de la page en entier. Dans ce cas, un intervalle de checkpoints plus petit augmentera le volume d'écriture des journaux de transaction, annulant en partie l'intérêt d'utiliser cet intervalle plus petit et générant de toute façon plus d'entrées/sorties au niveau disque.

Les checkpoints sont assez coûteux, tout d'abord parce qu'ils écrivent tous les tampons modifiés jusqu'à ce moment, et ensuite parce qu'ils génèrent un trafic supplémentaire dans les journaux de transaction, comme indiqué ci-dessus. Du coup, il est conseillé de configurer les paramètres des checkpoints assez haut pour qu'ils ne surviennent pas trop fréquemment. Pour une vérification rapide de l'adéquation de vos paramètres, vous pouvez configurer le paramètre checkpoint_warning. Si les checkpoints se déclenchent à moins de checkpoint_warning secondes d'intervalle, un message est affiché dans les journaux applicatifs du serveur recommandant d'accroître max_wal_size. L'apparition occasionnelle d'un tel message ne doit pas vous alarmer mais, s'il apparaît souvent, alors les paramètres de contrôle devraient être augmentés. Les opérations en masse, comme les transferts importants de données via COPY, peuvent faire apparaître nombre de ces messages d'avertissement si vous n'avez pas configuré max_wal_size avec une valeur suffisamment haute.

Pour éviter de saturer les entrées/sorties avec de très nombreuses écritures de pages modifiées, leur écriture pendant le checkpoint est étalée sur une période de temps. Elle est contrôlée par checkpoint_completion_target, donné comme une fraction de l'intervalle des checkpoints(configuré en utilisant checkpoint_timeout). Le taux d'entrées/sorties est ajusté pour que le checkpoint se termine quand la fraction indiquée de checkpoint_timeout secondes s'est écoulée, ou avant que max_wal_size soit dépassé, selon ce qui arrivera en premier. Avec la valeur par défaut de 0,9, on peut s'attendre à ce que PostgreSQL termine chaque checkpoint un peu avant le prochain checkpoint planifié (à environ 90% de la durée du dernier checkpoint). Ceci étant les I/O autant que possible pour que la charge en I/O des checkpoints soit lissée pendant tout l'intervalle des checkpoints. L'inconvénient est que prolonger les checkpoints affecte le temps de redémarrage après crash parce qu'un plus grand nombre de segments WAL devront être conservés pour une possible utilisation après crash. Un utilisateur inquiet par la durée requise pour la restauration après crash pourrait vouloir réduire checkpoint_timeout pour que les checkpoints surviennent plus fréquemment, tout en diluant les I/O sur l'intervalle des checkpoints. Alternativement, checkpoint_completion_target pourrait être réduit mais cela résulterait en des moments avec des I/O plus intenses (lors du checkpoint) et des momentsavec des I/O moins intenses (après la fin du checkpoint mais avant le début du suivant). De ce fait, cela n'est pas recommandé. Bien que checkpoint_completion_target puisse être configuré aussi haut que 1,0, il est typiquement recommandé de le configurer pas plus haut que 0,9 (la valeur par défaut) car les checkpoints ont quelques autres activités en dehors de l'écriture du cache modifié. Une valeur de 1,0 peut résulter en checkpoints qui ne se terminent pas à temps, ce qui entraînerait des baisses de performance à cause de variations inattendues dans le nombre de journaux nécessaires.

Sur les plateformes Linux et POSIX, checkpoint_flush_after permet de forcer le système d'exploitation à vider sur disque les pages écrites par un checkpoint après qu'un nombre configurable d'octets soit écrit. Sinon ces pages pourraient rester dans le cache disque du système d'exploitation, provoquant un blocage quand fsync est exécuté à la fin d'un checkpoint. Cette configuration aide souvent à réduire la latence des transactions mais il peut aussi avoir un effet inverse sur les performances, particulièrement pour des charges supérieures à shared_buffers mais plus petites que le cache disque du système d'exploitation.

Le nombre de fichiers de segments WAL dans le répertoire pg_wal dépend des paramètres min_wal_size, max_wal_size et de la quantité de WAL générée lors des cycles de checkpoints précédents. Quand les anciens fichiers de segments ne sont plus nécessaires, ils sont supprimés ou recyclés (c'est-à-dire renommés pour devenir les segments suivants selon les numéros de la séquence). Si, à cause d'un bref pic du débit des WAL, max_wal_size est dépassé, les fichiers inutiles seront supprimés jusqu'à ce que le système revienne sous cette limite. En-dessous de cette limite, le système recycle suffisamment de fichiers WAL pour couvrir le besoin estimé jusqu'au checkpoint suivant, et supprime le reste. L'estimation est basée sur une moyenne glissante du nombre de fichiers WAL utilisés dans les cycles de checkpoint précédents. Elle est augmentée immédiatement si l'utilisation en cours dépasse l'estimation, pour correspondre aux pics d'utilisation plutôt qu'à l'utilisation moyenne, jusqu'à un certain point. min_wal_size définit un nombre minimum de fichiers WAL recyclés pour une utilisation future, même si le système est inutilisé et que l'estimation suggère que peu de WAL sont nécessaires.

Indépendamment de max_wal_size, les wal_keep_size méga-octets les plus récents des fichiers WAL et un fichier WAL supplémentaire sont conservés en permanence. De plus, si l'archivage est activé, les anciens segments ne sont ni supprimés ni recyclés jusqu'à la réussite de leur archivage. Si l'archivage des WAL n'est pas assez rapide pour tenir le rythme de la génération des WAL, ou si la commande indiquée par archive_command échoue de manière répétée, les anciens fichiers WAL s'accumuleront dans le répertoire pg_wal jusqu'à ce que ce problème soit résolu. Un serveur standby lent ou en échec, et qui utilise un slot de réplication, aura le même effet (voir Section 27.2.6).

En mode de restauration d'archive et en mode standby, le serveur réalise périodiquement des restartpoints (points de redémarrage), qui sont similaire aux checkpoints lors du fonctionnement normal : le serveur force l'écriture de son état sur disque, met à jour le fichier pg_control pour indiquer que les données déjà traitées des journaux de transactions n'ont plus besoin d'être parcourues de nouveau, puis recycle les anciens journaux de transactions trouvés dans le répertoire pg_wal. Les restartpoints ne peuvent être réalisés plus fréquemment que les checkpoints du primaire car les restartpoints peuvent seulement être réalisés aux enregistrements de checkpoint. Un restartpoint est déclenché lorsqu'un enregistement de checkpoint est atteint si un minimum de checkpoint_timeout secondes se sont écoulées depuis le dernier restartpoint, ou si la taille totale des journaux de transactions va dépasser max_wal_size. Néanmoins, à cause de ces limitations sur quand un restartpoint peut être effectué, max_wal_size est souvent dépassé lors d'une restauration jusqu'à au plus un cycle de checkpoint de journaux (max_wal_size n'est de toute façon jamais une limite en dur, vous devriez donc toujours laisser plein d'espace pour éviter de manquer d'espace disque).

Il existe deux fonctions WAL internes couramment utilisées : XLogInsertRecord et XLogFlush. XLogInsertRecord est utilisée pour placer une nouvelle entrée à l'intérieur des tampons WAL en mémoire partagée. S'il n'y a plus d'espace pour la nouvelle entrée, XLogInsertRecord devra écrire (autrement dit, déplacer dans le cache du noyau) quelques tampons WAL remplis. Ceci n'est pas souhaitable parce que XLogInsertRecord est utilisée à chaque modification bas niveau de la base (par exemple, lors de l'insertion d'une ligne) quand un verrou exclusif est posé sur les pages de données affectées, et l'opération doit donc être aussi rapide que possible. Pire encore, écrire des tampons WAL peut aussi forcer la création d'un nouveau journal, ce qui prend encore plus de temps. Normalement, les tampons WAL doivent être écrits et vidés par un appel à XLogFlush fait, la plupart du temps, au moment de la validation d'une transaction pour assurer que les entrées de la transaction sont écrites vers un stockage permanent. Sur les systèmes avec une importante écriture de journaux, les requêtes de XLogFlush peuvent ne pas arriver assez souvent pour empêcher XLogInsert d'avoir à écrire lui-même. Sur de tels systèmes, on devrait augmenter le nombre de tampons WAL en modifiant le paramètre wal_buffers. Quand full_page_writes est configuré et que le système est très occupé, configurer wal_buffers avec une valeur plus importante aide à lisser les temps de réponse dans la période suivant immédiatement chaque checkpoint.

Le paramètre commit_delay définit combien de micro-secondes un processus maître d'un groupe de commit va s'endormir après avoir obtenu un verrou avec XLogFlush, pendant que les autres processus du groupe vont s'ajouter à la queue derrière le maître. Ce délai permet aux autres processus serveur d'ajouter leurs enregistrements de commit aux buffers WAL, pour qu'ils soient tous écrits par un éventuel vidage sur disque du maître. Il n'y aura pas d'endormissement si fsync n'est pas activé, ou si moins de commit_siblings autres sessions sont actuellement dans une transaction active ; ce mécanisme évite l'endormissement quand il est improbable que d'autres sessions valident bientôt leur transactions. Il est à noter que, sur certaines plateformes, la résolution d'une requête d'endormissement est de dix millisecondes, ce qui implique que toute valeur comprise entre 1 et 10000 pour le paramètre commit_delay aura le même effet. Notez aussi que, sur certaines plateformes, les opérations d'endormissement peuvent être légèrement plus longues que ce qui a été demandé par le paramètre.

Comme l'objet de commit_delay est de permettre d'amortir le coût de chaque opération de vidage sur disque sur plusieurs transactions concurrentes (potentiellement au prix de la latence des transactions), il est nécessaire de quantifier ce coût pour choisir intelligemment la valeur de ce paramètre. Plus le coût est élevé, plus commit_delay sera efficace au sein d'un débit de transactions croissant, jusqu'à un certain point. Le programme pg_test_fsync peut être utilisé pour mesurer le temps moyen en microsecondes que prend une seule opération de vidage de WAL. La moitié du temps moyen rapporté par ce programme pour une mise à jour d'une simple opération d'écriture de 8 ko est la valeur la plus souvent recommandée comme point de départ de l'optimisation d'une charge particulière. Bien que l'ajustement de la valeur de commit_delay soit particulièrement utile lorsque les journaux WAL sont stockés sur des disques à latence élevée, le gain peut aussi être significatif sur les supports de stockage avec des temps de synchronisation très rapides, comme les SSD ou les grappes RAID avec des caches en écriture dotés de batterie ; mais dans tous les cas, cela doit être testé avec une charge représentative de la réalité. Des valeurs plus élevées de commit_siblings peuvent être utilisées dans ce cas, alors que de petites valeurs de commit_siblings sont souvent utiles sur des supports de grande latence. À noter qu'il est possible qu'une valeur trop élevée de commit_delay augmente la latence des transactions à un tel point que le débit des transactions en souffre.

Lorsque commit_delay est défini à zéro (il s'agit de la valeur par défaut), il est toujours possible qu'un regroupement de commits se produise, mais chaque groupe ne consistera qu'en sessions atteignant le moment de l'enregistrement de commit pendant le laps de temps où la précédente opération de vidage (s'il y en a) opère. Avec un grand nombre de clients, un « effet tunnel » (gangway effect) a tendance à se produire, et ainsi les effets du regroupement de commits deviennent significatifs même lorsque commit_delay est à zéro, et dans ce cas commit_delay devient inutile. Définir commit_delay n'est utile que quand (1) il existe des transactions concurrentes, et (2) le débit est limité dans une certaine mesure par la vitesse de commit ; mais, dans le cas d'un temps de latence du disque élevé, ce paramètre peut augmenter efficacement le flux de transaction avec seulement deux clients (c'est-à-dire un unique client qui valide, et une transaction sœur).

Le paramètre wal_sync_method détermine comment PostgreSQL demande au noyau de forcer les mises à jour des journaux de transaction sur le disque. Toutes les différentes options devraient être identiques en terme de fiabilité, à l'exception de fsync_writethrough, qui peut parfois forcer une écriture du cache disque même quand d'autres options ne le font pas. Néanmoins, connaître l'option la plus rapide est assez dépendant de la plateforme. Vous pouvez tester les vitesses des différentes options en utilisant le programme pg_test_fsync. Notez que ce paramètre est ignoré si fsync a été désactivé.

Activer le paramètre de configuration wal_debug (à supposer que PostgreSQL ait été compilé avec le support de ce paramètre) permet d'enregistrer chaque appel WAL à XLogInsertRecord et XLogFlush dans les journaux applicatifs du serveur. Cette option pourrait être remplacée par un mécanisme plus général dans le futur.

Il existe deux fonctions internes pour écrire des données WAL sur disque : XLogWrite et issue_xlog_fsync. Quand track_wal_io_timing est activé, le nombre total de fois où XLogWrite écrit et issue_xlog_fsync synchronise des données WAL sur disque est compté comme, respectivement, wal_write_time et wal_sync_time dans pg_stat_wal. XLogWrite est normalement appelé par XLogInsertRecord (quand il n'y a plus d'espace pour le nouvel enregistrement dans le cachedes WAL), XLogFlush et le walwriter, pour écrire le cache WAL sur disque et appeler issue_xlog_fsync. issue_xlog_fsync est normalement appelé par XLogWrite pour synchroniser les fichiers WAL sur disque. Si wal_sync_method vaut soit open_datasync soit open_sync, une opération d'écriture dans XLogWrite garantit de synchroniser les données des WAL écrites sur disque et issue_xlog_fsync ne fait rien. Si wal_sync_method vaut soit fdatasync, soit fsync, soit fsync_writethrough, les opérations en écriture déplacent le cache WAL dans le cache du noyau et issue_xlog_fsync les synchronise sur disque. Quelque soit la configuration de track_wal_io_timing, le nombre de fois où XLogWrite écrit et que issue_xlog_fsync synchronise les données WAL sur disque sont aussi comptées dans, respectivement, wal_write et wal_sync de la vue pg_stat_wal.