Postgres Transaction OOM für 100.000 DDL-Anweisungen

7

Wir führen ungefähr 100.000 DDL-Anweisungen in einer einzelnen Transaktion in PostgreSQL aus. Während der Ausführung erhöht sich die Speicherauslastung der jeweiligen Postgres-Verbindung allmählich. Sobald kein Speicher mehr verfügbar ist (von 10 MB auf 2,2 GB bei 3 GB RAM), trifft OOM Killer sie mit 9, was dazu führt, dass Postgres in den Wiederherstellungsmodus wechselt .

BEGIN;

CREATE SCHEMA schema_1;
-- create table stmts - 714
-- alter table add pkey stmts - 714
-- alter table add constraint fkey stmts - 34
-- alter table add unique constraint stmts - 2
-- alter table alter column set default stmts - 9161
-- alter table alter column set not null stmts - 2405
-- alter table add check constraint stmts - 4
-- create unique index stmts - 224
-- create index stmts - 213

CREATE SCHEMA schema_2;
-- same ddl statements as schema_1 upto schema_7
-- ...
-- ...
-- ...
CREATE SCHEMA schema_7;

COMMIT

Einschließlich der Anweisung zum Erstellen eines Schemas sollten ungefähr 94304 DDL-Anweisungen ausgeführt werden.

Gemäß Transactional DDL in PostgreSQL

Wie bei mehreren kommerziellen Wettbewerbern ist eine der fortschrittlicheren Funktionen von PostgreSQL die Fähigkeit, Transaktions-DDL über das Write-Ahead-Protokolldesign durchzuführen. Dieses Design unterstützt das Zurücksetzen auch großer Änderungen an DDL, z. B. der Tabellenerstellung. Sie können nicht von einem Hinzufügen / Löschen in einer Datenbank oder einem Tabellenbereich wiederherstellen, aber alle anderen Katalogvorgänge sind umkehrbar.

Wir haben sogar problemlos ungefähr 35 GB Daten in einer einzigen Transaktion in PostgreSQL importiert. Warum benötigt die Postgres-Verbindung jedoch großen Speicher, wenn Tausende von DDL-Anweisungen in einer einzigen Transaktion ausgeführt werden?

Wir können das Problem vorübergehend beheben, indem wir den Arbeitsspeicher erhöhen oder den Swap zuweisen. Wir können jedoch sagen, dass die Anzahl der Schemaerstellungen in einer einzelnen Transaktion auf 50 bis 60 (ca. 1 Million DDL-Anweisungen) ansteigen kann, was mehr als 100 GB RAM oder Swap erfordern würde was momentan nicht machbar ist.

PostgreSQL-Version: 9.6.10

Gibt es einen Grund, warum das Ausführen vieler DDL-Anweisungen mehr Speicher erfordert, während dml-Anweisungen dies nicht tun? Behandeln nicht beide Transaktionen, indem sie in die zugrunde liegende WAL schreiben? Warum ist es für DLL anders?

Grund für eine einzelne Transaktion

Wir synchronisieren die gesamte Kundendatenbank von Customer Premise (SQL Server) mit Cloud (PostgreSQL). Alle Kunden haben unterschiedliche Datenbanken. Der Prozess ist, dass ganze Daten als CSV von SQL Server generiert und mithilfe von Temp Tables, COPY und ON CONFLICT DO UPDATE in PostgreSQL importiert werden. Während dieses Prozesses behandeln wir jeden Kunden als eine einzelne Datenbank in PG und eine einzelne Datenbank im SQL Server des Kunden als Schemas in der PG-Datenbank des Kunden.

Basierend auf den CSV-Daten werden wir die Schemas dynamisch erstellen und Daten in sie importieren. Gemäß unserem Anwendungsdesign sollten die Daten in PG zu jedem Zeitpunkt streng konsistent sein und es sollten keine Teilschemata / Tabellen / Daten vorhanden sein. Das mussten wir also in einer einzigen Transaktion erreichen. Außerdem synchronisieren wir alle 3 Minuten schrittweise vom Kunden zur Cloud-Datenbank. Die Schemaerstellung kann also entweder in der ersten oder inkrementellen Synchronisierung erfolgen. Die Wahrscheinlichkeit, so viele Schemas in der ersten Synchronisierung selbst zu erstellen, ist jedoch sehr hoch.

Update 1

Durch das Kommentieren der ALTER TABLE ALTER COLUMNAnweisungen wurde die Speichernutzung erheblich reduziert, da jetzt nur noch maximal 300 MB benötigt werden. Müssen diese in den CREATE TABLEAussagen selbst zusammenführen.

Wird das Kernproblem in der Mailingliste von PG Hackers fragen.

Der Codierer
quelle
1
Wird die Anweisung CREATE DATABASE automatisch als Teil des ersten Synchronisierungsprozesses ausgegeben (offensichtlich nicht als Teil derselben Transaktion, da CREATE DATABASEsie nicht innerhalb eines Transaktionsblocks ausgeführt werden kann ) oder wird sie in einem separaten Prozess ausgeführt? Verwandte Frage (möglicherweise eine Umformulierung der vorherigen): Wie wird die Anwendung auf einen neuen Kunden / eine neue Datenbank aufmerksam?
Andriy M
3
Können Sie die Prozedur, mit der die DDL erstellt wird, ändern, um die ALTER COLUMN-Anweisungen zu entfernen, indem Sie die CREATE TABLE-Anweisungen anpassen? Dadurch würden ungefähr 11.5K-Anweisungen nur für das erste Schema entfernt.
Ypercubeᵀᴹ
1
Unabhängig davon können Sie jedes Schema in eine separate Transaktion einfügen.
Ypercubeᵀᴹ
5
Sie können die Anzahl der ddl-Anweisungen erheblich reduzieren, indem Sie viele Klauseln in eine einzige alter table-Anweisung für dieselbe Tabelle packen ...
Erwin Brandstetter
@AndriyM Datenbank erstellen, die in einem separaten Prozess ausgeführt wird. Die Kundenerstellung ist ein separater Prozess. Wir pflegen Kundeninformationen und Verbindungseigenschaften auf verteilte Weise (etcd)
The Coder

Antworten:

4

Eine bessere Idee ist es, SQL Server FDW zu verwenden, das tatsächlich die Logik hat, Microsoft SQL Server in das PostgreSQL-Format zu ziehen (wird beispielsweise Bitzugeordnet Bool). Von diesem Punkt

Dann alle drei Minuten

  • Sie importieren das Fremdschema in last_fetch_schema
  • wenn das last_fetch_schemaanders ist alslocal_schema
    • Sie synchronisieren Schemas neu
  • Sie kopieren alle Daten mit a INSERT INTO ... SELECT ON CONFLICT DO UPDATEund können nur die neuesten Daten auswählen.
  • Sie löschen das Fremdschema last_fetch_schema

Was gewinnen Sie?

  • Beim ersten Laden können Sie einfach verwenden CREATE TABLE local.foo ( LIKE foreign.foo)
  • Sie können Metadatenunterschiede leicht vergleichen
  • CSVs verlieren Typen und lassen Sie auf Dinge schließen, FDW kann Metadatenkataloge lesen.
  • Nur die neuesten Inhalte abzurufen ist sehr einfach, wenn die Zeilen versioniert sind / Sie nicht mehr die gesamte Datenbank senden müssen.
Evan Carroll
quelle
Das war ein guter Vorschlag. Aber nicht jeder SQL Server ist über das Internet zugänglich. Kunden haben keine Einschränkungen bei ausgehenden Verbindungen, aber die meisten Kunden haben Schwierigkeiten beim Konfigurieren eingehender Verbindungen (dies ist nur einer, es gibt auch andere Fälle wie das Erstellen / Löschen / Ändern von Datenbanken / Tabellen / Spalten im Premise-Patch usw.). Aufgrund des Kundenvolumens ist dies für uns nicht realisierbar / skalierbar.
Der Kodierer
Normalerweise machen Sie das mit VPN. Ich habe nur das starke Gefühl, dass Sie den falschen Baum bellen, aber viel Glück damit. Es gibt viele andere Lösungen, die davon abhängen, wie viel Arbeit Sie investieren möchten. =)
Evan Carroll
3

Dieser Kommentar in src / backend / utils / cache / relcache.c scheint relevant zu sein:

    * If we Rebuilt a relcache entry during a transaction then its
    * possible we did that because the TupDesc changed as the result
    * of an ALTER TABLE that ran at less than AccessExclusiveLock.
    * It's possible someone copied that TupDesc, in which case the
    * copy would point to free'd memory. So if we rebuild an entry
    * we keep the TupDesc around until end of transaction, to be safe.
    */
    if (remember_tupdesc)
        RememberToFreeTupleDescAtEOX(relation->rd_att);

Ich verstehe es nicht wirklich, denn wer ist dieser "Jemand", der einen Zeiger haben könnte? Dies ist privater Speicher, kein gemeinsam genutzter Speicher. Wie auch immer, es scheint das Aufblähen zu erklären, da jede 'alter table'-Anweisung in derselben Transaktion eine weitere Kopie von TupDesc ​​für diese Tabelle hinterlässt. Und anscheinend alter tablehinterlässt jede einzelne Aktion eine Kopie , selbst wenn Sie mehrere Aktionen in einer verwenden . Was auch immer die Vorzüge sein mögen, dies erklärt einen großen Teil der Speichernutzung.

Weitere Informationen finden Sie in der Mailliste der pg-Hacker .

jjanes
quelle