PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

node-postgres com grande quantidade de consultas


ATUALIZAÇÃO

Esta resposta foi substituída por este artigo:Importações de dados , que representa a abordagem mais atualizada.

Para replicar seu cenário, usei pg-promise biblioteca, e posso confirmar que tentar de frente nunca funcionará, não importa qual biblioteca você use, é a abordagem que importa.

Abaixo está uma abordagem modificada em que particionamos inserções em partes e, em seguida, executamos cada parte dentro de uma transação, que é o balanceamento de carga (também conhecido como limitação):
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();
    });

Isso produziu 1.000.000 registros em cerca de 4 minutos, diminuindo drasticamente após as primeiras 3 transações. Eu estava usando o Node JS 0.10.38 (64 bits), que consumia cerca de 340 MB de memória. Dessa forma, inserimos 100.000 registros, 10 vezes seguidas.

Se fizermos o mesmo, só que desta vez inserir 10.000 registros dentro de 100 transações, os mesmos 1.000.000 registros são adicionados em apenas 1m25s, sem desaceleração, com o Node JS consumindo cerca de 100MB de memória, o que nos diz que particionar dados como este é muito boa ideia.

Não importa qual biblioteca você usa, a abordagem deve ser a mesma:
  1. Particione/acelere suas inserções em várias transações;
  2. Mantenha a lista de inserções em uma única transação com cerca de 10.000 registros;
  3. Execute todas as suas transações em uma cadeia síncrona.
  4. Libere a conexão de volta ao pool após o COMMIT de cada transação.

Se você quebrar qualquer uma dessas regras, você terá problemas garantidos. Por exemplo, se você quebrar a regra 3, seu processo Node JS provavelmente ficará sem memória rapidamente e gerará um erro. A regra 4 no meu exemplo foi fornecida pela biblioteca.

E se você seguir esse padrão, não precisará se preocupar com as configurações do pool de conexões.

ATUALIZAÇÃO 1

Versões posteriores de pg-promise suporta perfeitamente tais cenários, conforme mostrado abaixo:
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 você não quiser incluir nada extra, como criação de tabelas, parece ainda mais simples:
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;
    });

Consulte Transações síncronas para detalhes.

Usando o Bluebird como a biblioteca de promessas, por exemplo, leva 1m43s na minha máquina de produção para inserir 1.000.000 registros (sem rastreamentos de pilha longos ativados).

Você teria apenas sua factory solicitações de retorno do método de acordo com o index , até que você não tenha mais nenhum, simples assim.

E a melhor parte, isso não é apenas rápido, mas também cria pouca carga em seu processo NodeJS. O processo de teste de memória permanece abaixo de 60 MB durante todo o teste, consumindo apenas 7-8% do tempo da CPU.

ATUALIZAÇÃO 2

A partir da versão 1.7.2, pg-promise suporta transações super-massivas com facilidade. Consulte o capítulo Transações síncronas .

Por exemplo, eu poderia inserir 10.000.000 de registros em uma única transação em apenas 15 minutos no meu PC doméstico, com Windows 8.1 de 64 bits.

Para o teste, configurei meu PC para o modo de produção e usei Bluebird como a biblioteca de promessas. Durante o teste, o consumo de memória não ultrapassou 75 MB para todo o processo NodeJS 0.12.5 (64 bits), enquanto minha CPU i7-4770 mostrou uma carga consistente de 15%.

Inserir registros de 100m da mesma maneira exigiria apenas mais paciência, mas não mais recursos do computador.

Enquanto isso, o teste anterior para inserções de 1m caiu de 1m43s para 1m31s.

ATUALIZAÇÃO 3

As seguintes considerações podem fazer uma grande diferença:Aumento de desempenho .

ATUALIZAÇÃO 4

Pergunta relacionada, com um exemplo de implementação melhor:Inserções maciças com pg-promise .

ATUALIZAÇÃO 5

Um exemplo melhor e mais recente pode ser encontrado aqui:nodeJS inserting Data no erro do PostgreSQL