WAL dans PostgreSQL: 1. Cache tampon

La série précédente était consacrée à l' isolation et aux versions multiples de PostgreSQL, et aujourd'hui nous en commençons une nouvelle - sur le mécanisme de journalisation en écriture anticipée. Permettez-moi de vous rappeler que le matériel est basé sur des cours de formation administrative que Pavel pluzanov et moi faisons , mais ne les répétez pas textuellement et sont destinés à une lecture réfléchie et à une expérimentation indépendante.

Ce cycle comprendra quatre parties:

  • Cache tampon (cet article);
  • Journal préenregistrement - comment il est organisé et comment il est utilisé pendant la récupération;
  • Enregistrement des points de contrôle et des antécédents - pourquoi sont-ils nécessaires et comment sont-ils configurés;
  • Réglage du journal - niveaux et tâches à résoudre, fiabilité et performances.

Pourquoi la journalisation est-elle nécessaire?


Dans le processus, une partie des données traitées par le SGBD est stockée dans la RAM et écrite sur le disque (ou tout autre support non volatile) de manière différée. Moins cela se produit, moins les entrées-sorties et le fonctionnement du système sont rapides.

Mais que se passera-t-il en cas de panne, par exemple lorsque l'alimentation est coupée ou si une erreur se produit dans le code SGBD ou le système d'exploitation? Tout le contenu de la RAM sera perdu et seules les données écrites sur le disque resteront (avec certains types de pannes, le disque peut également souffrir, mais dans ce cas, seule une copie de sauvegarde sera utile). En principe, les E / S peuvent être organisées de manière à ce que les données sur le disque soient toujours maintenues dans un état cohérent, mais cela est difficile et pas trop efficace (pour autant que je sache, seul Firebird l'a fait).

Habituellement, y compris PostgreSQL, les données écrites sur le disque sont incohérentes et lors de la récupération après une panne, des actions spéciales sont requises pour restaurer la cohérence. La journalisation est le mécanisme même qui rend cela possible.

Cache tampon


Curieusement, nous allons commencer à parler de journalisation avec un cache tampon. Le cache tampon n'est pas la seule structure stockée dans la RAM, mais l'une des plus importantes et des plus complexes. Comprendre le principe de son fonctionnement est important en soi, d'ailleurs, dans cet exemple, nous allons nous familiariser avec la façon dont les données sont échangées entre la RAM et le disque.

La mise en cache est utilisée partout dans les systèmes informatiques modernes; un seul processeur peut compter trois ou quatre niveaux de cache. En général, n'importe quel cache est nécessaire pour aplanir la différence de performances entre les deux types de mémoire, dont l'un est relativement rapide, mais il n'est pas suffisant pour tout le monde, et l'autre est relativement lent, mais abondant. Ainsi, le cache tampon lisse la différence entre le temps d'accès à la RAM (nanosecondes) et au disque (millisecondes).

Notez que le système d'exploitation dispose également d'un cache disque qui résout le même problème. Par conséquent, les SGBD tentent généralement d'éviter la double mise en cache en accédant directement au disque, en contournant le cache du système d'exploitation. Mais dans le cas de PostgreSQL, ce n'est pas le cas: toutes les données sont lues et écrites à l'aide d'opérations de fichiers ordinaires.

De plus, les baies de disques, et même les disques eux-mêmes, ont également leur propre cache. Ce fait nous est toujours utile lorsque nous abordons la question de la fiabilité.

Mais revenons au cache de tampon du SGBD.

Il est appelé ainsi car il s'agit d'un tableau de tampons . Chaque tampon est un emplacement pour une page de données (bloc), plus un en-tête. Le titre, entre autres, contient:

  • emplacement sur le disque de la page dans le tampon (fichier et numéro de bloc);
  • un signe que les données de la page ont changé et devraient tôt ou tard être écrites sur le disque (un tel tampon est appelé sale );
  • nombre d'appels au tampon (nombre d'utilisation);
  • indicateur d'épinglage du tampon (nombre de broches).

Le cache de tampon est situé dans la mémoire partagée du serveur et est accessible à tous les processus. Pour travailler avec des données - lire ou modifier, - traite les pages lues dans le cache. Pendant que la page est en cache, nous travaillons avec elle en RAM et économisons sur les accès disque.



Initialement, le cache contient des tampons vides, et tous sont liés dans la liste des tampons libres. La signification du pointeur sur la «prochaine victime» deviendra claire un peu plus tard. Pour trouver rapidement la page souhaitée dans le cache, une table de hachage est utilisée.

Page de recherche dans le cache


Lorsqu'un processus doit lire une page, il essaie d'abord de la trouver dans le cache de tampon à l'aide d'une table de hachage. La clé de hachage est le numéro de fichier et le numéro de page à l'intérieur du fichier. Dans le panier correspondant de la table de hachage, le processus trouve le numéro de tampon et vérifie s'il contient vraiment la page souhaitée. Comme avec n'importe quelle table de hachage, les collisions sont possibles ici; dans ce cas, le processus devra vérifier plusieurs pages.

L'utilisation d'une table de hachage a longtemps été critiquée. Cette structure vous permet de trouver rapidement le tampon sur la page, mais elle est complètement inutile si, par exemple, vous devez trouver tous les tampons occupés par une table particulière. Mais personne n'a encore proposé un bon substitut.

Si la page souhaitée se trouve dans le cache, le processus doit «geler» le tampon en augmentant le nombre de broches (plusieurs processus peuvent le faire simultanément). Tant que le tampon est fixe (la valeur du compteur est supérieure à zéro), il est considéré que le tampon est utilisé et que son contenu ne doit pas être «radicalement» modifié. Par exemple, une nouvelle version de la ligne peut apparaître dans la page - cela ne dérange personne en raison des règles de multi-version et de visibilité. Mais une autre page ne peut pas être lue dans le tampon épinglé.

Éviction


Il peut arriver que la page nécessaire ne soit pas trouvée dans le cache. Dans ce cas, il doit être lu depuis le disque vers un tampon.

S'il reste des tampons libres dans le cache, le premier libre est sélectionné. Mais tôt ou tard, ils se termineront (généralement, la taille de la base de données est supérieure à la mémoire allouée au cache), puis vous devrez choisir l'un des tampons occupés, y forcer la page et en lire un nouveau sur l'espace libre.

Le mécanisme de préemption est basé sur le fait qu'à chaque accès au tampon, les processus augmentent le nombre d'utilisations dans l'en-tête du tampon. Ainsi, les tampons qui sont utilisés moins souvent que d'autres ont une valeur de compteur inférieure et sont de bons candidats pour l'éviction.

L'algorithme de balayage d'horloge parcourt tous les tampons (en utilisant le pointeur vers la "prochaine victime"), diminuant ainsi le nombre d'accès. Pour l'éviction, le premier tampon est sélectionné, ce qui:

  1. a un compteur de hit zéro (nombre d'utilisation),
  2. et non fixe (nombre de broches nul).

Vous remarquerez peut-être que si tous les tampons ont un compteur d'accès différent de zéro, l'algorithme devra alors faire plus d'un cercle, en réinitialisant les compteurs, jusqu'à ce que l'un d'eux atteigne enfin zéro. Pour éviter les "cercles sinueux", la valeur maximale du compteur d'accès est limitée à 5. Mais malgré tout, avec une grande taille de cache de tampon, cet algorithme peut entraîner une surcharge importante.

Une fois le tampon trouvé, ce qui suit lui arrive.

Le tampon est épinglé pour montrer aux autres processus qu'il est utilisé. En plus de la fixation, d'autres moyens de blocage sont également utilisés, mais nous en parlerons plus séparément.

Si le tampon s'est avéré sale, c'est-à-dire qu'il contient des données modifiées, la page ne peut pas être simplement supprimée - elle doit d'abord être enregistrée sur le disque. Ce n'est pas une bonne situation, car le processus qui est sur le point de lire la page doit attendre l'enregistrement de données «étrangères», mais cet effet est atténué par les processus d'enregistrement de point de contrôle et d'arrière-plan, qui seront examinés plus tard.

Ensuite, une nouvelle page est lue à partir du disque dans le tampon sélectionné. Le compteur du nombre d'appels est réglé sur un. De plus, le lien vers la page chargée doit être enregistré dans la table de hachage pour pouvoir être retrouvé ultérieurement.

Maintenant, le lien vers la «prochaine victime» pointe vers le tampon suivant, et celui qui vient d'être chargé a le temps d'augmenter le compteur d'accès jusqu'à ce que le pointeur contourne tout le cache du tampon et revienne.

De mes propres yeux


Comme il est de coutume dans PostgreSQL, il existe une extension qui vous permet de regarder à l'intérieur du cache de tampon.

=> CREATE EXTENSION pg_buffercache; 

Créez un tableau et insérez-y une ligne.

 => CREATE TABLE cacheme( id integer ) WITH (autovacuum_enabled = off); => INSERT INTO cacheme VALUES (1); 

Qu'y aura-t-il dans le cache tampon? Au minimum, une page doit y apparaître avec une seule ligne ajoutée. Nous allons vérifier cela avec la requête suivante, dans laquelle nous sélectionnons uniquement les tampons appartenant à notre table (par le numéro de fichier relfilenode), et décodons le numéro de couche (relforknumber):

 => SELECT bufferid, CASE relforknumber WHEN 0 THEN 'main' WHEN 1 THEN 'fsm' WHEN 2 THEN 'vm' END relfork, relblocknumber, isdirty, usagecount, pinning_backends FROM pg_buffercache WHERE relfilenode = pg_relation_filenode('cacheme'::regclass); 
  bufferid | relfork | relblocknumber | isdirty | usagecount | pinning_backends ----------+---------+----------------+---------+------------+------------------ 15735 | main | 0 | t | 1 | 0 (1 row) 

Il en est ainsi - il y a une page dans le tampon. Il est sale (isdirty), le compteur d'accès est égal à un (usagecount) et il n'est fixé par aucun processus (pinning_backends).

Ajoutez maintenant une autre ligne et répétez la requête. Pour enregistrer des lettres, nous insérons une ligne dans une autre session et répétons la longue requête avec la commande \g .

 | => INSERT INTO cacheme VALUES (2); 

 => \g 
  bufferid | relfork | relblocknumber | isdirty | usagecount | pinning_backends ----------+---------+----------------+---------+------------+------------------ 15735 | main | 0 | t | 2 | 0 (1 row) 

Aucun nouveau tampon n'a été ajouté - la deuxième ligne tient sur la même page. Veuillez noter que le compteur d'utilisation a augmenté.

 | => SELECT * FROM cacheme; 
 | id | ---- | 1 | 2 | (2 rows) 

 => \g 
  bufferid | relfork | relblocknumber | isdirty | usagecount | pinning_backends ----------+---------+----------------+---------+------------+------------------ 15735 | main | 0 | t | 3 | 0 (1 row) 

Et après avoir accédé à la page pour la lecture, le compteur augmente également.

Et si vous nettoyez?

 | => VACUUM cacheme; 

 => \g 
  bufferid | relfork | relblocknumber | isdirty | usagecount | pinning_backends ----------+---------+----------------+---------+------------+------------------ 15731 | fsm | 1 | t | 1 | 0 15732 | fsm | 0 | t | 1 | 0 15733 | fsm | 2 | t | 2 | 0 15734 | vm | 0 | t | 2 | 0 15735 | main | 0 | t | 3 | 0 (5 rows) 

Le nettoyage a créé une carte de visibilité (une page) et une carte d'espace libre (trois pages - la taille minimale de cette carte).

Eh bien et ainsi de suite.

Réglage de la taille


La taille du cache est définie par le paramètre shared_buffers . La valeur par défaut est ridicule 128 Mo. C'est l'un des paramètres qu'il est logique d'augmenter immédiatement après l'installation de PostgreSQL.

 => SELECT setting, unit FROM pg_settings WHERE name = 'shared_buffers'; 
  setting | unit ---------+------ 16384 | 8kB (1 row) 

Gardez à l'esprit que la modification d'un paramètre nécessite un redémarrage du serveur, car toute la mémoire cache nécessaire est allouée au démarrage du serveur.

Pour quelles raisons choisir la valeur appropriée?

Même la plus grande base de données possède un ensemble limité de données «à chaud», avec lesquelles un travail actif est effectué à chaque instant. Idéalement, cet ensemble devrait être placé dans le cache de tampon (plus un peu d'espace pour les données «ponctuelles»). Si la taille du cache est plus petite, les pages activement utilisées se serrent constamment les unes les autres, créant une entrée-sortie excessive. Mais l'augmentation inconsidérée du cache est également une erreur. Avec une grande taille, les frais généraux de maintenance augmenteront et, en outre, la RAM est également requise pour d'autres besoins.

Ainsi, la taille optimale du cache tampon sera différente selon les systèmes: elle dépend des données, de l'application, de la charge. Malheureusement, il n'y a pas une telle signification magique qui conviendra aussi bien à tout le monde.

La recommandation standard est de prendre 1/4 de la RAM en première approximation (pour Windows avant PostgreSQL 10, il était recommandé de choisir une taille plus petite).

Et puis vous devez regarder la situation. Il vaut mieux faire une expérience: augmenter ou diminuer la taille du cache et comparer les performances du système. Bien sûr, pour cela, il est nécessaire d'avoir un banc d'essai et de pouvoir reproduire la charge typique - dans l'environnement de production, de telles expériences ressemblent à un plaisir douteux.

N'oubliez pas de consulter le rapport de Nikolay Samokhvalov à PgConf-2019: "Une approche industrielle de l' optimisation PostgreSQL: expériences de base de données "

Mais certaines informations sur ce qui se passe peuvent être glanées directement sur un système en direct en utilisant la même extension pg_buffercache - surtout, regardez sous le bon angle.

Par exemple, vous pouvez étudier la distribution des tampons en fonction de leur degré d'utilisation:

 => SELECT usagecount, count(*) FROM pg_buffercache GROUP BY usagecount ORDER BY usagecount; 
  usagecount | count ------------+------- 1 | 221 2 | 869 3 | 29 4 | 12 5 | 564 | 14689 (6 rows) 

Dans ce cas, de nombreuses valeurs de compteur vides sont des tampons libres. Pas étonnant pour un système où rien ne se passe.

Vous pouvez voir combien de tables de notre base de données sont mises en cache et à quel point ces données sont utilisées activement (par utilisation active dans cette requête, nous entendons des tampons avec un compteur d'utilisation supérieur à 3):

 => SELECT c.relname, count(*) blocks, round( 100.0 * 8192 * count(*) / pg_table_size(c.oid) ) "% of rel", round( 100.0 * 8192 * count(*) FILTER (WHERE b.usagecount > 3) / pg_table_size(c.oid) ) "% hot" FROM pg_buffercache b JOIN pg_class c ON pg_relation_filenode(c.oid) = b.relfilenode WHERE b.reldatabase IN ( 0, (SELECT oid FROM pg_database WHERE datname = current_database()) ) AND b.usagecount is not null GROUP BY c.relname, c.oid ORDER BY 2 DESC LIMIT 10; 
  relname | blocks | % of rel | % hot ---------------------------+--------+----------+------- vac | 833 | 100 | 0 pg_proc | 71 | 85 | 37 pg_depend | 57 | 98 | 19 pg_attribute | 55 | 100 | 64 vac_s | 32 | 4 | 0 pg_statistic | 27 | 71 | 63 autovac | 22 | 100 | 95 pg_depend_reference_index | 19 | 48 | 35 pg_rewrite | 17 | 23 | 8 pg_class | 16 | 100 | 100 (10 rows) 

Ici, par exemple, on peut voir que la table vac occupe la plus grande place (nous l'avons utilisée dans l'un des sujets précédents), mais personne ne l'a abordée depuis longtemps et elle n'a pas encore été éliminée simplement parce que les tampons libres ne sont pas encore épuisés.

Vous pouvez trouver d'autres sections qui fourniront des informations utiles à la réflexion. Il suffit de considérer que de telles demandes:

  • doit être répété plusieurs fois: les chiffres varient dans certaines limites;
  • il n'est pas nécessaire de l'exécuter en permanence (dans le cadre de la surveillance) en raison du fait que l'extension bloque le fonctionnement avec le cache tampon pendant une courte période.

Et encore une chose. Nous ne devons pas oublier que PostgreSQL travaille avec des fichiers via des appels réguliers au système d'exploitation et, par conséquent, il y a une double mise en cache: les pages tombent à la fois dans le cache de tampon du SGBD et dans le cache du système d'exploitation. Ainsi, le «manque» dans le cache de tampon n'entraîne pas toujours le besoin d'une véritable E / S. Mais la stratégie d'éviction du système d'exploitation est différente de la stratégie du SGBD: le système d'exploitation ne sait rien de la signification des données lues.

Déplacement de masse


Dans les opérations qui effectuent une lecture ou une écriture en masse de données, il existe un danger de déplacer rapidement les pages utiles du cache de tampon avec des données "ponctuelles".

Pour éviter que cela ne se produise, les soi-disant anneaux de tampon sont utilisés pour de telles opérations - une petite partie du cache de tampon est allouée pour chaque opération. L'extrusion n'agit que dans l'anneau, de sorte que le reste des données du cache de tampon ne souffre pas.

Pour la lecture séquentielle de grandes tables (dont la taille dépasse le quart du cache de tampon), 32 pages sont allouées. Si un autre processus a également besoin de ces données lors de la lecture d'une table, il ne commence pas la lecture de la table en premier, mais se connecte à un anneau tampon existant. Après avoir numérisé, il lit le début «manqué» du tableau.

Voyons ça. Pour ce faire, créez un tableau de sorte qu'une ligne occupe une page entière - il est plus pratique de compter. La taille du cache de tampon par défaut est de 128 Mo = 16384 pages de 8 Ko. Vous devez donc insérer plus de 4096 lignes de page dans le tableau.

 => CREATE TABLE big( id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, s char(1000) ) WITH (fillfactor=10); => INSERT INTO big(s) SELECT 'FOO' FROM generate_series(1,4096+1); 

Analysons le tableau.

 => ANALYZE big; => SELECT relpages FROM pg_class WHERE oid = 'big'::regclass; 
  relpages ---------- 4097 (1 row) 

Nous devons maintenant redémarrer le serveur pour vider le cache des données de la table lues par l'analyse.

 student$ sudo pg_ctlcluster 11 main restart 

Après le redémarrage, lisez l'intégralité du tableau:

 => EXPLAIN (ANALYZE, COSTS OFF) SELECT count(*) FROM big; 
  QUERY PLAN --------------------------------------------------------------------- Aggregate (actual time=14.472..14.473 rows=1 loops=1) -> Seq Scan on big (actual time=0.031..13.022 rows=4097 loops=1) Planning Time: 0.528 ms Execution Time: 14.590 ms (4 rows) 

Et assurez-vous que seulement 32 tampons sont occupés par des pages tabulaires dans le cache de tampons:

 => SELECT count(*) FROM pg_buffercache WHERE relfilenode = pg_relation_filenode('big'::regclass); 
  count ------- 32 (1 row) 

Si le balayage séquentiel est interdit, le tableau sera lu par index:

 => SET enable_seqscan = off; => EXPLAIN (ANALYZE, COSTS OFF) SELECT count(*) FROM big; 
  QUERY PLAN ------------------------------------------------------------------------------------------- Aggregate (actual time=50.300..50.301 rows=1 loops=1) -> Index Only Scan using big_pkey on big (actual time=0.098..48.547 rows=4097 loops=1) Heap Fetches: 4097 Planning Time: 0.067 ms Execution Time: 50.340 ms (5 rows) 

Dans ce cas, l'anneau tampon n'est pas utilisé et la table entière apparaît dans le cache tampon (et presque tout l'index aussi):

 => SELECT count(*) FROM pg_buffercache WHERE relfilenode = pg_relation_filenode('big'::regclass); 
  count ------- 4097 (1 row) 

De la même manière, des anneaux de tampon sont utilisés pour le processus de nettoyage (également 32 pages) et pour les opérations d'écriture en bloc COPY IN et CREATE TABLE AS SELECT (généralement 2048 pages, mais pas plus de 1/8 du cache de tampon total).

Tables temporaires


Une exception à la règle générale est les tables temporaires. Étant donné que les données temporaires ne sont visibles que pour un seul processus, elles n'ont rien à voir dans le cache de tampon partagé. De plus, les données temporaires n'existent que dans une seule session, il n'est donc pas nécessaire de les protéger contre les pannes.

Pour les données temporaires, un cache est utilisé dans la mémoire locale du processus propriétaire de la table. Étant donné que ces données ne sont disponibles que pour un seul processus, elles n'ont pas besoin d'être protégées par des verrous. Le cache local utilise l'algorithme préemptif habituel.

Contrairement au cache de tampon général, la mémoire du cache local est allouée selon les besoins, car les tables temporaires ne sont pas utilisées dans toutes les sessions. La quantité maximale de mémoire pour les tables temporaires dans une session est limitée par le paramètre temp_buffers .

Réchauffer le cache


Après le redémarrage du serveur, un certain temps doit s'écouler avant que le cache ne se "réchauffe" - accumule les données réellement utilisées activement. Parfois, il peut être utile de lire immédiatement les données de certaines tables dans le cache, et une extension spéciale est conçue pour cela:

 => CREATE EXTENSION pg_prewarm; 

Auparavant, une extension ne pouvait lire que certaines tables dans le cache du tampon (ou uniquement dans le cache du système d'exploitation). Mais dans PostgreSQL 11, il a pu enregistrer l'état actuel du cache sur le disque et le restaurer après un redémarrage du serveur. Pour en profiter, vous devez ajouter la bibliothèque à shared_preload_libraries et redémarrer le serveur.

 => ALTER SYSTEM SET shared_preload_libraries = 'pg_prewarm'; 

 student$ sudo pg_ctlcluster 11 main restart 

Le champ de redémarrage, si le paramètre pg_prewarm.autoprewarm n'a pas changé, le processus d'arrière-plan maître autoprewarm démarrera automatiquement, ce qui une fois dans pg_prewarm.autoprewarm_interval videra la liste des pages du cache sur le disque (n'oubliez pas de prendre en compte le nouveau processus lors de la configuration de max_parallel_processes ).

 => SELECT name, setting, unit FROM pg_settings WHERE name LIKE 'pg_prewarm%'; 
  name | setting | unit ---------------------------------+---------+------ pg_prewarm.autoprewarm | on | pg_prewarm.autoprewarm_interval | 300 | s (2 rows) 

 postgres$ ps -o pid,command --ppid `head -n 1 /var/lib/postgresql/11/main/postmaster.pid` | grep prewarm 
 10436 postgres: 11/main: autoprewarm master 

Maintenant, il n'y a pas de grande table dans le cache:

 => SELECT count(*) FROM pg_buffercache WHERE relfilenode = pg_relation_filenode('big'::regclass); 
  count ------- 0 (1 row) 

Si nous supposons que tout son contenu est très important, nous pouvons le lire dans le cache tampon en appelant la fonction suivante:

 => SELECT pg_prewarm('big'); 
  pg_prewarm ------------ 4097 (1 row) 

 => SELECT count(*) FROM pg_buffercache WHERE relfilenode = pg_relation_filenode('big'::regclass); 
  count ------- 4097 (1 row) 

La liste des pages est sauvegardée dans le fichier autoprewarm.blocks. Pour le voir, vous pouvez simplement attendre la première exécution du processus maître de bras automatique, mais nous l'initions manuellement:

 => SELECT autoprewarm_dump_now(); 
  autoprewarm_dump_now ---------------------- 4340 (1 row) 

Le nombre de pages supprimées est supérieur à 4097 - cela inclut les pages d'objets de catalogue système déjà lues par le serveur. Et voici le dossier:

 postgres$ ls -l /var/lib/postgresql/11/main/autoprewarm.blocks 
 -rw------- 1 postgres postgres 102078  29 15:51 /var/lib/postgresql/11/main/autoprewarm.blocks 

Redémarrez maintenant le serveur à nouveau.

 student$ sudo pg_ctlcluster 11 main restart 

Et immédiatement après le lancement, notre table apparaît à nouveau dans le cache.

 => SELECT count(*) FROM pg_buffercache WHERE relfilenode = pg_relation_filenode('big'::regclass); 
  count ------- 4097 (1 row) 

Cela fournit le même processus maître à autoprewarm: il lit le fichier, divise les pages en bases de données, les trie (afin que la lecture à partir du disque soit aussi cohérente que possible) et passe le travailleur autoprewarm au flux de travail séparé pour le traitement.

À suivre .

Source: https://habr.com/ru/post/fr458186/


All Articles