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_id
s. 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 ayons un processeur limité ( 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 autovacuums sont désactivés
- maintenance_work_mem: 64 Go (Trop haut?)
Spécifications du tableau:
- Taille: 1,26 To
- Nombre de lignes: 10,537 milliards
- Taille d'index typique: 3,2 Go (il existe une variance de ~ 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 vraiment 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 entièrement 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 d'index.
- La vitesse de requête 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 maintenant 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.
run_id
? Distribué équitablement? Taille de l'index résultant sur le disque? Les données sont statiques, ok. Mais êtes-vous le seul utilisateur?completely static
, que voulez-vous dire par làWe have a separate team onsite that consumes this data
? Indexez-vous simplement la plagerun_id >= 266 AND run_id <= 270
ou la table entière? Quelle est l'espérance de vie de chaque index / combien de requêtes l'utiliseront? Pour combien de valeurs différentesrun_id
? Sonne comme ~ 15 Mio. lignes parrun_id
, ce qui ferait environ 800 valeurs différentes pourrun_id
? Pourquoi sontobj_type_set
,by_s_id
,seq
non défini NOT NULL? Quel pourcentage approximatif des valeurs NULL pour chacun?Réponses:
Indice BRIN
Disponible depuis Postgres 9.5 et probablement exactement ce que vous recherchez. Création d'index beaucoup plus rapide, index beaucoup plus petit. Mais les requêtes ne sont généralement pas aussi rapides. Le manuel:
Lisez la suite, il y a plus.
Depesz a effectué un test préliminaire.
L'optimum pour votre cas: Si vous pouvez écrire des lignes groupées sur
run_id
, votre index devient très faible et la création beaucoup moins cher.Vous pourriez même simplement indexer la table entière .
Disposition de la table
Quoi que vous fassiez d'autre, vous pouvez économiser 8 octets perdus à cause du remplissage en raison des exigences d'alignement par ligne en ordonnant des colonnes comme ceci:
Rend votre table 79 Go plus petite si aucune des colonnes n'a de valeurs NULL. Détails:
En outre, vous ne disposez que de trois colonnes qui peuvent être NULL. Le bitmap NULL occupe 8 octets pour 9 à 72 colonnes. Si une seule colonne entière est NULL, il y a un cas d'angle pour un paradoxe de stockage: il serait moins cher d'utiliser une valeur fictive à la place: 4 octets gaspillés mais 8 octets enregistrés en n'ayant pas besoin d'un bitmap NULL pour la ligne. Plus de détails ici:
Index partiels
Selon vos requêtes réelles, il pourrait être plus efficace d'avoir ces cinq indices partiels au lieu de celui ci-dessus:
Exécutez une transaction pour chacun.
La suppression en
run_id
tant que colonne d'index de cette manière économise 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 grande pour rester dans le cache (comme @ Jürgen et @Chris ont commenté). 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:
Accentuation mienne. Par conséquent, en estimant 1000 valeurs différentes pour
run_id
, vous feriez des partitions s'étendant sur environ 10 valeurs chacune.maintenance_work_mem
J'ai regretté que vous vous ajustiez déjà
maintenance_work_mem
dans ma première lecture. Je vais laisser une citation et des conseils dans ma réponse pour référence. Par documentation:Je le mettrais seulement aussi haut que nécessaire - ce qui dépend de la taille d'index inconnue (pour nous). Et uniquement localement pour la session d'exécution. Comme l'explique la citation, un paramètre général trop élevé peut affamer le serveur autrement, car autovacuum peut également réclamer plus de RAM. De plus, ne le définissez pas beaucoup plus haut que nécessaire, même pendant la session d'exécution, la RAM libre peut être utilisée à bon escient dans la mise en cache des données.
Cela pourrait ressembler à ceci:
À propos de
SET LOCAL
:Pour mesurer la taille des objets:
Le serveur doit généralement être configuré raisonnablement sinon, évidemment.
la source
Peut-être que c'est juste trop conçu. Avez-vous réellement essayé d'utiliser un seul index complet? Les indices partiels couvrant l'ensemble de la table ensemble ne fournissent pas beaucoup de gain, le cas échéant, pour les recherches d'index, et à partir de votre texte, je déduis que vous avez des indices pour tous les run_ids? Il peut y avoir certains avantages à indexer les analyses avec des indices partiels, mais je voudrais tout d'abord comparer la solution simple à un index.
Pour chaque création d'index, vous avez besoin d'une analyse complète liée aux E / S dans la table. Ainsi, la création de plusieurs index partiels nécessite beaucoup plus d'E / S lisant 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 des indices 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 + quelques frais généraux.
la source
Vous pouvez également créer des index sur d'autres espaces disque logiques que ceux par défaut. Ces espaces disque logiques peuvent pointer vers des disques qui ne sont pas redondants (recréez simplement les index s'ils échouent) ou qui se trouvent sur des tableaux 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 la requête, sans réellement créer d'index.
la source