PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

node-postgres con un'enorme quantità di query

AGGIORNAMENTO

Da allora questa risposta è stata sostituita da questo articolo:Importazioni di dati , che rappresenta l'approccio più aggiornato.

Per replicare il tuo scenario ho usato pg-promise libreria e posso confermare che provarlo frontalmente non funzionerà mai, non importa quale libreria utilizzi, è l'approccio che conta.

Di seguito è riportato un approccio modificato in cui partizioniamo gli inserti in blocchi e quindi eseguiamo ogni blocco all'interno di una transazione, che è il bilanciamento del carico (ovvero la limitazione):

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

Ciò ha prodotto 1000.000 record in circa 4 minuti, rallentando drasticamente dopo le prime 3 transazioni. Stavo usando Node JS 0.10.38 (64 bit), che consumava circa 340 MB di memoria. In questo modo abbiamo inserito 100.000 record, 10 volte di seguito.

Se facciamo lo stesso, solo che questa volta inseriamo 10.000 record in 100 transazioni, gli stessi 1.000.000 di record vengono aggiunti in appena 1 m25, senza rallentamenti, con Node JS che consuma circa 100 MB di memoria, il che ci dice che il partizionamento di dati come questo è molto buona idea.

Non importa quale libreria utilizzi, l'approccio dovrebbe essere lo stesso:

  1. Dividi/limita i tuoi inserti in più transazioni;
  2. Mantieni l'elenco degli inserti in una singola transazione a circa 10.000 record;
  3. Esegui tutte le tue transazioni in una catena sincrona.
  4. Rilascia la connessione al pool dopo ogni COMMIT di transazione.

Se infrangi una di queste regole, hai problemi garantiti. Ad esempio, se si infrange la regola 3, è probabile che il processo Node JS esaurisca la memoria molto rapidamente e generi un errore. La regola 4 nel mio esempio è stata fornita dalla biblioteca.

E se segui questo schema, non devi preoccuparti delle impostazioni del pool di connessioni.

AGGIORNAMENTO 1

Versioni successive di pg-promise supportare perfettamente tali scenari, come mostrato di seguito:

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

e se non vuoi includere nulla in più, come la creazione di tabelle, sembra ancora più semplice:

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

Vedi Transazioni sincrone per dettagli.

Usando Bluebird come la libreria delle promesse, ad esempio, ci vuole 1m43s sulla mia macchina di produzione per inserire 1.000.000 di record (senza le tracce lunghe dello stack abilitate).

Avresti solo la tua factory richieste di restituzione del metodo in base all'index , finché non ne hai più nessuno, così semplice.

E la parte migliore, questo non è solo veloce, ma crea anche poco carico sul tuo processo NodeJS. Il processo di test della memoria rimane sotto i 60 MB durante l'intero test, consumando solo il 7-8% del tempo della CPU.

AGGIORNAMENTO 2

A partire dalla versione 1.7.2, pg-promise supporta facilmente transazioni super-massicce. Vedere il capitolo Transazioni sincrone .

Ad esempio, potrei inserire 10.000.000 di record in una singola transazione in soli 15 minuti sul mio PC di casa, con Windows 8.1 a 64 bit.

Per il test ho impostato il mio PC in modalità produzione e ho utilizzato Bluebird come la libreria delle promesse. Durante il test, il consumo di memoria non ha superato i 75 MB per l'intero processo NodeJS 0.12.5 (64 bit), mentre la mia CPU i7-4770 ha mostrato un carico costante del 15%.

L'inserimento di 100 milioni di record allo stesso modo richiederebbe solo più pazienza, ma non più risorse del computer.

Nel frattempo, il test precedente per inserti da 1 m è sceso da 1 m43 a 1 m31.

AGGIORNAMENTO 3

Le seguenti considerazioni possono fare un'enorme differenza:Incremento delle prestazioni .

AGGIORNAMENTO 4

Domanda correlata, con un esempio di implementazione migliore:Inserti massicci con pg-promise .

AGGIORNAMENTO 5

Un esempio migliore e più recente può essere trovato qui:nodeJS inserendo dati nell'errore PostgreSQL