web-dev-qa-db-fra.com

Calcul et économie d'espace dans PostgreSQL

J'ai une table en pg comme ça:

CREATE TABLE t (
    a BIGSERIAL NOT NULL,               -- 8 b
    b SMALLINT,                         -- 2 b
    c SMALLINT,                         -- 2 b
    d REAL,                             -- 4 b
    e REAL,                             -- 4 b
    f REAL,                             -- 4 b
    g INTEGER,                          -- 4 b
    h REAL,                             -- 4 b
    i REAL,                             -- 4 b
    j SMALLINT,                         -- 2 b
    k INTEGER,                          -- 4 b
    l INTEGER,                          -- 4 b
    m REAL,                             -- 4 b
    CONSTRAINT a_pkey PRIMARY KEY (a)
);

Ce qui précède ajoute jusqu'à 50 octets par ligne. D'après mon expérience, j'ai besoin d'un autre 40% à 50% pour la surcharge du système, sans même aucun index créé par l'utilisateur pour ce qui précède. Donc, environ 75 octets par ligne. J'aurai beaucoup, beaucoup de lignes dans le tableau, potentiellement plus de 145 milliards de lignes, donc le tableau va pousser 13-14 téraoctets. Quelles astuces, le cas échéant, pourrais-je utiliser pour compacter cette table? Mes idées possibles ci-dessous ...

Convertissez les valeurs real en integer. S'ils peuvent être stockés en tant que smallint, cela représente une économie de 2 octets par champ.

Convertissez les colonnes b .. m en un tableau. Je n'ai pas besoin de rechercher sur ces colonnes, mais je dois pouvoir renvoyer la valeur d'une colonne à la fois. Donc, si j'ai besoin de la colonne g, je pourrais faire quelque chose comme

SELECT a, arr[5] FROM t;

Vais-je économiser de l'espace avec l'option tableau? Y aurait-il une pénalité de vitesse?

D'autres idées?

62
punkish

Je ne vois rien à gagner (et quelque chose à perdre) en stockant plusieurs champs numériques dans un tableau.

Le taille de chaque type numérique est clairement documenté, vous devez simplement utiliser le plus petit type compatible avec la résolution de plage souhaitée; et c'est à peu près tout ce que vous pouvez faire.

Je ne pense pas (mais je ne suis pas sûr) s'il existe une exigence d'alignement d'octets pour les colonnes le long d'une ligne, dans ce cas, un réordonnancement des colonnes pourrait modifier l'espace utilisé - mais je ne pense pas.

BTW, il y a une surcharge fixe par ligne, environ 23 octets .

11
leonbloy

"Colonne Tetris"

En fait, vous pouvez faire quelque chose, mais cela nécessite une compréhension plus approfondie. Le mot clé est padding d'alignement. Chaque type de données a des exigences d'alignement spécifiques .

Vous pouvez minimiser l'espace perdu par le remplissage entre les colonnes en les ordonnant favorablement. L'exemple (extrême) suivant gaspillerait beaucoup d'espace disque physique:

CREATE TABLE t (
    e int2    -- 6 bytes of padding after int2
  , a int8
  , f int2    -- 6 bytes of padding after int2
  , b int8
  , g int2    -- 6 bytes of padding after int2
  , c int8
  , h int2    -- 6 bytes of padding after int2
  , d int8)

Pour enregistrer 24 octets par ligne, utilisez plutôt:

CREATE TABLE t (
    a int8
  , b int8
  , c int8
  , d int8
  , e int2
  , f int2
  , g int2
  , h int2)   -- 4 int2 occupy 8 byte (MAXALIGN), no padding at the end

SQL Fiddle.

En règle générale, si vous placez les colonnes de 8 octets en premier, puis les colonnes de 4 octets, 2 octets et 1 octet en dernier, vous ne pouvez pas vous tromper.

boolean, uuid et quelques autres types n'ont pas besoin de remplissage d'alignement. text, varchar et autres types de "varlena" (longueur variable) nominalement nécessitent un alignement "int" (4 octets sur la plupart des machines). Mais en fait il n'y a pas de remplissage d'alignement au format disque (contrairement à la RAM). J'ai vérifié dans de nombreux tests. Finalement, j'ai trouvé l'explication dans un note dans le code source:

Notez également que nous permettons à l'alignement nominal d'être violé lors du stockage de varlenas "emballés";

Normalement, vous pouvez enregistrer quelques octets par ligne au mieux en jouant "colonne tetris". Rien de tout cela n'est nécessaire dans la plupart des cas. Mais avec des milliards de lignes, cela peut signifier facilement quelques gigaoctets.

Vous pouvez tester la taille réelle de la colonne/ligne avec la fonction pg_column_size() .
Certains types occupent plus d'espace dans RAM que sur disque (format compressé ou "compressé"). Vous pouvez obtenir des résultats plus importants pour les constantes (format RAM) que pour les colonnes de table lorsque vous testez la même valeur (ou ligne de valeurs par rapport à la ligne de table) avec pg_column_size().

Enfin, certains types peuvent être compressés ou "grillés" (stockés hors ligne) ou les deux.

Frais généraux par tuple (ligne)

4 octets par ligne pour le pointeur d'élément - non soumis aux considérations ci-dessus.
Et au moins 24 octets (23 + remplissage) pour l'en-tête Tuple. Le manuel sur la mise en page de la base de données:

Il existe un en-tête de taille fixe (occupant 23 octets sur la plupart des machines), suivi d'un bitmap nul facultatif, d'un champ ID d'objet facultatif et des données utilisateur.

Pour le remplissage entre l'en-tête et les données utilisateur, vous devez connaître MAXALIGN sur votre serveur - généralement 8 octets sur un système d'exploitation 64 bits (ou 4 octets sur un système d'exploitation 32 bits). Si vous n'êtes pas sûr, consultez pg_controldata .

Exécutez ce qui suit dans votre répertoire binaire Postgres pour obtenir une réponse définitive:

./pg_controldata /path/to/my/dbcluster

Le manuel:

Les données utilisateur réelles (colonnes de la ligne) commencent à l'offset indiqué par t_hoff, Qui doit toujours être un multiple de la distance MAXALIGN pour la plate-forme.

Ainsi, vous obtenez généralement l'optimisation du stockage en regroupant les données par multiples de 8 octets.

Il n'y a rien à gagner dans l'exemple que vous avez publié. Il est déjà bien emballé. 2 octets de remplissage après le dernier int2, 4 octets à la fin. Vous pouvez consolider le remplissage à 6 octets à la fin, ce qui ne changerait rien.

Frais généraux par page de données

La taille de la page de données est généralement de 8 Ko. Quelques frais généraux/ballonnements à ce niveau aussi: des restes pas assez grands pour contenir un autre tuple, et plus important encore des lignes mortes ou un pourcentage réservé avec le paramètre FILLFACTOR .

Il y a quelques autres facteurs à prendre en compte pour la taille sur le disque:

Types de tableaux?

Avec un type de tableau comme vous l'avez évalué, vous ajouteriez 24 octets de surcharge pour le type. De plus, les éléments du tableau occupent l'espace comme d'habitude. Rien à y gagner.

174
Erwin Brandstetter