52.3. Emplacement des pages de la base de données

Cette section fournit un aperçu du format des pages utilisées par les tables et index de PostgreSQL™.[10] Les séquences et les tables TOAST tables sont formatées comme des tables standards.

Dans l'explication qui suit, un octet contient huit bits. De plus, le terme élément fait référence à une valeur de données individuelle qui est stockée dans une page. Dans une table, un élément est une ligne ; dans un index, un élément est une entrée d'index.

Chaque table et index est stocké comme un tableau de pages d'une taille fixe (habituellement 8 Ko, bien qu'une taille de page différente peut être sélectionnée lors de la compilation du serveur). Dans une table, toutes les pages sont logiquement équivalentes pour qu'un élément (ligne) particulier puisse être stocké dans n'importe quelle page. Dans les index, la première page est généralement réservée comme métapage contenant des informations de contrôle, et il peut exister différents types de pages à l'intérieur de l'index, suivant la méthode d'accès à l'index.

Tableau 52.2, « Disposition d'une page » affiche le contenu complet d'une page. Il existe cinq parties pour chaque page.

Tableau 52.2. Disposition générale d'une page

Élément Description
PageHeaderData Longueur de 20 octets. Contient des informations générales sur la page y compris des pointeurs sur les espaces libres.
ItemPointerData Tableau de paires (décalage,longueur) pointant sur les éléments réels. Quatre octets par élément.
Free space L'espace non alloué. Les pointeurs de nouveaux éléments sont alloués à partir du début de cette région, les nouveaux éléments à partir de la fin.
Items Les éléments eux-mêmes.
Special space Données spécifiques des méthodes d'accès aux index. Différentes méthodes stockent différentes données. Vide pour les tables ordinaires.

Les vingt premiers octets de chaque page consistent en un en-tête de page (PageHeaderData). Son format est détaillé dans Tableau 52.3, « Disposition de PageHeaderData ». Les deux premiers champs traquent l'entrée WAL la plus récente relative à cette page. Ils sont suivis par trois champs d'entiers sur deux octets (pd_lower, pd_upper et pd_special). Ils contiennent des décalages d'octets à partir du début de la page jusqu'au début de l'espace non alloué, jusqu'à la fin de l'espace non alloué, et jusqu'au début de l'espace spécial. Les deux derniers octets de l'en-tête de page, pd_pagesize_version, stockent à la fois la taille de la page et un indicateur de versoin. À partir de la version 8.1 de PostgreSQL™, le numéro de version est 3 ; PostgreSQL™ 8.0 a utilisé le numéro de version 2 ; PostgreSQL™ 7.3 et 7.4 ont utilisé le numéro de version 1 ; les versions précédentes utilisaient le numéro de version 0. (La disposition fondamentale de la page et le format de l'en-tête n'ont pas changé dans ces versions mais la disposition de l'en-tête des lignes de tête a changé.) La taille de la page est seulement présente comme vérification croisée ; il n'existe pas de support pour avoir plus d'une taille de page dans une installation.

Tableau 52.3. Disposition de PageHeaderData

Champ Type Longueur Description
pd_lsn XLogRecPtr 8 octets LSN : octet suivant le dernier octet de l'enregistrement xlog pour la dernière modification de cette page
pd_tli TimeLineID 4 octets TLI de la dernière modification
pd_lower LocationIndex 2 octets Décalage jusqu'au début de l'espace libre
pd_upper LocationIndex 2 octets Décalage jusqu'à la fin de l'espace libre
pd_special LocationIndex 2 octets Décalage jusqu'au début de l'espace spécial
pd_pagesize_version uint16 2 octets Taille de la page et disposition de l'information du numéro de version

Tous les détails se trouvent dans src/include/storage/bufpage.h.

Après l'en-tête de la page se trouvent les identificateurs d'élément (ItemIdData), chacun nécessitant quatre octets. Un identificateur d'élément contient un décalage d'octet vers le début d'un élément, sa longueur en octets, et quelques bits d'attributs qui affectent son interprétation. Les nouveaux identificateurs d'éléments sont alloués si nécessaire à partir du début de l'espace non alloué. Le nombre d'identificateurs d'éléments présents peut être déterminé en regardant pd_lower, qui est augmenté pour allouer un nouvel identificateur. Comme un identificateur d'élément n'est jamais déplacé tant qu'il n'est pas libéré, son index pourrait être utilisé sur une base à long terme pour référencer un élément, même quand l'élément lui-même est déplacé le long de la page pour compresser l'espace libre. En fait, chaque pointeur vers un élément (ItemPointer, aussi connu sous le nom de CTID), créé par PostgreSQL™ consiste en un numéro de page et l'index de l'identificateur d'élément.

Les éléments eux-mêmes sont stockés dans l'espace alloué en marche arrière, à partir de la fin de l'espace non alloué. La structure exacte varie suivant le contenu de la table. Les tables et les séquences utilisent toutes les deux une structure nommée HeapTupleHeaderData, décrite ci-dessous.

La section finale est la « section spéciale » qui pourrait contenir tout ce que les méthodes d'accès souhaitent stocker. Par exemple, les index b-tree stockent des liens vers les enfants gauche et droit de la page ainsi que quelques autres données sur la structure de l'index. Les tables ordinaires n'utilisent pas du tout de section spéciale (indiquée en configurant pd_special à la taille de la page).

Toutes les lignes de la table sont structurées de la même façon. Il existe un en-tête à taille fixe (occupant 27 octets sur la plupart des machines), suivi par un bitmap NULL optionnel, un champ ID de l'objet optionnel et les données de l'utilisateur. L'en-tête est détaillé dans Tableau 52.4, « Disposition de HeapTupleHeaderData ». Les données réelles de l'utilisateur (les colonnes de la ligne) commencent àu décalage indiqué par t_hoff, qui doit toujours être un multiple de la distance MAXALIGN pour la plateforme. Le bitmap NULL est seulement présent si le bit HEAP_HASNULL est initialisé dans t_infomask. S'il est présent, il commence juste après l'en-tête fixe et occupe suffisamment d'octets pour avoir un bit par colonne de données (c'est-à-dire t_natts bits ensemble). Dans cette liste de bits, un bit 1 indique une valeur non NULL, un bit 0 une valeur NULL. Quand le bitmap n'est pas présent, toutes les colonnes sont supposées non NULL. L'ID de l'objet est seulement présent si le bit HEAP_HASOID est initialisé dans t_infomask. S'il est présent, il apparaît juste avant la limite t_hoff. Tout ajout nécessaire pour faire de t_hoff un multiple de MAXALIGN apparaîtra entre le bitmap NULL et l'ID de l'objet. (Ceci nous assure en retour que l'ID de l'objet est convenablement aligné.)

Tableau 52.4. Disposition de HeapTupleHeaderData

Champ Type Longueur Description
t_xmin TransactionId 4 octets insère le tampon XID
t_cmin CommandId 4 octets insère le tampon CID
t_xmax TransactionId 4 octets supprime le tampon XID
t_cmax CommandId 4 octets supprime le tampon CID (surcharge avec t_xvac)
t_xvac TransactionId 4 octets XID pour l'opération VACUUM déplaçant une version de ligne
t_ctid ItemPointerData 6 octets TID en cours pour cette version de ligne ou pour une version plus récente
t_natts int16 2 octets nombre d'attributs
t_infomask uint16 2 octets différents bits d'options (flag bits)
t_hoff uint8 1 octet décalage vers les données utilisateur

Tous les détails sont disponibles dans src/include/access/htup.h.

Interpréter les données réelles peut seulement se faire avec des informations obtenues à partir d'autres tables, principalement pg_attribute. Les valeurs clés nécessaires pour identifier les emplacements des champs sont attlen et attalign. Il n'existe aucun moyen pour obtenir directement un attribut particulier, sauf quand il n'y a que des champs de largeur fixe et aucune colonne NULL. Tout ceci est emballé dans les fonctions heap_getattr, fastgetattr et heap_getsysattr.

Pour lire les données, vous avez besoin d'examinez chaque attribut à son tour. Commencez par vérifier si le champ est NULL en fonction du bitmap NULL. S'il l'est, allez au suivant. Puis, assurez-vous que vous avez le bon alignement. Si le champ est un champ à taille fixe, alors tous les octets sont placés simplement. S'il s'agit d'un champ à taille variable (attlen = -1), alors c'est un peu plus compliqué. Tous les types de données à longueur variable partagent la même structure commune d'en-tête, varattrib, qui inclut la longueur totale de la valeur stockée et quelques bits d'option. Suivant les options, les données pourraient être soit dans la table de base soit dans une table TOAST ; elles pourraient aussi être compressées (voir Section 52.2, « TOAST »).



[10] En réalité, les méthodes d'accès par index n'ont pas besoin d'utiliser ce format de page. Toutes les méthodes d'indexage existantes utilisent ce format de base mais les données conservées dans les métapages des index ne suivent habituellement pas les règles d'emplacement des éléments.