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 »).
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 les 32 premiers bits contiennent la longueur totale de la valeur en octets (ceci incluant la longueur elle-même). TOAST n'a aucune contrainte supplémentaire sur la représentation. Toutes les fonctions niveau C qui gèrent un type données supportant TOAST doivent faire attention à gérer les valeurs en entrée TOASTées. (Ceci se fait normalement en appelant PG_DETOAST_DATUM avant de faire quoi que ce soit avec une valeur en entrée; mais dans certains cas, des approches plus efficaces sont possibles.)
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 indique 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 donne la taille total du datum (incluant ce mot) en octets. Il reste un cas spécial : si les bits restants sont tous à zéro (ce qui est impossible étant donné que le mot indiquant la longueur est inclut dans la taille), la valeur est un pointeur vers une donnée stockée dans une table TOAST séparée (la taille d'un pointeur TOAST est indiquée dans le second octet du datum). Les valeurs dont l'en-tête fait un seul octet ne sont pas alignées sur une limite particulière. Enfin, quand le bit de poids fort (ou de poids faible) est supprimé mais que le bit adjacent vaut un, le contenu du datum est compressé et doit être décompresser avant utilisation. Dans ce cas, les bits restants du mot contenant la longueur indiquent la 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.
Si une des colonnes d'une table est TOAST-able, la table disposera d'une table TOAST associé, dont l'OID est stockée dans l'entrée pg_class.reltoastrelid de la table. Les valeurs TOASTées hors-ligne sont conservées dans la table TOAST comme décrit avec plus de détails ci-dessous.
La technique de compression utilisée est un simple et rapide membre de la famille des techniques de compression LZ. Voir src/backend/utils/adt/pg_lzcompress.c pour les détails.
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) et la taille stockée réelle (différente si la compression a été appliquée). À 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 ; de plus, il désactive l'utilisation d'en-tête sur un octet pour les types varlena. 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.
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.