Ich versuche, Teilindizes für eine große statische Tabelle (1,2 TB) in Postgres 9.4 zu erstellen.
Meine Daten sind vollständig statisch, sodass ich alle Daten einfügen und dann alle Indizes erstellen kann.
In dieser 1,2-TB-Tabelle habe ich eine Spalte mit dem Namen run_id
, die die Daten sauber aufteilt . Wir haben eine großartige Leistung erzielt, indem wir Indizes erstellt haben, die einen Bereich von run_id
s abdecken . Hier ist ein Beispiel:
CREATE INDEX perception_run_frame_idx_run_266_thru_270
ON run.perception
(run_id, frame)
WHERE run_id >= 266 AND run_id <= 270;
Diese Teilindizes geben uns die gewünschte Abfragegeschwindigkeit. Leider dauert die Erstellung jedes Teilindex etwa 70 Minuten.
Es sieht so aus, als wären wir CPU-begrenzt ( top
zeigt 100% für den Prozess an).
Kann ich irgendetwas tun, um die Erstellung unserer Teilindizes zu beschleunigen?
Systemspezifikationen:
- 18 Kern Xeon
- 192 GB RAM
- 12 SSDs in RAID
- Autovakuums sind ausgeschaltet
- tenance_work_mem: 64 GB (zu hoch?)
Tabellenspezifikationen:
- Größe: 1,26 TB
- Anzahl der Zeilen: 10.537 Milliarden
- Typische Indexgröße: 3,2 GB (es gibt eine Varianz von ~ 0,5 GB)
Tabellendefinition:
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))
(Lesen Sie nicht zu viel in die Spaltennamen - ich habe sie etwas verschleiert.)
Hintergrundinformation:
- Wir haben ein separates Team vor Ort, das diese Daten verwendet, aber tatsächlich gibt es nur einen oder zwei Benutzer. (Diese Daten werden alle über eine Simulation generiert.) Benutzer beginnen erst mit der Analyse der Daten, wenn die Einfügungen abgeschlossen und die Indizes vollständig erstellt sind. Unser Hauptanliegen ist es, den Zeitaufwand für die Generierung verwendbarer Daten zu reduzieren. Derzeit besteht der Engpass in der Indexerstellungszeit.
- Die Abfragegeschwindigkeit war bei Verwendung von Partials völlig ausreichend. Tatsächlich denke ich, wir könnten die Anzahl der Läufe, die jeder Index abdeckt, erhöhen und dennoch eine ausreichend gute Abfrageleistung aufrechterhalten.
- Ich vermute, dass wir die Tabelle partitionieren müssen. Wir versuchen, alle anderen Optionen auszuschöpfen, bevor wir diesen Weg einschlagen.
run_id
? Gleichmäßig verteilt? Größe des resultierenden Index auf der Festplatte? Daten sind statisch, ok. Aber bist du der einzige Benutzer?completely static
, was meinen Sie dann damitWe have a separate team onsite that consumes this data
? Indizieren Sie nur den Bereichrun_id >= 266 AND run_id <= 270
oder die gesamte Tabelle? Wie hoch ist die Lebenserwartung jedes Index / wie viele Abfragen werden ihn verwenden? Für wie viele verschiedene Werterun_id
? Klingt nach ~ 15 Mio. Zeilen prorun_id
, was würde es rund 800 verschiedene Werte für machenrun_id
? Warumobj_type_set
,by_s_id
,seq
nicht NOT NULL definiert? Welcher grobe Prozentsatz der NULL-Werte für jeden?Antworten:
BRIN-Index
Verfügbar seit Postgres 9.5 und wahrscheinlich genau das, wonach Sie suchen. Viel schnellere Indexerstellung, viel kleinerer Index. Abfragen sind jedoch normalerweise nicht so schnell. Das Handbuch:
Lesen Sie weiter, es gibt noch mehr.
Depesz führte einen Vorversuch durch.
Das Optimum für Ihren Fall: Wenn Sie Zeilen schreiben können geclustert auf
run_id
, wird Ihr Index sehr klein und Schöpfung viel billiger.Sie können sogar einfach die gesamte Tabelle indizieren .
Tabellenlayout
Was auch immer Sie tun, Sie können 8 Bytes sparen, die aufgrund von Ausrichtungsanforderungen pro Zeile durch Auffüllen verloren gehen, indem Sie Spalten wie folgt anordnen:
Verkleinert Ihre Tabelle um 79 GB, wenn keine der Spalten NULL-Werte enthält. Einzelheiten:
Außerdem haben Sie nur drei Spalten, die NULL sein können. Die NULL-Bitmap belegt 8 Bytes für 9 bis 72 Spalten. Wenn nur eine Ganzzahlspalte NULL ist, gibt es einen Eckfall für ein Speicherparadoxon: Es wäre billiger, stattdessen einen Dummy-Wert zu verwenden: 4 Bytes verschwendet, aber 8 Bytes gespeichert, da keine NULL-Bitmap für die Zeile benötigt wird. Weitere Details hier:
Teilindizes
Abhängig von Ihren tatsächlichen Abfragen ist es möglicherweise effizienter, diese fünf Teilindizes anstelle der oben genannten zu verwenden:
Führen Sie jeweils eine Transaktion aus.
Durch Entfernen auf
run_id
diese Weise als Indexspalte werden 8 Byte pro Indexeintrag eingespart - 32 statt 40 Byte pro Zeile. Jeder Index ist auch billiger zu erstellen, aber das Erstellen von fünf statt nur einem Index dauert wesentlich länger, wenn eine Tabelle zu groß ist, um im Cache zu bleiben (wie @ Jürgen und @Chris kommentiert). Das kann also für Sie nützlich sein oder auch nicht.Partitionierung
Basierend auf Vererbung - die einzige Option bis Postgres 9.5.
(Die neue deklarative Partitionierung in Postgres 11 oder vorzugsweise 12 ist intelligenter.)
Das Handbuch:
Meine kühne Betonung. Wenn Sie also 1000 verschiedene Werte für schätzen
run_id
, würden Sie Partitionen erstellen, die jeweils etwa 10 Werte umfassen.maintenance_work_mem
Ich habe vermisst, dass Sie sich bereits
maintenance_work_mem
in meiner ersten Lektüre darauf eingestellt haben. Ich werde Zitat und Rat in meiner Antwort als Referenz hinterlassen. Pro Dokumentation:Ich würde es nur so hoch wie nötig einstellen - was von der unbekannten (für uns) Indexgröße abhängt. Und nur lokal für die ausführende Sitzung. Wie das Zitat erklärt, kann eine zu hohe allgemeine Einstellung den Server ansonsten aushungern lassen, da das Autovakuum möglicherweise auch mehr RAM beansprucht. Stellen Sie es auch nicht viel höher als nötig ein, selbst in der ausgeführten Sitzung kann der freie Arbeitsspeicher beim Zwischenspeichern von Daten gut genutzt werden.
Es könnte so aussehen:
Über
SET LOCAL
:So messen Sie Objektgrößen:
Der Server sollte im Allgemeinen natürlich vernünftigerweise anders konfiguriert werden.
quelle
Vielleicht ist das nur überentwickelt. Haben Sie tatsächlich versucht, einen einzelnen vollständigen Index zu verwenden? Teilindizes, die die gesamte Tabelle zusammen abdecken, bieten, wenn überhaupt, keinen großen Gewinn für Indexsuchen, und aus Ihrem Text schließe ich, dass Sie Indizes für alle run_ids haben? Index-Scans mit Teilindizes können einige Vorteile haben, dennoch würde ich zuerst die einfache Ein-Index-Lösung vergleichen.
Für jede Indexerstellung benötigen Sie einen vollständigen E / A-gebundenen Scan der Tabelle. Das Erstellen mehrerer Teilindizes erfordert daher weitaus mehr E / A-Lesen der Tabelle als für einen einzelnen Index, obwohl die Sortierung für den einzelnen großen Index auf die Festplatte übertragen wird. Wenn Sie auf Teilindizes bestehen, können Sie versuchen, alle (oder mehrere) Indizes gleichzeitig zu erstellen (sofern der Speicher dies zulässt).
Für eine grobe Schätzung von wartung_work_mem, die erforderlich ist, um alle run_ids zu sortieren, die 8-Byte-Bigints sind, benötigen Sie 10,5 * 8 GB + etwas Overhead im Speicher.
quelle
Sie können die Indizes auch für andere als die Standardtabellenbereiche erstellen. Diese Tablespaces können auf Festplatten verweisen, die nicht redundant sind (erstellen Sie die Indizes einfach neu, wenn sie fehlschlagen) oder sich auf schnelleren Arrays befinden.
Sie können die Tabelle auch nach denselben Kriterien wie Ihre Teilindizes partitionieren. Dies würde die gleiche Geschwindigkeit wie der Index bei der Abfrage ermöglichen, ohne überhaupt einen Index zu erstellen.
quelle