Cette section fournit un aperçu de TOAST (The Oversized-Attribute Storage Technique, la technique de stockage des attributs trop grands).
Puisque PostgreSQL utilise une taille de page fixe (habituellement 8 ko) et n'autorise pas qu'une ligne s'étende sur plusieurs pages. Du coup, il n'est pas possible de stocker de grandes valeurs directement dans les champs. Pour dépasser cette limitation, les valeurs de champ volumineuses sont compressées et/ou divisées en plusieurs lignes physiques. Ceci survient de façon transparente pour l'utilisateur, avec seulement un petit impact sur le code du serveur. Cette technique est connu sous l'acronyme affectueux de TOAST (ou « the best thing since sliced bread »). L'infrastructure TOAST est aussi utilisé pour améliorer la gestion des valeurs de grande taille en mémoire.
Seuls certains types de données supportent TOAST --
il n'est pas nécessaire d'imposer cette surcharge sur les types de données
qui ne produisent pas de gros volumes. Pour supporter
TOAST, un type de données doit avoir une représentation
(varlena) à longueur variable, dans laquelle,
généralement, le premier mot de quatre octets contient la longueur totale
de la valeur en octets (en incluant ce mot). TOAST ne
restreint pas le reste de la représentation de la donnée. Les
représentations spéciales appelées collectivement valeurs
TOASTées fonctionnement en modifiant et en
ré-interprétant ce mot de longueur initial. De ce fait, les fonctions C
supportant un type de données TOAST-able doivent faire
attention à la façon dont elles gèrent les valeurs en entrées
potentiellement TOASTées : une entrée pourrait ne
pas consister en un mot longueur de quatre octets et son contenu situé
après tant qu'elle n'a pas été dé-toastée. (Ceci se
fait habituellement en appelant PG_DETOAST_DATUM
avant
toute action sur une valeur en entrée, mais dans certains cas, des
approches plus efficaces sont possibles. Voir Section 36.13.1 pour plus de détails.)
TOAST récupère deux bits du mot contenant la longueur d'un varlena (ceux de poids fort sur les machines big-endian, ceux de poids faible sur les machines little-endian), limitant du coup la taille logique de toute valeur d'un type de données TOAST à 1 Go (230 - 1 octets). Quand les deux bits sont à zéro, la valeur est une valeur non TOASTé du type de données et les bits restants dans le mot contenant la longueur indiquent la taille total du datum (incluant ce mot) en octets. Quand le bit de poids fort (ou de poids faible) est à un, la valeur a un en-tête de seulement un octet alors qu'un en-tête normal en fait quatre. Les bits restants donnent la taille total du datum (incluant ce mot) en octets. Cette alternative supporte un stockage efficace en espace de valeurs plus petites que 127 octets, tout en permettant au type de données de grossir jusqu'à 1 Go si besoin. Les valeurs avec un en-tête sur un octet ne sont pas alignées par rapport à une limite particulière, alors que les valeurs avec des en-têtes à quatre octets sont au moins alignées sur une limite de quatre octets ; la suppression de cet alignement permet de gagner encore un peu d'espace supplémentaire qui est significatif quand on le compare au stockage d'une petite valeur. Voici un cas particulier. Si les bits restants d'un en-tête sur un octet sont tous à zéro (ce qui serait impossible pour une longueur auto-inclue), la valeur est un pointeur vers la donnée sur disque, avec d'autres alternatives décrites ci-dessous. Le type et la taille d'un tel pointeur TOAST sont déterminés par le code enregistré dans le deuxième octet du datum. Enfin, quand le premier ou dernier bit vaut 0 mais que le bit adjacent vaut 1, le contenu du datum a été compressé et doit être décompressé avant de pouvoir être utilisé. Dans ce cas, les bits restants du mot longueur de quatre octets donnent une taille totale du datum compressé, pas celles des données au départ. Notez que la compression est aussi possible pour les données de la table TOAST mais l 'en-tête varlena n'indique pas si c'est le cas -- le contenu du pointeur TOAST le précise.
La technique de compression utilisée soit pour les données en ligne ou hors
ligne est sélectionnable pour chaque colonne en configurant l'option de
colonne COMPRESSION
avec les commandes CREATE
TABLE
et ALTER TABLE
. La valeur par défaut
pour les colonnes sans paramétrage spécifique correspond à la valeur du
paramètre default_toast_compression au moment où la
donnée est insérée.
Comme mentionné, il existe plusieurs types de pointeurs
TOAST. Le type le plus ancien et le plus commun est un
pointeur vers des données disques stockées dans une table
TOAST qui est séparée, bien qu'associée, de
la table contenant le pointeur TOAST. Ces pointeurs
sur disque sont créés par le code de gestion des
TOAST (dans
access/common/toast_internals.c
) quand un enregistrement à
stocker sur disque est trop gros pour être stocké comme d'habitude. Plus de
détails sont disponibles dans Section 65.2.1.
Alternativement, un pointeur TOAST peut contenir un
pointeur vers des données hors-ligne qui apparaissent ailleurs en mémoire.
De tels datums ont une vie courte, et n'iront jamais sur disque. Elles sont
cependant utiles pour éviter de copier et de traiter plusieurs fois de
grosses données. Section 65.2.2 fournit plus de
détails.
Si une des colonnes d'une table est TOAST-able, la table
aura une table TOAST associée, dont l'OID est enregistré
dans la colonne
pg_class
.reltoastrelid
pour cette table. Les valeurs TOASTées sur disque sont
conservées dans la table TOAST, comme décrit en détails
ci-dessous.
Les valeurs hors-ligne sont divisées (après compression si nécessaire) en
morceaux d'au plus TOAST_MAX_CHUNK_SIZE
octets
(par défaut, cette valeur est choisie pour que quatre morceaux de ligne
tiennent sur une page, d'où les 2000 octets). Chaque morceau est stocké
comme une ligne séparée dans la table TOAST de la
table propriétaire. Chaque table TOAST contient les
colonnes chunk_id
(un OID identifiant la
valeur TOASTée particulière),
chunk_seq
(un numéro de séquence pour le
morceau de la valeur) et chunk_data
(la donnée
réelle du morceau). Un index unique sur
chunk_id
et
chunk_seq
offre une récupération rapide des
valeurs. Un pointeur datum représentant une valeur
TOASTée hors-ligne a par conséquent besoin de stocker
l'OID de la table TOAST dans laquelle chercher et
l'OID de la valeur spécifique (son chunk_id
).
Par commodité, les pointeurs datums stockent aussi la taille logique du
datum (taille de la donnée originale non compressée), la taille stockée
réelle (différente si la compression a été appliquée), et la méthode de
compression utilisée, si nécessaire. À partir des octets d'en-tête
varlena, la taille totale d'un pointeur datum TOAST
est par conséquent de 18 octets quelque soit la taille réelle de la
valeur représentée.
Le code TOAST est déclenché seulement quand une valeur de ligne
à stocker dans une table est plus grande que TOAST_TUPLE_THRESHOLD
octets (habituellement
2 ko). Le code TOAST compressera et/ou déplacera les valeurs
de champ hors la ligne jusqu'à ce que la valeur de la ligne soit plus petite que
TOAST_TUPLE_TARGET
octets (habituellement là-aussi
2 ko) ou que plus aucun gain ne puisse être réalisé.
Lors d'une opération UPDATE, les valeurs des champs non modifiées sont habituellement
préservées telles quelles ; donc un UPDATE sur une ligne avec des valeurs hors
ligne n'induit pas de coûts à cause de TOAST si aucune des valeurs
hors-ligne n'est modifiée.
Le code TOAST connaît quatre stratégies différentes pour stocker les colonnes TOAST-ables :
PLAIN
empêche soit la compression soit le stockage
hors-ligne. Ceci est la seule stratégie possible
pour les colonnes des types de données non
TOAST-ables.
EXTENDED
permet à la fois la compression et le
stockage hors-ligne. Ceci est la valeur par défaut de la plupart des
types de données TOAST-ables. La compression sera tentée en
premier, ensuite le stockage hors-ligne si la ligne est toujours trop
grande.
EXTERNAL
autorise le stockage hors-ligne mais pas la
compression. L'utilisation d'EXTERNAL
rendra plus rapides les
opérations sur des sous-chaînes d'importantes colonnes de type
text
et bytea
(au dépens d'un
espace de stockage accrus) car ces opérations sont optimisées pour
récupérer seulement les parties requises de la valeur hors-ligne
lorsqu'elle n'est pas compressée.
MAIN
autorise la compression mais pas le stockage
hors-ligne. (En réalité le stockage hors-ligne sera toujours réalisé
pour de telles colonnes mais seulement en dernier ressort s'il n'existe
aucune autre solution pour diminuer suffisamment la taille de la ligne
pour qu'elle tienne sur une page.)
Chaque type de données TOAST-able spécifie une stratégie par défaut
pour les colonnes de ce type de donnée, mais la stratégie pour une colonne d'une table
donnée peut être modifiée avec ALTER
TABLE ... SET STORAGE
.
TOAST_TUPLE_TARGET
peut être ajusté pour chaque table en
utilisant ALTER TABLE ... SET
(toast_tuple_target = N)
Cette combinaison a de nombreux avantages comparés à une approche plus directe comme autoriser le stockage des valeurs de lignes sur plusieurs pages. En supposant que les requêtes sont habituellement qualifiées par comparaison avec des valeurs de clé relativement petites, la grosse partie du travail de l'exécuteur sera réalisée en utilisant l'entrée principale de la ligne. Les grandes valeurs des attributs TOASTés seront seulement récupérées (si elles sont sélectionnées) au moment où l'ensemble de résultats est envoyé au client. Ainsi, la table principale est bien plus petite et un plus grand nombre de ses lignes tiennent dans le cache du tampon partagé, ce qui ne serait pas le cas sans aucun stockage hors-ligne. Le tri l'utilise aussi, et les tris seront plus souvent réalisés entièrement en mémoire. Un petit test a montré qu'une table contenant des pages HTML typiques ainsi que leurs URL étaient stockées en à peu près la moitié de la taille des données brutes en incluant la table TOAST et que la table principale contenait moins de 10 % de la totalité des données (les URL et quelques petites pages HTML). Il n'y avait pas de différence à l'exécution en comparaison avec une table non TOASTée, dans laquelle toutes les pages HTLM avaient été coupées à 7 ko pour tenir.
Les pointeurs TOAST peuvent pointer vers des données qui ne sont pas sur disque, mais ailleurs, dans la mémoire du processus serveur en cours d'exécution. De toute évidence, de tels pointeurs ont une durée de vie courte, mais ils n'en restent pas moins utiles. Il existe actuellement deux cas : les pointeurs vers des données indirectes et les pointeurs vers des données étendues.
Les pointeurs TOAST indirectes pointent simplement vers une valeur varlena dite non-indirect en mémoire. Ce cas a été créé à la base comme un PoC (Proof of Concept), mais il est actuellement utilisé lors du décodage logique pour éviter d'avoir potentiellement à créer des enregistrements physiques dépassant 1 Go (ce que le déplacement des valeurs hors-ligne du champ dans l'enregistrement pourrait faire). L'intérêt est limité car le création du datum pointeur est totalement responsable de la survie de la donnée référencée tant que le pointeur existe, et aucune infrastructure n'a été mise en place pour aider à ça.
Les pointeurs TOAST étendus sont utiles pour les types
de données complexes dont la représentation sur disque n'est pas
particulièrement adaptée pour un traitement. Par exemple, la représentation
varlena standard d'un tableau PostgreSQL inclut
des informations sur les dimensions, un champ de bits pour les éléments
NULL s'il y en a, et enfin les valeurs de tous les éléments dans l'ordre.
Quand l'élément est lui-même de longueur variable, la seule façon de
trouver l'élément N
est de parcourir tous les
éléments précédents. Cette représentation est appropriée pour le stockage
sur disque car elle prend peu de place mais pour le traitement du tableau,
il est mieux d'avoir une représentation « étendue » ou
« déconstruite » pour laquelle l'emplacement de chaque élément
est identifié. Le mécanisme du pointeur TOAST supporte
ce besoin en autorisant un Datum passé par référencer à pointer vers soit
une valeur varlena standard (la représentation sur disque) soit un pointeur
TOAST vers une représentation étendue quelque part en
mémoire. Les détails de cette représentation étendue sont à la discrétion
du type de données, bien qu'elle doive avoir un en-tête standard et
accepter les autres prérequis de l'API indiqués dans
src/include/utils/expandeddatum.h
. Les fonctions C
travaillant avec le type de données doivent choisir de gérer une ou l'autre
représentation. Les fonctions qui ne connaissant pas la représentation
étendue, et qui de ce fait appliquent PG_DETOAST_DATUM
à leurs données en entrée, recevront automatiquement la représentation
varlena traditionnelle. De ce fait, le support d'une représentation étendue
peut se faire petit à petit, une fonction à la fois.
Les pointeurs TOAST vers des valeurs étendues sont encore divisés en pointeurs read-write (lecture/écriture) et read-only (lecture seule). La représentation pointée est la même dans les deux cas, mais une fonction qui reçoit un pointeur read-write est autorisée à modifier directement la valeur référencée alors qu'une fonction qui reçoit un pointeur read-only ne l'est pas ; elle doit tout d'abord créer une copie si elle veut avoir une version modifiée de la valeur. Cette distinction et certaines conventions associées rendent possible d'éviter des copies inutiles de valeurs étendues pendant l'exécution de la requête.
Pour tous les types de pointeurs TOAST en mémoire, le code de gestion des TOAST s'assurer qu'aucun datum pointeur ne puisse être enregistré par erreur sur disque. Les pointeurs TOAST en mémoire sont automatiquement étendus en des valeurs varlena en ligne tout à fait standards avant leur enregistrement -- puis potentiellement convertis en pointeurs TOAST sur disque si l'enregistrement devient trop gros.