Konfigurieren von PostgreSQL für die Leseleistung

39

Unser System schreibt viele Daten (eine Art Big-Data-System). Die Schreibleistung ist gut genug für unsere Anforderungen, aber die Leseleistung ist wirklich zu langsam.

Die Primärschlüsselstruktur (Constraint) ist für alle unsere Tabellen ähnlich:

timestamp(Timestamp) ; index(smallint) ; key(integer).

Eine Tabelle kann Millionen von Zeilen enthalten, sogar Milliarden von Zeilen, und eine Leseanforderung gilt normalerweise für einen bestimmten Zeitraum (Zeitstempel / Index) und ein bestimmtes Tag. Es ist üblich, eine Abfrage zu haben, die ungefähr 200.000 Zeilen zurückgibt. Gegenwärtig können wir ungefähr 15.000 Zeilen pro Sekunde lesen, aber wir müssen 10-mal schneller sein. Ist das möglich und wenn ja, wie?

Hinweis: PostgreSQL ist im Lieferumfang unserer Software enthalten, sodass sich die Hardware von Client zu Client unterscheidet.

Es ist eine VM, die zum Testen verwendet wird. Der Host der VM ist Windows Server 2008 R2 x64 mit 24,0 GB RAM.

Serverspezifikation (virtuelle Maschine VMWare)

Server 2008 R2 x64
2.00 GB of memory
Intel Xeon W3520 @ 2.67GHz (2 cores)

postgresql.conf Optimierungen

shared_buffers = 512MB (default: 32MB)
effective_cache_size = 1024MB (default: 128MB)
checkpoint_segment = 32 (default: 3)
checkpoint_completion_target = 0.9 (default: 0.5)
default_statistics_target = 1000 (default: 100)
work_mem = 100MB (default: 1MB)
maintainance_work_mem = 256MB (default: 16MB)

Tabellendefinition

CREATE TABLE "AnalogTransition"
(
  "KeyTag" integer NOT NULL,
  "Timestamp" timestamp with time zone NOT NULL,
  "TimestampQuality" smallint,
  "TimestampIndex" smallint NOT NULL,
  "Value" numeric,
  "Quality" boolean,
  "QualityFlags" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag" ),
  CONSTRAINT "FK_AnalogTransition_Tag" FOREIGN KEY ("KeyTag")
      REFERENCES "Tag" ("Key") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE,
  autovacuum_enabled=true
);

Abfrage

Die Ausführung der Abfrage in pgAdmin3 dauert ungefähr 30 Sekunden, wir möchten jedoch, wenn möglich, dasselbe Ergebnis in weniger als 5 Sekunden erzielen.

SELECT 
    "AnalogTransition"."KeyTag", 
    "AnalogTransition"."Timestamp" AT TIME ZONE 'UTC', 
    "AnalogTransition"."TimestampQuality", 
    "AnalogTransition"."TimestampIndex", 
    "AnalogTransition"."Value", 
    "AnalogTransition"."Quality", 
    "AnalogTransition"."QualityFlags", 
    "AnalogTransition"."UpdateTimestamp"
FROM "AnalogTransition"
WHERE "AnalogTransition"."Timestamp" >= '2013-05-16 00:00:00.000' AND "AnalogTransition"."Timestamp" <= '2013-05-17 00:00:00.00' AND ("AnalogTransition"."KeyTag" = 56 OR "AnalogTransition"."KeyTag" = 57 OR "AnalogTransition"."KeyTag" = 58 OR "AnalogTransition"."KeyTag" = 59 OR "AnalogTransition"."KeyTag" = 60)
ORDER BY "AnalogTransition"."Timestamp" DESC, "AnalogTransition"."TimestampIndex" DESC
LIMIT 500000;

Erklären Sie 1

"Limit  (cost=0.00..125668.31 rows=500000 width=33) (actual time=2.193..3241.319 rows=500000 loops=1)"
"  Buffers: shared hit=190147"
"  ->  Index Scan Backward using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..389244.53 rows=1548698 width=33) (actual time=2.187..1893.283 rows=500000 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-16 01:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-16 15:00:00-04'::timestamp with time zone))"
"        Filter: (("KeyTag" = 56) OR ("KeyTag" = 57) OR ("KeyTag" = 58) OR ("KeyTag" = 59) OR ("KeyTag" = 60))"
"        Buffers: shared hit=190147"
"Total runtime: 3863.028 ms"

Erklären Sie 2

In meinem letzten Test hat die Auswahl meiner Daten 7 Minuten gedauert! Siehe unten:

"Limit  (cost=0.00..313554.08 rows=250001 width=35) (actual time=0.040..410721.033 rows=250001 loops=1)"
"  ->  Index Scan using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..971400.46 rows=774511 width=35) (actual time=0.037..410088.960 rows=250001 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-22 20:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-24 20:00:00-04'::timestamp with time zone) AND ("KeyTag" = 16))"
"Total runtime: 411044.175 ms"
JPelletier
quelle

Antworten:

52

Datenausrichtung und Speichergröße

Tatsächlich beträgt der Overhead pro Tupel 24 Byte für den Tupelkopf plus 4 Byte für den Elementzeiger.
Weitere Details in der Berechnung in dieser verwandten Antwort:

Grundlagen der Datenausrichtung und -auffüllung in dieser verwandten Antwort zu SO:

Wir haben drei Spalten für den Primärschlüssel:

PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag")

"Timestamp"      timestamp (8 bytes)
"TimestampIndex" smallint  (2 bytes)
"KeyTag"         integer   (4 bytes)

Ergebnisse in:

 4-Byte-Elementzeiger im Seitenkopf (nicht auf ein Vielfaches von 8 Byte angerechnet)
---
23 Bytes für den Tupelheader
 1-Byte-Auffüllung für Datenausrichtung (oder NULL-Bitmap)
 8 Bytes "Zeitstempel"
 2 Bytes "TimestampIndex"
 2-Byte-Abstand für die Datenausrichtung
 4 Bytes "KeyTag" 
 0 Auffüllen auf das nächste Vielfache von 8 Bytes
-----
44 Bytes pro Tupel

Weitere Informationen zum Messen der Objektgröße finden Sie in dieser Antwort:

Reihenfolge der Spalten in einem mehrspaltigen Index

Lesen Sie diese beiden Fragen und Antworten, um Folgendes zu verstehen:

Die Art und Weise, wie Sie Ihren Index (Primärschlüssel) haben, können Sie Zeilen ohne einen Sortierschritt abrufen, das ist ansprechend, insbesondere mit LIMIT. Aber das Abrufen der Zeilen scheint extrem teuer zu sein.

Im Allgemeinen sollten in einem mehrspaltigen Index "Gleichheits" -Spalten an erster Stelle und "Bereichs" -Spalten an letzter Stelle stehen:

Versuchen Sie daher einen zusätzlichen Index mit umgekehrter Spaltenreihenfolge :

CREATE INDEX analogransition_mult_idx1
   ON "AnalogTransition" ("KeyTag", "TimestampIndex", "Timestamp");

Das hängt von der Datenverteilung ab. Aber mit millions of row, even billion of rowsdieser Macht wesentlich schneller sein.

Die Tupelgröße ist aufgrund der Ausrichtung und Auffüllung der Daten 8 Byte größer. Wenn Sie dies als einfachen Index verwenden, versuchen Sie möglicherweise, die dritte Spalte zu löschen "Timestamp". Möglicherweise etwas schneller oder nicht (da dies beim Sortieren hilfreich sein kann).

Möglicherweise möchten Sie beide Indizes beibehalten. Abhängig von einer Reihe von Faktoren kann Ihr ursprünglicher Index vorzuziehen sein - insbesondere bei einem kleinen LIMIT.

Autovakuum- und Tabellenstatistik

Ihre Tabellenstatistiken müssen aktuell sein. Ich bin sicher, Sie haben ein automatisches Vakuum .

Da Ihre Tabelle sehr umfangreich zu sein scheint und Statistiken für den richtigen Abfrageplan wichtig sind, würde ich das Statistikziel für relevante Spalten erheblich erhöhen :

ALTER TABLE "AnalogTransition" ALTER "Timestamp" SET STATISTICS 1000;

... oder noch höher mit Milliarden von Zeilen. Das Maximum ist 10000, der Standardwert ist 100.

Tun Sie dies für alle Spalten, die an WHEREoder ORDER BYKlauseln beteiligt sind. Dann lauf ANALYZE.

Tabellenlayout

Wenn Sie dabei das anwenden, was Sie über das Ausrichten und Auffüllen von Daten gelernt haben, sollte dieses optimierte Tabellenlayout Speicherplatz sparen und die Leistung ein wenig verbessern (ohne Berücksichtigung von pk und fk):

CREATE TABLE "AnalogTransition"(
  "Timestamp" timestamp with time zone NOT NULL,
  "KeyTag" integer NOT NULL,
  "TimestampIndex" smallint NOT NULL,
  "TimestampQuality" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  "QualityFlags" smallint,
  "Quality" boolean,
  "Value" numeric
);

CLUSTER / pg_repack

Um die Leseleistung für Abfragen zu optimieren, die einen bestimmten Index verwenden (sei es Ihre ursprüngliche oder meine vorgeschlagene Alternative), können Sie die Tabelle in der physischen Reihenfolge des Index neu schreiben. CLUSTERtut dies, aber es ist eher invasiv und erfordert eine exklusive Sperre für die Dauer der Operation. pg_repackist eine raffiniertere Alternative, die dasselbe auch ohne eine exklusive Sperre für den Tisch tun kann.
Dies kann bei großen Tabellen erheblich helfen, da wesentlich weniger Blöcke der Tabelle gelesen werden müssen.

RAM

Im Allgemeinen reichen 2 GB physischer RAM nicht aus, um Milliarden von Zeilen schnell zu verarbeiten. Mehr RAM könnte einen langen Weg gehen - begleitet von einer angepassten Einstellung: offensichtlich eine größere effective_cache_size, um damit zu beginnen.

Erwin Brandstetter
quelle
2
Ich habe nur für KeyTag einen einfachen Index hinzugefügt, der jetzt ziemlich schnell zu sein scheint. Ich werde auch Ihre Empfehlungen zum Datenabgleich anwenden. Vielen Dank!
JPelletier
9

Aus den Plänen geht hervor, dass Ihr Index entweder aufgebläht ist (dann zusammen mit der zugrunde liegenden Tabelle) oder einfach nicht für diese Art von Abfrage geeignet ist (ich habe versucht, dies in meinem letzten Kommentar oben zu beheben).

Eine Zeile des Index enthält 14 Datenbytes (und einige für den Header). Berechnen Sie nun anhand der im Plan angegebenen Zahlen: Sie haben 500.000 Zeilen von 190147 Seiten erhalten - das bedeutet im Durchschnitt weniger als 3 nützliche Zeilen pro Seite, dh etwa 37 Byte pro 8-kb-Seite. Das ist ein sehr schlechtes Verhältnis, nicht wahr? Da die erste Spalte des Index das TimestampFeld ist und in der Abfrage als Bereich verwendet wird, kann und kann der Planer den Index auswählen, um passende Zeilen zu finden. TimestampIndexDie WHEREBedingungen werden jedoch nicht erwähnt , sodass das Filtern nach KeyTagnicht sehr effektiv ist, da diese Werte angeblich zufällig auf den Indexseiten angezeigt werden.

Eine Möglichkeit besteht darin, die Indexdefinition in zu ändern

CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp", "KeyTag", "TimestampIndex")

(oder erstellen Sie diesen Index unter Berücksichtigung der Auslastung Ihres Systems als neuen:

CREATE INDEX CONCURRENTLY "idx_AnalogTransition" 
    ON "AnalogTransition" ("Timestamp", "KeyTag", "TimestampIndex");
  • Dies wird sicher eine Weile dauern, aber Sie können in der Zwischenzeit noch arbeiten.)

Die andere Möglichkeit ist, dass ein großer Teil der Indexseiten von toten Zeilen belegt ist, die durch Staubsaugen entfernt werden könnten. Sie haben die Tabelle mit Einstellung erstellt autovacuum_enabled=true- aber haben Sie jemals mit dem automatischen Staubsaugen begonnen? Oder VACUUMmanuell ausführen ?

dezso
quelle