So beschleunigen Sie die Einfügeleistung in PostgreSQL

215

Ich teste die Postgres-Einfügungsleistung. Ich habe eine Tabelle mit einer Spalte mit der Nummer als Datentyp. Es gibt auch einen Index. Ich habe die Datenbank mit dieser Abfrage gefüllt:

insert into aNumber (id) values (564),(43536),(34560) ...

Mit der obigen Abfrage habe ich sehr schnell 4 Millionen Zeilen auf einmal 10.000 eingefügt. Nachdem die Datenbank 6 Millionen Zeilen erreicht hatte, ging die Leistung alle 15 Minuten drastisch auf 1 Million Zeilen zurück. Gibt es einen Trick, um die Einfügeleistung zu erhöhen? Ich benötige eine optimale Einfügeleistung für dieses Projekt.

Verwenden von Windows 7 Pro auf einem Computer mit 5 GB RAM.

Luke101
quelle
5
Erwähnenswert ist auch Ihre Pg-Version bei Fragen. In diesem Fall macht es keinen großen Unterschied, aber es macht viele Fragen.
Craig Ringer
1
Löschen Sie die Indizes in der Tabelle und lösen Sie gegebenenfalls aus und führen Sie das Einfügeskript aus. Sobald Sie die Massenladung abgeschlossen haben, können Sie die Indizes neu erstellen.
Sandeep

Antworten:

481

Siehe Auffüllen einer Datenbank im PostgreSQL-Handbuch, Depesz 'ausgezeichneter Artikel zum Thema und diese SO-Frage .

(Beachten Sie, dass diese Antwort ist etwa Bulk-Laden von Daten in eine bestehende DB oder ein neues zu erstellen. Wenn Sie Interesse DB Leistung wiederherstellen mit pg_restoreoder psqlAusführung von pg_dumpAusgabe, ein großer Teil davon gilt nicht da pg_dumpund pg_restoreschon Dinge tun , wie die Schaffung Trigger und Indizes nach Abschluss eines Schemas + Datenwiederherstellung) .

Es gibt viel zu tun. Die ideale Lösung wäre, in eine UNLOGGEDTabelle ohne Indizes zu importieren , diese dann in protokolliert zu ändern und die Indizes hinzuzufügen. Leider wird in PostgreSQL 9.4 das Ändern von Tabellen von UNLOGGEDin protokolliert nicht unterstützt . 9.5 fügt hinzu ALTER TABLE ... SET LOGGED, damit Sie dies tun können.

Wenn Sie Ihre Datenbank für den Massenimport offline schalten können, verwenden Sie pg_bulkload.

Andernfalls:

  • Deaktivieren Sie alle Trigger in der Tabelle

  • Löschen Sie die Indizes, bevor Sie mit dem Import beginnen, und erstellen Sie sie anschließend neu. (Das Erstellen eines Index in einem Durchgang dauert viel weniger lange als das schrittweise Hinzufügen derselben Daten, und der resultierende Index ist viel kompakter.)

  • Wenn Sie den Import innerhalb einer einzelnen Transaktion ausführen, können Sie sicher Fremdschlüsseleinschränkungen löschen, den Import durchführen und die Einschränkungen vor dem Festschreiben neu erstellen. Tun Sie dies nicht, wenn der Import auf mehrere Transaktionen aufgeteilt ist, da Sie möglicherweise ungültige Daten einführen.

  • Wenn möglich, verwenden Sie COPYanstelle von INSERTs

  • Wenn Sie nicht verwenden können, sollten Sie COPYmehrwertige INSERTs verwenden, wenn dies praktikabel ist. Sie scheinen dies bereits zu tun. Versuchen Sie jedoch nicht, zu viele Werte in einem einzigen VALUESaufzulisten. Diese Werte müssen ein paar Mal in den Speicher passen. Halten Sie sie daher auf einige Hundert pro Anweisung.

  • Batch Ihre Einfügungen in explizite Transaktionen, wobei Hunderttausende oder Millionen von Einfügungen pro Transaktion ausgeführt werden. Es gibt keine praktische Begrenzung für AFAIK, aber durch Batching können Sie einen Fehler beheben, indem Sie den Start jedes Batches in Ihren Eingabedaten markieren. Wieder scheinen Sie dies bereits zu tun.

  • Verwenden Sie synchronous_commit=offund eine enorme commit_delay, um fsync () Kosten zu reduzieren. Dies hilft jedoch nicht viel, wenn Sie Ihre Arbeit in große Transaktionen zusammengefasst haben.

  • INSERToder COPYparallel von mehreren Verbindungen. Wie viele davon hängen vom Festplattensubsystem Ihrer Hardware ab. Als Faustregel gilt, dass Sie eine Verbindung pro physischer Festplatte benötigen, wenn Sie direkt angeschlossenen Speicher verwenden.

  • Stellen Sie einen hohen checkpoint_segmentsWert ein und aktivieren Sie log_checkpoints. Sehen Sie sich die PostgreSQL-Protokolle an und stellen Sie sicher, dass Sie sich nicht über zu häufig auftretende Checkpoints beschweren.

  • Wenn und nur wenn es Ihnen nichts ausmacht, Ihren gesamten PostgreSQL-Cluster (Ihre Datenbank und alle anderen im selben Cluster) durch katastrophale Beschädigung zu verlieren, wenn das System während des Imports abstürzt, können Sie Pg stoppen, festlegen fsync=off, Pg starten, Ihren Import durchführen, dann (lebenswichtig) Pg stoppen und fsync=onerneut einstellen . Siehe WAL-Konfiguration . Tun Sie dies nicht, wenn sich in einer Datenbank Ihrer PostgreSQL-Installation bereits Daten befinden, die Sie interessieren. Wenn Sie einstellen fsync=off, können Sie auch einstellen full_page_writes=off; Denken Sie auch hier daran, es nach dem Import wieder einzuschalten, um eine Beschädigung der Datenbank und Datenverlust zu vermeiden. Siehe nicht dauerhafte Einstellungen im Pg-Handbuch.

Sie sollten sich auch die Optimierung Ihres Systems ansehen:

  • Verwenden Sie so viel wie möglich hochwertige SSDs für die Speicherung. Gute SSDs mit zuverlässigen, stromgeschützten Rückschreibcaches beschleunigen die Festschreibungsraten erheblich. Sie sind weniger nützlich, wenn Sie den obigen Ratschlägen folgen - was das Löschen der Festplatte / die Anzahl der fsync()s verringert -, können aber dennoch eine große Hilfe sein. Verwenden Sie keine billigen SSDs ohne angemessenen Stromausfallschutz, es sei denn, Sie möchten Ihre Daten nicht aufbewahren.

  • Wenn Sie RAID 5 oder RAID 6 für direkt angeschlossenen Speicher verwenden, beenden Sie jetzt. Sichern Sie Ihre Daten, strukturieren Sie Ihr RAID-Array auf RAID 10 um und versuchen Sie es erneut. RAID 5/6 ist für die Massenschreibleistung hoffnungslos - obwohl ein guter RAID-Controller mit einem großen Cache helfen kann.

  • Wenn Sie die Option haben, einen Hardware-RAID-Controller mit einem großen batteriegepufferten Rückschreibcache zu verwenden, kann dies die Schreibleistung für Workloads mit vielen Commits erheblich verbessern. Es hilft nicht so viel, wenn Sie ein asynchrones Commit mit einem commit_delay verwenden oder wenn Sie während des Massenladens weniger große Transaktionen ausführen.

  • Wenn möglich, speichern Sie WAL ( pg_xlog) auf einer separaten Festplatte / einem separaten Festplattenarray. Es macht wenig Sinn, ein separates Dateisystem auf derselben Festplatte zu verwenden. Menschen entscheiden sich oft dafür, ein RAID1-Paar für WAL zu verwenden. Dies hat wiederum größere Auswirkungen auf Systeme mit hohen Festschreibungsraten und hat nur geringe Auswirkungen, wenn Sie eine nicht protokollierte Tabelle als Datenladeziel verwenden.

Möglicherweise interessieren Sie sich auch für die Optimierung von PostgreSQL für schnelle Tests .

Craig Ringer
quelle
1
Würden Sie zustimmen, dass die Schreibstrafe von RAID 5/6 etwas gemindert wird, wenn SSDs von guter Qualität verwendet werden? Natürlich gibt es immer noch eine Strafe, aber ich denke, der Unterschied ist weitaus weniger schmerzhaft als bei Festplatten.
1
Das habe ich nicht getestet. Ich würde sagen, es ist wahrscheinlich weniger schlimm - die fiesen Schreibverstärkungseffekte und (für kleine Schreibvorgänge) die Notwendigkeit eines Lese-, Änderungs- und Schreibzyklus bestehen weiterhin, aber die schwerwiegende Strafe für übermäßiges Suchen sollte kein Problem sein.
Craig Ringer
Können wir Indizes einfach deaktivieren, anstatt sie zu löschen , indem wir beispielsweise indisvalid( postgresql.org/docs/8.3/static/catalog-pg-index.html ) auf false setzen, dann Daten laden und dann Indizes online schaltenREINDEX ?
Vladislav Rastrusny
1
@CraigRinger Ich habe RAID-5 gegen RAID-10 mit SSDs auf einem Perc H730 getestet. RAID-5 ist tatsächlich schneller. Es könnte auch erwähnenswert sein, dass Einfügen / Transaktionen in Kombination mit großen Bytes schneller zu sein scheinen als Kopieren. Insgesamt aber guter Rat.
Atlaste
2
Hat jemand größere Geschwindigkeitsverbesserungen mit UNLOGGED? Ein schneller Test zeigt eine Verbesserung um 10-20%.
Serg
15

Die Verwendung COPY table TO ... WITH BINARYgemäß der Dokumentation ist " etwas schneller als die Text- und CSV-Formate ". Tun Sie dies nur, wenn Sie Millionen von Zeilen einfügen müssen und wenn Sie mit Binärdaten vertraut sind.

Hier ist ein Beispielrezept in Python, das psycopg2 mit binärer Eingabe verwendet .

Mike T.
quelle
1
Der Binärmodus kann bei einigen Eingaben, z. B. Zeitstempeln, bei denen das Parsen nicht trivial ist, eine große Zeitersparnis bedeuten. Für viele Datentypen bietet es keinen großen Vorteil oder kann aufgrund der erhöhten Bandbreite (z. B. kleine Ganzzahlen) sogar etwas langsamer sein. Guter Punkt, um es zu erhöhen.
Craig Ringer
11

Neben dem hervorragenden Beitrag von Craig Ringer und dem Blog-Beitrag von depesz müssen Sie einige zusätzliche Dinge tun, um Ihre Einfügungen über die ODBC- Schnittstelle ( psqlodbc ) mithilfe von Einfügungen mit vorbereiteten Anweisungen innerhalb einer Transaktion zu beschleunigen schnell arbeiten:

  1. Setzen Sie die Rollback-Stufe für Fehler auf "Transaktion", indem Sie Protocol=-1in der Verbindungszeichenfolge angeben. Standardmäßig verwendet psqlodbc die Ebene "Anweisung", wodurch für jede Anweisung ein SAVEPOINT erstellt wird und nicht für eine gesamte Transaktion, wodurch Einfügungen langsamer werden.
  2. Verwenden Sie serverseitig vorbereitete Anweisungen, indem Sie UseServerSidePrepare=1in der Verbindungszeichenfolge angeben. Ohne diese Option sendet der Client die gesamte Einfügeanweisung zusammen mit jeder eingefügten Zeile.
  3. Deaktivieren Sie die automatische Festschreibung für jede Anweisung mit SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
  4. Nachdem alle Zeilen eingefügt wurden, schreiben Sie die Transaktion mit fest SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);. Es ist nicht erforderlich, eine Transaktion explizit zu öffnen.

Leider "implementiert" psqlodbc, SQLBulkOperationsindem es eine Reihe unvorbereiteter Einfügeanweisungen ausgibt , so dass zum Erreichen der schnellsten Einfügung die obigen Schritte manuell codiert werden müssen.

Maxim Egorushkin
quelle
Eine große Socket-Puffergröße A8=30000000in der Verbindungszeichenfolge sollte auch verwendet werden, um Einfügungen zu beschleunigen.
Andrus
9

Ich habe heute ungefähr 6 Stunden mit dem gleichen Thema verbracht. Die Einfügungen werden mit einer "normalen" Geschwindigkeit (weniger als 3 Sekunden pro 100 KB) bis zu 5MI (von insgesamt 30MI) Zeilen ausgeführt, und dann sinkt die Leistung drastisch (bis auf 1 Minute pro 100K).

Ich werde nicht alle Dinge auflisten, die nicht funktioniert haben und direkt zum Fleisch schneiden.

Ich habe einen Primärschlüssel in der Zieltabelle abgelegt (die eine GUID war), und meine 30MI oder Zeilen flossen glücklich mit einer konstanten Geschwindigkeit von weniger als 3 Sekunden pro 100 KB an ihr Ziel.

Dennis
quelle
6

Wenn Sie zufällig Spalten mit UUIDs einfügen (was nicht genau Ihr Fall ist) und zur @ Tennis- Antwort hinzufügen möchten (ich kann noch keinen Kommentar abgeben), raten Sie, als gen_random_uuid () zu verwenden (erfordert PG 9.4 und das Modul pgcrypto) (a lot) schneller als uuid_generate_v4 ()

=# explain analyze select uuid_generate_v4(),* from generate_series(1,10000);
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=11.674..10304.959 rows=10000 loops=1)
 Planning time: 0.157 ms
 Execution time: 13353.098 ms
(3 filas)

vs.


=# explain analyze select gen_random_uuid(),* from generate_series(1,10000);
                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=252.274..418.137 rows=10000 loops=1)
 Planning time: 0.064 ms
 Execution time: 503.818 ms
(3 filas)

Es ist auch der vorgeschlagene offizielle Weg, dies zu tun

Hinweis

Wenn Sie nur zufällig generierte (Version 4) UUIDs benötigen, sollten Sie stattdessen die Funktion gen_random_uuid () aus dem pgcrypto-Modul verwenden.

Dadurch wurde die Einfügezeit für 3,7 Millionen Zeilen von ~ 2 Stunden auf ~ 10 Minuten gesenkt.

Francisco Reynoso
quelle
1

Deaktivieren Sie für eine optimale Einfügeleistung den Index, wenn dies für Sie eine Option ist. Ansonsten ist auch eine bessere Hardware (Festplatte, Speicher) hilfreich

Ikarus
quelle
-1

Ich habe auch dieses Problem mit der Einfügungsleistung festgestellt. Meine Lösung besteht darin, einige Go-Routinen zu erstellen, um die Einfügearbeiten abzuschließen. In der Zwischenzeit SetMaxOpenConnssollte eine richtige Nummer angegeben werden, da sonst zu viele offene Verbindungsfehler gemeldet werden.

db, _ := sql.open() 
db.SetMaxOpenConns(SOME CONFIG INTEGER NUMBER) 
var wg sync.WaitGroup
for _, query := range queries {
    wg.Add(1)
    go func(msg string) {
        defer wg.Done()
        _, err := db.Exec(msg)
        if err != nil {
            fmt.Println(err)
        }
    }(query)
}
wg.Wait()

Die Ladegeschwindigkeit ist für mein Projekt viel schneller. Dieses Code-Snippet gab nur eine Idee, wie es funktioniert. Leser sollten es leicht ändern können.

Patrick
quelle
Das kann man so sagen. Für meinen Fall reduziert sich die Laufzeit für Millionen von Zeilen von einigen Stunden auf einige Minuten. :)
Patrick