PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

node-postgres mit einer riesigen Menge an Abfragen

AKTUALISIEREN

Diese Antwort wurde inzwischen durch diesen Artikel ersetzt:Datenimporte , der den aktuellsten Ansatz darstellt.

Um Ihr Szenario nachzubilden, habe ich pg-promise verwendet Bibliothek, und ich kann bestätigen, dass es niemals funktionieren wird, es direkt zu versuchen, egal welche Bibliothek Sie verwenden, es ist der Ansatz, der zählt.

Unten ist ein modifizierter Ansatz, bei dem wir Einfügungen in Chunks aufteilen und dann jeden Chunk innerhalb einer Transaktion ausführen, was Lastausgleich (auch bekannt als Drosselung) ist:

function insertRecords(N) {
    return db.tx(function (ctx) {
        var queries = [];
        for (var i = 1; i <= N; i++) {
            queries.push(ctx.none('insert into test(name) values($1)', 'name-' + i));
        }
        return promise.all(queries);
    });
}
function insertAll(idx) {
    if (!idx) {
        idx = 0;
    }
    return insertRecords(100000)
        .then(function () {
            if (idx >= 9) {
                return promise.resolve('SUCCESS');
            } else {
                return insertAll(++idx);
            }
        }, function (reason) {
            return promise.reject(reason);
        });
}
insertAll()
    .then(function (data) {
        console.log(data);
    }, function (reason) {
        console.log(reason);
    })
    .done(function () {
        pgp.end();
    });

Dies erzeugte 1000.000 Datensätze in etwa 4 Minuten und verlangsamte sich nach den ersten 3 Transaktionen dramatisch. Ich habe Node JS 0.10.38 (64-Bit) verwendet, das etwa 340 MB Speicher verbraucht hat. Auf diese Weise haben wir 100.000 Datensätze eingefügt, 10 Mal hintereinander.

Wenn wir dasselbe tun, nur dieses Mal 10.000 Datensätze in 100 Transaktionen einfügen, werden die gleichen 1.000.000 Datensätze in nur 1:25 Sekunden hinzugefügt, ohne Verlangsamung, wobei Node JS etwa 100 MB Speicher verbraucht, was uns sagt, dass das Partitionieren von Daten wie folgt ist sehr gute Idee.

Es spielt keine Rolle, welche Bibliothek Sie verwenden, der Ansatz sollte derselbe sein:

  1. Partitionieren/Drosseln Sie Ihre Einfügungen in mehrere Transaktionen;
  2. Halten Sie die Liste der Einfügungen in einer einzelnen Transaktion bei etwa 10.000 Datensätzen;
  3. Führen Sie alle Ihre Transaktionen in einer synchronen Kette aus.
  4. Verbindung nach dem COMMIT jeder Transaktion wieder zum Pool freigeben.

Wenn Sie gegen eine dieser Regeln verstoßen, ist Ärger garantiert. Wenn Sie beispielsweise gegen Regel 3 verstoßen, wird Ihrem Node JS-Prozess wahrscheinlich sehr schnell der Arbeitsspeicher ausgehen und einen Fehler ausgeben. Regel 4 in meinem Beispiel wurde von der Bibliothek bereitgestellt.

Und wenn Sie diesem Muster folgen, brauchen Sie sich nicht um die Verbindungspooleinstellungen zu kümmern.

AKTUALISIERUNG 1

Spätere Versionen von pg-promise unterstützen solche Szenarien perfekt, wie unten gezeigt:

function factory(index) {
    if (index < 1000000) {
        return this.query('insert into test(name) values($1)', 'name-' + index);
    }
}

db.tx(function () {
    return this.batch([
        this.none('drop table if exists test'),
        this.none('create table test(id serial, name text)'),
        this.sequence(factory), // key method
        this.one('select count(*) from test')
    ]);
})
    .then(function (data) {
        console.log("COUNT:", data[3].count);
    })
    .catch(function (error) {
        console.log("ERROR:", error);
    });

und wenn Sie nichts extra einbauen wollen, wie z. B. Tabellenerstellung, dann sieht es noch einfacher aus:

function factory(index) {
    if (index < 1000000) {
        return this.query('insert into test(name) values($1)', 'name-' + index);
    }
}

db.tx(function () {
    return this.sequence(factory);
})
    .then(function (data) {
        // success;
    })
    .catch(function (error) {
        // error;
    });

Siehe Synchrone Transaktionen für Details.

Mit Bluebird B. die Promise-Bibliothek, dauert es auf meiner Produktionsmaschine 1 43 Minuten, um 1.000.000 Datensätze einzufügen (ohne aktivierte lange Stack-Traces).

Sie hätten nur Ihre factory Methode Rückgabeanforderungen gemäß index , bis du keine mehr hast, ganz einfach.

Und das Beste daran ist, dass dies nicht nur schnell ist, sondern auch Ihren NodeJS-Prozess wenig belastet. Der Speichertestprozess bleibt während des gesamten Tests unter 60 MB und verbraucht nur 7–8 % der CPU-Zeit.

AKTUALISIERUNG 2

Ab Version 1.7.2 pg-promise unterstützt supermassive Transaktionen mit Leichtigkeit. Siehe Kapitel Synchrone Transaktionen .

Beispielsweise könnte ich 10.000.000 Datensätze in einer einzigen Transaktion in nur 15 Minuten auf meinem Heim-PC mit Windows 8.1 64-Bit einfügen.

Für den Test habe ich meinen PC in den Produktionsmodus versetzt und Bluebird verwendet als Verheißungsbibliothek. Während des Tests ging der Speicherverbrauch für den gesamten NodeJS 0.12.5-Prozess (64-Bit) nicht über 75 MB, während meine i7-4770-CPU eine konstante Auslastung von 15 % aufwies.

Das Einfügen von 100 Millionen Datensätzen auf die gleiche Weise würde nur mehr Geduld erfordern, aber nicht mehr Computerressourcen.

In der Zwischenzeit ist der vorherige Test für 1-m-Beilagen von 1:43 auf 1:31 gesunken.

AKTUALISIERUNG 3

Die folgenden Überlegungen können einen großen Unterschied machen:Leistungssteigerung .

AKTUALISIERUNG 4

Verwandte Frage mit einem besseren Implementierungsbeispiel:Massive Inserts with pg-promise .

AKTUALISIERUNG 5

Ein besseres und neueres Beispiel finden Sie hier:nodeJS inserting Data in PostgreSQL-Fehler