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

Bom, antes de postar o código, queria deixar claro meu cenário.
Eu tenho um CSV com milhões de registros (aproximadamente 40 milhões) e preciso fazer algumas operações com eles para que eu possa gerar um enorme relatório.
fiz um código que atende minha necessidade, leio o csv e salvo em um BD, apartir dai faço os devidos procedimentos…
porém meu código demora em media 30 min para ler 1 milhão de registros ( para ler os 40 milhões isso iria levar horas !). Queria saber se tem como otimizar mais o código abaixo ou se a unica maneira seria comprar um PC da NASA para fazer o trabalho kkkkkkk

[code]package com.util;

import java.io.BufferedReader;
import java.io.FileReader;
import java.sql.Time;

import com.facade.FacadeConsulta;
import com.model.Consulta;

public class ManipulaArquivo {
private static final String CAMINHO = “teste.txt”;

public void lerArquivo() {  
    try {  
    	FileReader reader = new FileReader(CAMINHO);
		BufferedReader leitor = new BufferedReader(reader, 1 * 512 * 512);
		String linha = null;
    	FacadeConsulta salvadorDeConsultas = new FacadeConsulta();
    	leitor.readLine();
    	salvadorDeConsultas.abreConexao();
        while ((linha = leitor.readLine()) != null) {  
             
            String campos[] = linha.split(";", linha.length());

            Consulta objConsulta = new Consulta(); 
            objConsulta.setBilhetador(campos[0]);
            objConsulta.setOrigem(campos[1]);  //Pulando uma coluna
            objConsulta.setDestino(campos[3]);  //Pulando uma coluna
            objConsulta.setRotaEntrada(campos[5]); 
            objConsulta.setRotaSaida(campos[6]); 
            objConsulta.setFds(campos[7]);  
            objConsulta.setDataHora(campos[8]);  
            objConsulta.setTtc(campos[9]); 
            
            salvadorDeConsultas.salvaConsulta(objConsulta);
            
            //Bloco gerado para fugir do erro de "heap space" do hibernate
            if(contLinha >= 190000){
            	salvadorDeConsultas.commitAndCloseTransaction();
            	salvadorDeConsultas.abreConexao();
            }
            
        }
        salvadorDeConsultas.fechaConexao();
    } catch (Exception e) {  
        throw new RuntimeException("Não foi possível ler o arquivo!", e);  
    }  
    
}  

}[/code]

leva meia hora somente pra persistir os dados?

Se isso for algo que só deve ser executado uma vez, e caso esse banco seja só pra isso, você pode montar um script com os 40 milhões de inserts e rodar direto no banco, e aí trabalha disso pra frente.

Você já não tinha um tópico sobre esse problema? Duplicar tópicos não é muito bem visto.

na verdade, antes meu problema era como ler o arquivo … esse problema já foi resolvido …
meu problema agora é que o código ficou lento ( ou não ), só queria a opinião de pessoas mais experientes para saber se tem como melhorar o código ou não :smiley:

obrigado pela atenção :smiley:

[quote=hamisterbr]na verdade, antes meu problema era como ler o arquivo … esse problema já foi resolvido …
meu problema agora é que o código ficou lento ( ou não ), só queria a opinião de pessoas mais experientes para saber se tem como melhorar o código ou não :smiley:

obrigado pela atenção :smiley: [/quote]
Ah, ok. Entendi.

E quanto à minha pergunta? esse tempo é somente para persistir os dados? A minha sugestão é válida para o seu cenário ?

Bom dia!

Qual o BD que você está usando?

[]'s

[quote=Rodrigo Sasaki][quote=hamisterbr]na verdade, antes meu problema era como ler o arquivo … esse problema já foi resolvido …
meu problema agora é que o código ficou lento ( ou não ), só queria a opinião de pessoas mais experientes para saber se tem como melhorar o código ou não :smiley:

obrigado pela atenção :smiley: [/quote]
Ah, ok. Entendi.

E quanto à minha pergunta? esse tempo é somente para persistir os dados? A minha sugestão é válida para o seu cenário ?[/quote]

O tempo seria pra tudo, ler o dado do csv e persistir no banco de dados.
em relação a sua sugestão, se eu conseguisse fazer um script no MySql que lesse um CSV, acho que seria valida sim, mas meu conhecimento não permite isso no momento :stuck_out_tongue:
vou dar uma estudada a respeito

[quote]Bom dia!

Qual o BD que você está usando?

[]'s[/quote]

estou usando o MySql

Talvez seu problema esteja no momento de inserir os dados, pelo que vi é feito insert a cada iteração, normalmente para este tipo de situação eu crio script, ao invés de salvar direto.
Recentemente precisei ler dados de um excel com muitissimos dados.

fiz assim[code]
public void dados() throws BiffException, IOException {
Workbook workbook = Workbook.getWorkbook(new File("T:\xande\DadosVestibular2005.xls"));
Sheet sheet = workbook.getSheet(0);
StringBuilder sql = new StringBuilder();
PrintWriter pw = new PrintWriter("T:\xande\insert-dados.sql");
try {
int linhas = sheet.getRows();
for (int i = 0; i < linhas; i++) {

			System.out.println(&quot;Iterando linha: &quot; + i);
			
			Cell celulaA = sheet.getCell(0, i);
			Cell celulaB = sheet.getCell(1, i);
			Cell celulaC = sheet.getCell(2, i);
			Cell celulaD = sheet.getCell(3, i);
			Cell celulaE = sheet.getCell(4, i);
			Cell celulaF = sheet.getCell(5, i);
			Cell celulaG = sheet.getCell(6, i);
			Cell celulaH = sheet.getCell(7, i);
			Cell celulaI = sheet.getCell(8, i);
			Cell celulaJ = sheet.getCell(9, i);
			Cell celulaK = sheet.getCell(10, i);
			Cell celulaL = sheet.getCell(11, i);
			Cell celulaM = sheet.getCell(12, i);
			Cell celulaN = sheet.getCell(13, i);
			Cell celulaO = sheet.getCell(14, i);
			Cell celulaP = sheet.getCell(15, i);
			Cell celulaQ = sheet.getCell(16, i);
			
			
			String conteudoA = celulaA.getContents();
			String conteudoB = celulaB.getContents();
			String conteudoC = celulaC.getContents();
			String conteudoD = celulaD.getContents();
			String conteudoE = celulaE.getContents();
			String conteudoF = celulaF.getContents();
			String conteudoG = celulaG.getContents();
			String conteudoH = celulaH.getContents();
			String conteudoI = celulaI.getContents();
			String conteudoJ = celulaJ.getContents();
			String conteudoK = celulaK.getContents();
			String conteudoL = celulaL.getContents();
			String conteudoM = celulaM.getContents();
			String conteudoN = celulaN.getContents();
			String conteudoO = celulaO.getContents();
			String conteudoP = celulaP.getContents();
			String conteudoQ = celulaQ.getContents();

			sql.append(&quot;INSERT INTO DadosVestibular2005(ID, sexo, ano_fim_2_grau, ano_nascimento, acertos_total, nome_curso, nome_area, acertos_biologia, acertos_geografia, acertos_matematica, acertos_lingua_estrangeira, acertos_portugues, acertos_redacao, acertos_fisica, acertos_historia, acertos_quimica, aprovado) values (&quot;);
			sql.append(conteudoA);
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.colocaAspasSimples(conteudoB));
			sql.append(&quot;, &quot;);
			sql.append(conteudoC);
			sql.append(&quot;, &quot;);
			sql.append(conteudoD);
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoE).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.colocaAspasSimples(conteudoF));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.colocaAspasSimples(conteudoG));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoH).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoI).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoJ).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoK).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoL).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoM).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoN).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoO).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.returnValidNumber(conteudoP).replace(&quot;,&quot;, &quot;.&quot;));
			sql.append(&quot;, &quot;);
			sql.append(TextUtils.colocaAspasSimples(conteudoQ));
			sql.append(&quot;);&quot;);
			sql.append(TextUtils.NOVA_LINHA);
		}
		pw.write(sql.toString());
	} finally {
		workbook.close();
		pw.flush();
		pw.close();
	}
}[/code]

Não é complexo, ao invés de você inserir o dado na mão, monte o INSERT e faça o append em um StringBuilder ou grave em um arquivo, algo do tipo, depois execute o script todo.

Bom, na minha humilde opinião …
eu estou tirando a informação de um csv, portanto se for pra mim tirar a informação de um csv e montar outro igual (com alguns campos a menos) só para poder inserir este outro csv de uma vez no BD não seria inviável ?
se eu montar um StringBuilder no java e não montar um outro csv para armazenar as informações não daria erro de estouro de pilha ?. devido ao tamanho dessa string que seria giganteeeeeesca

[quote=hamisterbr]Bom, na minha humilde opinião …
eu estou tirando a informação de um csv, portanto se for pra mim tirar a informação de um csv e montar outro igual (com alguns campos a menos) só para poder inserir este outro csv de uma vez no BD não seria inviável ?
se eu montar um StringBuilder no java e não montar um outro csv para armazenar as informações não daria erro de estouro de pilha ?. devido ao tamanho dessa string que seria giganteeeeeesca [/quote]

Você não vai montar outro CSV, você vai criar um INSERT para cada registro do seu CSV, simplesmente para poder inserir tudo diretamente no banco, e trabalhar daí pra frente.

Se você precisa usar o BD, eu acho que essa solução pode ser viável, cortando uma fase do seu processamento.

E claro, se isso for algo que precisa ser executado mais de uma vez, aí talvez não seja uma boa ideia.

[quote=Rodrigo Sasaki][quote=hamisterbr]Bom, na minha humilde opinião …
eu estou tirando a informação de um csv, portanto se for pra mim tirar a informação de um csv e montar outro igual (com alguns campos a menos) só para poder inserir este outro csv de uma vez no BD não seria inviável ?
se eu montar um StringBuilder no java e não montar um outro csv para armazenar as informações não daria erro de estouro de pilha ?. devido ao tamanho dessa string que seria giganteeeeeesca [/quote]

Você não vai montar outro CSV, você vai criar um INSERT para cada registro do seu CSV, simplesmente para poder inserir tudo diretamente no banco, e trabalhar daí pra frente.

Se você precisa usar o BD, eu acho que essa solução pode ser viável, cortando uma fase do seu processamento.

E claro, se isso for algo que precisa ser executado mais de uma vez, aí talvez não seja uma boa ideia.[/quote]

entendi sua ideia Rodrigo Sasaki, mas o que estou querendo dizer é o seguinte … pra mim criar uma string “INSERT xxxx,xx,xx, INTO xx ;INSERT xxxx,xx,xx, INTO xx ;INSERT xxxx,xx,xx, INTO xx ;” para poder inserir todos dados de uma vez só não iria dar estouro de pilha ( Java heap space ) devido ao tamanho da string ?
esse processo não é mais ou menos o que o hibernate faz com o método persist da interface EntityManager ?
desculpa minha ignorância se estiver falando abobrinha kkkkk

[quote=hamisterbr]entendi sua ideia Rodrigo Sasaki, mas o que estou querendo dizer é o seguinte … pra mim criar uma string “INSERT xxxx,xx,xx, INTO xx ;INSERT xxxx,xx,xx, INTO xx ;INSERT xxxx,xx,xx, INTO xx ;” para poder inserir todos dados de uma vez só não iria dar estouro de pilha ( Java heap space ) devido ao tamanho da string ?
esse processo não é mais ou menos o que o hibernate faz com o método persist da interface EntityManager ?
desculpa minha ignorância se estiver falando abobrinha kkkkk [/quote]

Pode ser que dê sim, ou talvez não.

Mas nesse caso você pode quebrar o arquivo em partes, não precisa fazer tudo de uma vez, desde que execute todas as partes.

Pode tentar quebrar o arquivo em 4, e gerar 4 scripts.

Então tá, vou fazer isso que vc me disse, vou quebrar o arquivo em 4 ( ou mais ) e fazer um INSERT só …
dai posto aqui o resultado ;D

vlw Rodrigo Sasaki pelas dicas !

[quote=hamisterbr]Então tá, vou fazer isso que vc me disse, vou quebrar o arquivo em 4 ( ou mais ) e fazer um INSERT só …
dai posto aqui o resultado ;D

vlw Rodrigo Sasaki pelas dicas ![/quote]

Sem problemas, se tiver mais alguma dúvida é só perguntar. Vou ajudando com o que posso :slight_smile:

Não sei de ferramento para o mysql, mas no sql server eu uso uma ferramento do sql management para importar arquivos grandes. No meu caso importei arquivos com mais de 100 mil registros em poucos segundos, muito mais eficiente do que gerar inserts individuais, mas essa minha proposta é mais manual.

é … neste caso eu teria que abrir um CSV, retirar os dados que preciso e montar outro CSV para fazer o import … isso fica meio inviável para este meu cenário =/

Neste caso não seria melhor fazer o insert em batch? Já pensou no que aconteceria se desse um erro no registro nº 25.000.000?

Só por curiosidade, você leu o tópico todo ?

Qual é o problema de se criar um CSV? É muito fácil, na verdade. Só é um pouco trabalhoso.

Veja se o seu banco requer o formato CSV ou requer algum outro formato - você precisa consultar a documentação do seu banco (procure por “bulk import” ou “bulk insert” ou “import” - não sei que banco você está usando).

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)