Manipulação de milhões de registros (otimização de código)

[quote=sergiotaborda]Uma outra opção é usar o padrão Produtor-Consumidor.
A ideia é que uma thread irá ler o arquivo linha por linha. Cada linha será incluida numa fila ( um objeto BlockingQueue).
Um outro conjunto de threads irá ler essa fila. Cada thread irá gravar o item no banco. Se der erro, a linha é enviada para uma outra fila (fila de erros).
No final de ler o arquivo a thread principal espera pela fila ficar vazia e pela threads terminarem o processamento. Verifica se houve erros (linha na fila de erros) e avisa o usuário.
Eu tese ao particionar o trabalho vai mais depressa.
Se quiser pode criar grupos de linhas e por os grupos na fila em vez de linhas individuais. Assim vc pode usar os mecanismo de insersão em batch.

Trabalhar com threads pode ser complexo, então é melhor vc utiliza construtos mais modernos como os Executors, ou fork/join (vem no java 7, mas existe uma lib à parte se quiser usar no java 6)[/quote]

O grande problema de inserir muitos registros em um banco de dados usando a instrução INSERT (mesmo sendo um BATCH INSERT ou um BATCH UPDATE), em vez de usar um utilitário do banco de dados, é que isso costuma ser muito mais lento porque:

a) o INSERT normalmente participa de uma transação (implícita ou explicita) - ou seja, isso cria entradas no “journal” do banco de dados
b) Normalmente o INSERT também atualiza os índices da tabela (ou tabelas, se algum índice envolver “views” materializadas com múltiplas tabelas)

Para acelerar esse processo, normalmente costuma ser muito mais rápido importar os dados com o utilitário do banco, e talvez dropar os índices antes da importação e recriá-los depois da importação - isso depende muito do banco.

E é por isso que é mais simples criar um CSV (ou o formato que o banco exige para importar os dados) - pra começar, é bem mais fácil conferir se os dados que devem ser importados são realmente esses tendo um CSV, e de qualquer maneira, mesmo usando essa arquitetura com threads e o escambau, você precisa saber exatamente o que foi inserido (mesmo que seja um simples arquivo de log), para saber se não houve algum problema na transferência dos dados.

[quote=entanglement][quote=sergiotaborda]Uma outra opção é usar o padrão Produtor-Consumidor.
A ideia é que uma thread irá ler o arquivo linha por linha. Cada linha será incluida numa fila ( um objeto BlockingQueue).
Um outro conjunto de threads irá ler essa fila. Cada thread irá gravar o item no banco. Se der erro, a linha é enviada para uma outra fila (fila de erros).
No final de ler o arquivo a thread principal espera pela fila ficar vazia e pela threads terminarem o processamento. Verifica se houve erros (linha na fila de erros) e avisa o usuário.
Eu tese ao particionar o trabalho vai mais depressa.
Se quiser pode criar grupos de linhas e por os grupos na fila em vez de linhas individuais. Assim vc pode usar os mecanismo de insersão em batch.

Trabalhar com threads pode ser complexo, então é melhor vc utiliza construtos mais modernos como os Executors, ou fork/join (vem no java 7, mas existe uma lib à parte se quiser usar no java 6)[/quote]

O grande problema de inserir muitos registros em um banco de dados usando a instrução INSERT (mesmo sendo um BATCH INSERT ou um BATCH UPDATE), em vez de usar um utilitário do banco de dados, é que isso costuma ser muito mais lento porque:

a) o INSERT normalmente participa de uma transação (implícita ou explicita) - ou seja, isso cria entradas no “journal” do banco de dados
b) Normalmente o INSERT também atualiza os índices da tabela (ou tabelas, se algum índice envolver “views” materializadas com múltiplas tabelas)

Para acelerar esse processo, normalmente costuma ser muito mais rápido importar os dados com o utilitário do banco, e talvez dropar os índices antes da importação e recriá-los depois da importação - isso depende muito do banco.

E é por isso que é mais simples criar um CSV (ou o formato que o banco exige para importar os dados) - pra começar, é bem mais fácil conferir se os dados que devem ser importados são realmente esses tendo um CSV, e de qualquer maneira, mesmo usando essa arquitetura com threads e o escambau, você precisa saber exatamente o que foi inserido (mesmo que seja um simples arquivo de log), para saber se não houve algum problema na transferência dos dados. [/quote]

Gostei muito da solução dada pelo “sergiotaborda” ! mas pelo jeito vou ter que estudar um pouco mais sobre threads :stuck_out_tongue:
mas pelo que disse o “entanglement” a melhor solução seria mesmo, ler o csv, criar outro com as informações que eu quero e ai então importar manualmente esse outro csv para o BD …
sem frescura, simples e fácil

[quote=sergiotaborda]Uma outra opção é usar o padrão Produtor-Consumidor.
A ideia é que uma thread irá ler o arquivo linha por linha. Cada linha será incluida numa fila ( um objeto BlockingQueue).
Um outro conjunto de threads irá ler essa fila. Cada thread irá gravar o item no banco. Se der erro, a linha é enviada para uma outra fila (fila de erros).
No final de ler o arquivo a thread principal espera pela fila ficar vazia e pela threads terminarem o processamento. Verifica se houve erros (linha na fila de erros) e avisa o usuário.
Eu tese ao particionar o trabalho vai mais depressa.
Se quiser pode criar grupos de linhas e por os grupos na fila em vez de linhas individuais. Assim vc pode usar os mecanismo de insersão em batch.

Trabalhar com threads pode ser complexo, então é melhor vc utiliza construtos mais modernos como os Executors, ou fork/join (vem no java 7, mas existe uma lib à parte se quiser usar no java 6)[/quote]

eu ia sugerir justamente isso se a ideia é performance, a ideia é ir inserindo ao mesmo tempo que vai sendo lido, sem o processo leitura aguardar o de inserção na base, de repente seja mais lento do queimportar usando a própria ferramenta do banco de dados, mas seria mais rápido do que criar o arquivo no formato a ser usado para a importação e após o termino disso, importá-lo.
você precisaria provavelmente de um número á ser usado para quantidade de comandos por transação, por exemplo, a cada 1000 comandos dar um commit ou rollback, 1000000, sei la… se for muito, muito grande, vai estourar uma área no banco que eele usa para guardar estas alterações…

se houver algum dba responsável pela base, converse com ele quanto a qual a melhor forma de fazer isso visando desempenho, o que é possível fazer com os indices e coisa assim, se for só uma pq de um campo numérico pro exemplo, nem sei se valeria a pena.

Ok … alguém tem um material para mim dar uma estudada de como implementar a solução (Produtor-Consumidor) dada pelo nosso amigo “sergiotaborda” ?

índio desenvolve em Java agora?

eu ia sugerir justamente isso se a ideia é performance, a ideia é ir inserindo ao mesmo tempo que vai sendo lido, sem o processo leitura aguardar o de inserção na base, de repente seja mais lento do queimportar usando a própria ferramenta do banco de dados, mas seria mais rápido do que criar o arquivo no formato a ser usado para a importação e após o termino disso, importá-lo.
você precisaria provavelmente de um número á ser usado para quantidade de comandos por transação, por exemplo, a cada 1000 comandos dar um commit ou rollback, 1000000, sei la… se for muito, muito grande, vai estourar uma área no banco que eele usa para guardar estas alterações…
[/quote]

A ideia é que a thread consumidora dê commit a cada linha que recebe da queue. Se usar grupos de linhas como falei ( mais o batch ) você pode limitar o numero de comits no batch pelo numero de linhas no grupo.

O conceito de produtor consumidor também pode ser usado se o objetivo for transformar um arquivo CSV em outro. A ideias das threada é a mesma mas funcionaria um pouco diferente.
A thread pordutora iria fazer o mesmo, ler as linhas. As threads consumidoras iriam trasnformas esas linhas em outra informação ( um DTO A que contém os dados do primeiro arquivo, para um DTO B que contém os dado do segundo). A thread consumidora então agiria como produtora de um novo tipo de objeto. Essa nova linha seria enviada para outra queue. Se a linha foi rejeitada por alguma razão, simplesmente não coloca na queue. Uma terceira thread iria ler essa queue e escrever os dados no arquivo final.
A primeira e terceria thread são únicas (uma unica instancia correndo). As threads transformadores podem ser N.

Produtor (Leitor do arquivo) --> Queue A —> { conjunto de threads transformadoras } —> Queue B --> Consumidor final (Escritor do Arquivo)

Este processo em que A vira uma coleção de B e depois é tudo aggreagado em C é mais comum do que parece. hoje em dia é comum chamar este tipo de algoritmos de MapReduce (devido ao nome do algoritmo do google) ou de Fork/join como o framework do java).

Sim, é um pouco avançado isto aqui. Mas é muito mais facil no java 6/7 do que no java 4 :slight_smile: hoje existem classes como Executor, BlockingQueue que escondem o uso de threads

Passar de um CSV para outro, ou até para um script SQL é uma opção, mas ai vc está desenhando uma ferramenta de linha de comandos e não um sistema que tem a inteligencia integrada. às vezes a transformação e filtro.
Contudo, se essa for a escolha o padrão Produtor-Consumidor pode ajudar também.