Question

J'essaie de créer des index partiels pour une grande table statique (1,2 To) dans Postgres 9.4.

Mes données sont complètement statiques, je peux donc insérer toutes les données, puis créer tous les index.

Dans ce tableau de 1,2 To, j'ai une colonne nommée run_id qui divise proprement les données.Nous avons obtenu d'excellentes performances en créant des index qui couvrent une gamme de run_ids.Voici un exemple :

CREATE INDEX perception_run_frame_idx_run_266_thru_270
ON run.perception
(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

Ces index partiels nous donnent la vitesse de requête souhaitée.Malheureusement, la création de chaque index partiel prend environ 70 minutes.

Il semble que nous soyons limités en CPU (top affiche 100 % pour le processus).
Puis-je faire quelque chose pour accélérer la création de nos index partiels ?

Spécifications du système :

  • Xeon 18 cœurs
  • 192 Go de RAM
  • 12 SSD en RAID
  • Les aspirateurs automatiques sont désactivés
  • maintenance_work_mem :64 Go (trop élevé ?)

Spécifications du tableau :

  • Taille:1,26 To
  • Nombre de rangées:10,537 milliards
  • Taille d'index typique :3,2 Go (il existe une variation d'environ 0,5 Go)

Définition du tableau :

CREATE TABLE run.perception(
id bigint NOT NULL,
run_id bigint NOT NULL,
frame bigint NOT NULL,
by character varying(45) NOT NULL,
by_anyone bigint NOT NULL,
by_me bigint NOT NULL,
by_s_id integer,
owning_p_id bigint NOT NULL,
obj_type_set bigint,
seq integer,
subj_id bigint NOT NULL,
subj_state_frame bigint NOT NULL,
CONSTRAINT perception_pkey PRIMARY KEY (id))

(Ne lisez pas trop les noms des colonnes – je les ai quelque peu obscurcis.)

Informations de fond:

  • Nous avons une équipe distincte sur place qui consomme ces données, mais il n'y a en réalité qu'un ou deux utilisateurs.(Ces données sont toutes générées via une simulation.) Les utilisateurs ne commencent à analyser les données qu'une fois les insertions terminées et les index complètement construits.Notre principale préoccupation est de réduire le temps nécessaire pour générer des données utilisables, et actuellement le goulot d'étranglement est le temps de création des index.
  • La vitesse des requêtes a été tout à fait adéquate lors de l'utilisation de partiels.En fait, je pense que nous pourrions augmenter le nombre d’exécutions couvertes par chaque index, tout en conservant des performances de requête suffisamment bonnes.
  • Je suppose que nous devrons partitionner la table.Nous essayons d’épuiser toutes les autres options avant d’emprunter cette voie.
Était-ce utile?

La solution

Indice BRIN

Disponible depuis Postgres 9.5 et probablement exactement ce que vous recherchez.Création d'index beaucoup plus rapide, beaucoup indice plus petit.Mais les requêtes ne sont généralement pas aussi rapides. Le manuel:

BRIN signifie Block Range Index.BRIN est conçu pour la manutention de grands tableaux dans lesquels certaines colonnes ont une certaine corrélation naturelle avec leur emplacement physique dans la table.UN bloc est un groupe de pages qui sont physiquement adjacentes dans le tableau ;pour chaque , certaines informations récapitulatives sont stockées par l’index.

Continuez à lire, il y a plus.
Depesz a effectué un test préliminaire.

L'optimum pour votre cas :Si vous pouvez écrire des lignes regroupé sur run_id, votre index devient très petit et la création beaucoup moins chère.

CREATE INDEX foo ON run.perception USING brin (run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

Vous pourriez même simplement indexer le table entière.

Disposition du tableau

Quoi que vous fassiez d'autre, vous pouvez économiser 8 octets perdus à cause du remplissage en raison des exigences d'alignement par ligne en triant les colonnes comme ceci :

CREATE TABLE run.perception(
  id               bigint NOT NULL PRIMARY KEY
, run_id           bigint NOT NULL
, frame            bigint NOT NULL
, by_anyone        bigint NOT NULL
, by_me            bigint NOT NULL
, owning_p_id      bigint NOT NULL
, subj_id          bigint NOT NULL
, subj_state_frame bigint NOT NULL
, obj_type_set     bigint
, by_s_id          integer
, seq              integer
, by               varchar(45) NOT NULL -- or just use type text
);

Rend votre table de 79 Go plus petite si aucune des colonnes n'a de valeurs NULL.Détails:

De plus, vous ne disposez que de trois colonnes pouvant être NULL.Le bitmap NULL occupe 8 octets pour 9 à 72 colonnes.Si seulement un entier La colonne est NULL, il existe un cas d'angle pour un paradoxe de stockage :il serait moins coûteux d'utiliser une valeur fictive :4 octets gaspillés mais 8 octets économisés en n'ayant pas besoin d'un bitmap NULL pour la ligne.Plus de détails ici :

Index partiels

En fonction de vos requêtes réelles, il peut être plus efficace d'avoir ces cinq index partiels au lieu de celui ci-dessus :

CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 266;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 267;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 268;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 269;
CREATE INDEX perception_run_id266_idx ON run.perception(frame) WHERE run_id = 270;

Exécutez une transaction pour chacun.

Suppression run_id en tant que colonne d'index, cette méthode permet d'économiser 8 octets par entrée d'index - 32 au lieu de 40 octets par ligne.Chaque index est également moins cher à créer, mais en créer cinq au lieu d'un seul prend beaucoup plus de temps pour une table trop volumineuse pour rester en cache (comme l'ont commenté @Jürgen et @Chris).Cela peut donc vous être utile ou non.

Partitionnement

Basé sur l'héritage - la seule option jusqu'à Postgres 9.5.
(Le nouveau partitionnement déclaratif dans Postgres 11 ou, de préférence, 12 est plus intelligent.)

Le manuel:

Toutes les contraintes sur tous les enfants de la table parent sont examinées lors de l’exclusion de contraintes, de sorte qu’un grand nombre de partitions sont susceptibles de pour augmenter considérablement le temps de planification des requêtes.Ainsi, l’héritage hérité Le partitionnement basé fonctionnera bien avec jusqu'à peut-être une centaine de partitions;n'essayez pas d'utiliser plusieurs milliers de partitions.

C'est moi qui souligne en gras.Par conséquent, estimer 1000 valeurs différentes pour run_id, vous créeriez des partitions couvrant environ 10 valeurs chacune.


maintenance_work_mem

J'ai raté que tu sois déjà en train de t'adapter maintenance_work_mem lors de ma première lecture.Je laisserai une citation et des conseils dans ma réponse pour référence.Par documentation :

maintenance_work_mem (entier)

Spécifie la quantité maximale de mémoire à utiliser par la maintenance opérations, telles que VACUUM, CREATE INDEX, et ALTER TABLE ADD FOREIGN KEY.La valeur par défaut est 64 mégaoctets (64MB).Étant donné qu’un seul de ces opérations peuvent être exécutées à la fois par une session de base de données, et un l’installation n’a normalement pas beaucoup d’entre eux en cours d’exécution simultanément, Il est prudent de définir cette valeur beaucoup plus grande que work_mem.Grandes paramètres peuvent améliorer les performances pour l’aspiration et la restauration vidages de la base de données.

Notez que lorsque autovacuum court, jusqu'à autovacuum_max_workers fois que cette mémoire peut être allouée, alors veillez à ne pas définir la valeur par défaut valeur trop élevée.Il peut être utile de contrôler cela séparément setting autovacuum_work_mem.

Je ne le définirais qu'aussi haut que nécessaire - ce qui dépend de la taille de l'index inconnue (pour nous).Et uniquement localement pour la session en cours d'exécution.Comme l'explique la citation, un niveau trop élevé général Sinon, ce paramètre peut affamer le serveur, car l'autovacuum peut également réclamer plus de RAM.De plus, ne le réglez pas beaucoup plus haut que nécessaire, même pendant la session d'exécution, la RAM libre pourrait être utilisée à bon escient dans la mise en cache des données.

Cela pourrait ressembler à ceci :

BEGIN;

DÉFINIR LOCAL maintenance_work_mem = 10GB;  -- depends on resulting index size

CREATE INDEX perception_run_frame_idx_run_266_thru_270 ON run.perception(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;

COMMIT;

À propos SET LOCAL:

Les effets de SET LOCAL Dernier jusqu'à la fin de la transaction actuelle, qu'il soit engagé ou non.

Pour mesurer la taille d'un objet :

Le serveur doit généralement être configuré de manière raisonnablement différente, évidemment.

Autres conseils

Peut-être que c'est juste trop sophistiqué.Avez-vous réellement essayé d'utiliser un seul index complet ?Les indices partiels couvrant l'ensemble du tableau ne fournissent pas beaucoup de gain, le cas échéant, pour les recherches d'index, et d'après votre texte, je déduis que vous avez des indices pour tous les run_ids ?Les analyses d'index avec des indices partiels peuvent présenter certains avantages, mais je comparerais d'abord la solution simple à un index.

Pour chaque création d'index, vous avez besoin d'une analyse complète des E/S dans la table.Ainsi, la création de plusieurs index partiels nécessite beaucoup plus d'E/S pour lire la table que pour un seul index, bien que le tri se répande sur le disque pour le seul grand index.Si vous insistez sur les index partiels, vous pouvez essayer de construire tous (ou plusieurs) indices en même temps en parallèle (si la mémoire le permet).

Pour une estimation approximative de maintenance_work_mem nécessaire pour trier tous les run_ids, qui sont des bigints de 8 octets, en mémoire, vous auriez besoin de 10,5 * 8 Go + une certaine surcharge.

Vous pouvez également créer les index sur d'autres tablespaces que ceux par défaut.Ces espaces de table peuvent pointer vers des disques qui ne sont pas redondants (il suffit de recréer les index s'ils échouent) ou qui se trouvent sur des baies plus rapides.

Vous pouvez également envisager de partitionner la table en utilisant les mêmes critères que vos index partiels.Cela permettrait la même vitesse que l'index lors de l'interrogation, sans créer d'index du tout.

Licencié sous: CC-BY-SA avec attribution
Non affilié à dba.stackexchange
scroll top