PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

node-postgres avec une quantité massive de requêtes

MISE À JOUR

Cette réponse a depuis été remplacée par cet article :Importations de données , qui représente l'approche la plus récente.

Afin de reproduire votre scénario, j'ai utilisé pg-promise bibliothèque, et je peux confirmer que l'essayer de front ne fonctionnera jamais, quelle que soit la bibliothèque que vous utilisez, c'est l'approche qui compte.

Vous trouverez ci-dessous une approche modifiée dans laquelle nous partitionnons les insertions en blocs, puis exécutons chaque bloc dans une transaction, ce qui correspond à l'équilibrage de charge (c'est-à-dire à la limitation) :

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();
    });

Cela a produit 1 000 000 enregistrements en 4 minutes environ, ralentissant considérablement après les 3 premières transactions. J'utilisais Node JS 0.10.38 (64 bits), qui consommait environ 340 Mo de mémoire. De cette façon, nous avons inséré 100 000 enregistrements, 10 fois de suite.

Si nous faisons de même, seulement cette fois insérons 10 000 enregistrements dans 100 transactions, les mêmes 1 000 000 enregistrements sont ajoutés en seulement 1m25s, sans ralentissement, avec Node JS consommant environ 100 Mo de mémoire, ce qui nous indique que le partitionnement de données comme celui-ci est très bonne idée.

Peu importe la bibliothèque que vous utilisez, l'approche doit être la même :

  1. Partitionner/limiter vos insertions en plusieurs transactions ;
  2. Gardez la liste des insertions dans une seule transaction à environ 10 000 enregistrements ;
  3. Exécutez toutes vos transactions dans une chaîne synchrone.
  4. Relâchez la connexion au pool après le COMMIT de chaque transaction.

Si vous enfreignez l'une de ces règles, vous êtes assuré d'avoir des problèmes. Par exemple, si vous enfreignez la règle 3, votre processus Node JS risque de manquer de mémoire très rapidement et de générer une erreur. La règle 4 dans mon exemple a été fournie par la bibliothèque.

Et si vous suivez ce modèle, vous n'avez pas besoin de vous soucier des paramètres du pool de connexions.

MISE À JOUR 1

Versions ultérieures de pg-promise prennent parfaitement en charge de tels scénarios, comme indiqué ci-dessous :

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);
    });

et si vous ne voulez rien inclure de plus, comme la création d'un tableau, cela semble encore plus simple :

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;
    });

Voir Transactions synchrones pour plus de détails.

Utilisation de Bluebird comme la bibliothèque promise par exemple, il faut 1m43s sur ma machine de production pour insérer 1 000 000 d'enregistrements (sans les longues traces de pile activées).

Vous auriez juste votre factory la méthode renvoie les requêtes selon l'index , jusqu'à ce qu'il ne vous en reste plus, aussi simple que cela.

Et la meilleure partie, ce n'est pas seulement rapide, mais crée également peu de charge sur votre processus NodeJS. Le processus de test de la mémoire reste inférieur à 60 Mo pendant toute la durée du test, ne consommant que 7 à 8 % du temps CPU.

MISE À JOUR 2

À partir de la version 1.7.2, pg-promise prend en charge les transactions super massives avec facilité. Voir le chapitre Transactions synchrones .

Par exemple, je pourrais insérer 10 000 000 enregistrements dans une seule transaction en seulement 15 minutes sur mon PC personnel, avec Windows 8.1 64 bits.

Pour le test, j'ai mis mon PC en mode production et utilisé Bluebird comme bibliothèque de promesses. Pendant le test, la consommation de mémoire n'a pas dépassé 75 Mo pour l'ensemble du processus NodeJS 0.12.5 (64 bits), tandis que mon processeur i7-4770 affichait une charge constante de 15 %.

Insérer des enregistrements de 100 m de la même manière demanderait juste plus de patience, mais pas plus de ressources informatiques.

Entre temps, le test précédent pour les inserts de 1m est passé de 1m43s à 1m31s.

MISE À JOUR 3

Les considérations suivantes peuvent faire une énorme différence :Performance Boost .

MISE À JOUR 4

Question connexe, avec un meilleur exemple d'implémentation :Inserts massifs avec pg-promise .

MISE À JOUR 5

Un exemple meilleur et plus récent peut être trouvé ici :nodeJS inserting Data dans l'erreur PostgreSQL