Fórum Looping em grande volume de dados #390629

17/11/2010

0

Pessoal,

Eu criei um programa em delphi (delphi 2007) onde uso os componentes IBX (IBQuery) e o banco de dados firebird 2.1 que tem o objetivo de transferir dados de um banco de dados firbird para outro banco de dados firebird.

Por exemplo tenho o BANCO1.fdb de onde preciso tranferir registros de determinadas tabelas para o BANCO2.fdb e apagar os resgistros transferidos do BANCO1.fdb.

Porem estou com perda de desempenho durante o looping que transfere os registros de um banco para o outro.

Se a minha aplicação começa migrando 400 registro/segundo no final termina com 25 registros/segundo.  Estou falando em questão de aproximandamente 15.000.000 de registros.

Porem o BANCO2.FDB que é o desitno, não possui indices, nem trigger nas tabelas muito menos PK, isso já foi feito assim para que não houvesse perda de desempenho ao alimentar índices que causaria uma perda exponecial de velocidade.

Bom, creio que o problema não seja com o banco pois estou commitando os registro pelo menos a cada 500 no banco destino e origem.

O mais engraçado que acontece é que eu tenho várias tabelas a serem exportadas se a primeira tabela terminar á velocidade de 100 regsitros/segundo a proxima começa nessa mesma velocidade, porem isso somente teria lógica se quando eu fechar a abrir o sistema o importação das outras tabelas continuassem com a mesma velocidade de término, no entanto quando fecho e abro o sistema a velocidade volta à rapidez inicial (400 reg/seg) usando o mesmo BANCO2.fdb.

Alguem pode me ajudar dando algumas dicas de como posso evitar essa perda de desempenho no looping? Talves a forma que esteja fazendo está errada. Mas não consegui fazer de outra meneira por enquanto.

Obs.:
      * todas as IBquerys utilizadas estão como unidirecionais.
      * não estou usando IBUpateSQL e sim commando inserts e deletes executados através de IBQuerys

Obrigado.


Command Informatica

Command Informatica

Responder

Posts

17/11/2010

Wilson Junior

Verifique:
  - se alguns objetos que você esteja criando está sendo liberados da memória (FreeAndNil);
  - se algumas consultas estão sendo abertas, utilizadas e FECHADAS.

Espero ter colaborado.
Responder

Gostei + 0

17/11/2010

Command Informatica

Olá Paulista.


Obrigado pelas dicas, porem mesmo liberando todos os objetos que crio o desempenho continua caindo muito. Todas as querys que utilizo eu fecho no final (IBquery1.close).


Eu creio que tenha alguma coisa haver com o fetch  dos registros que vão ocupando a memória, porem já monitorei a memória do computador quando o sistema está em execução e não está se elevando tando para ocorrer perda de desempenho, pois está ficando sempre abaixo da quantidade de memória física que possuo.

Mas muito obrigado.


Se alguem mais tiver como ajudar. Ficarei grato.









Responder

Gostei + 0

17/11/2010

Wilson Junior

Tente utilizar transações para uma certa quantidade de registros (500 como você citou), talvez possa melhorar.

Espero ter colaborado.
Responder

Gostei + 0

17/11/2010

Wilson Junior

Como você está fazendo estes seus loops???
Responder

Gostei + 0

17/11/2010

Command Informatica

Tente utilizar transações para uma certa quantidade de registros (500 como você citou), talvez possa melhorar.

Espero ter colaborado.


Obrigado mais uma vez Paulista, porem não consegui enterder o que você quis dizer acima.


estou postando uma parte do código para você ver. Creio que ficará mais fácil assim.

Agradeço desde já.


procedure TdmArquivoMorto.procArquivarLogs(d_ArquivarAte: TDateTime);
var
//  sera usada para copiar as informações do log corrente para a base do arquivo morto
    qCopia  :TIBQuery;
//  sera usada para apagar o log corrente
    qDelete :TIBQuery;

//  copia o log corrente para a base do arquivo morto
    procedure procCopiaLog;
    var i :Integer;
        campo :TField;
    begin
       qCopia.Close;
       qCopia.SQL.Text :=
          ' INSERT INTO AUX006 (                              '+
          '    LOG_USUARIO,    LOG_SISTEMA,    LOG_PROGRAMA,  '+
          '    LOG_DATA,       LOG_HORA,       LOG_TIPO,      '+
          '    LOG_HISTORICO,  PRG_DESCRICAO,  LOG_ID)        '+
          ' VALUES(                                           '+
          '    :LOG_USUARIO,   :LOG_SISTEMA,   :LOG_PROGRAMA, '+
          '    :LOG_DATA,      :LOG_HORA,      :LOG_TIPO,     '+
          '    :LOG_HISTORICO, :PRG_DESCRICAO, :LOG_ID)       ';

       for i := 0 to qCopia.Params.Count - 1 do
       begin
          campo := qArquivarLogs.FindField(qCopia.Params[i].Name);

          if campo <> nil then
             qCopia.Params[i].Value := campo.Value;
       end;

       qCopia.ExecSQL;
    end;

//  Apaga o log corrente do cperfect
    procedure procApagaLog;
    var i :Integer;
    begin
       qDelete.Close;
       qDelete.SQL.Text := ' DELETE FROM AUX006     '+
                           ' WHERE LOG_ID = :LOG_ID ';
       qDelete.ParamByName('LOG_ID').AsInteger := qArquivarLogs.FieldByName('LOG_ID').AsInteger;
       qDelete.ExecSQL;
    end;
begin
   LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Iniciando arquivamento de Logs (AUX006)');

   qDelete := nil;
   qCopia  := nil;

   //A função criar query dinamica criar uma instância de uma TIBQuery para o banco e transação especificados
   qDelete := CriarQueryDinamica(dmBanco.Banco, Self.TBDCPerfect);
   qCopia  := CriarQueryDinamica(Self.BDArquivoMorto, Self.TBDArquivoMorto);
   try
      ...

      LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Selecionando logs...');


      // qArquivarLogs é unidirecional
      qArquivarLogs.Close;
      qArquivarLogs.SQL.Text :=
                             ' SELECT AUX006.*, SIS003.PRG_DESCRICAO FROM AUX006 '+
                             ' INNER JOIN SIS003 ON LOG_SISTEMA = PRG_SISTEMA    '+
                             '                  AND LOG_PROGRAMA = PRG_PROGRAMA  '+
                             ' WHERE LOG_DATA <= :D_ARQUIVAR_ATE                 ';
      qArquivarLogs.ParamByName('D_ARQUIVAR_ATE').AsDateTime := d_ArquivarAte;
      qArquivarLogs.Open;

      LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Logs selecionados');
      LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Iniciando arquivamento de logs...');

      while not qArquivarLogs.Eof do
      begin
         ...

         try
            procCopiaLog();
            procApagaLog();

            //Realiza um commit a cada 500
            procGravaAlteracoes();
         except
            on E :Exception do
            begin
               //realiza um rollback
               procDesfazerAlteracoes();
               LogProcesso(TLOGP_ERRO,'ARQUIVAR_LOGS',e.Message,dmArquivoMorto.qArquivarLogs);
            end;
         end;

         qArquivarLogs.Next;
      end;

      //Grava o que estiver pendente
      try
         if Self.TBDArquivoMorto.InTransaction then
            Self.TBDArquivoMorto.Commit;

         if Self.TBDCPerfect.InTransaction then
            Self.TBDCPerfect.Commit;
      except
         on e :Exception do
         begin
            procDesfazerAlteracoes();

            LogProcesso(TLOGP_ERRO,'ARQUIVAR_LOGS',e.Message,qArquivarRecados);
         end;
      end;

      LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Arquivamento de logs concluído');
   finally
      qArquivarLogs.Close;
      qCopia.Close;
      qDelete.Close;

      Self.Conectado := false;

      if qDelete <> nil then
         FreeAndNil(qDelete);

      if qCopia <> nil then
         FreeAndNil(qCopia);
   end;
end;

Responder

Gostei + 0

17/11/2010

Command Informatica

Então ninguém tem alguma dica para ajudar?

Agradeço desde já
Responder

Gostei + 0

17/11/2010

Emerson Nascimento

trabalhe com um numero limitado de registros por vez.
outra coisa: mapeie os campos para que não seja necessário efetuar um scan na lista de campos a cada cópia de registro.


procedure TdmArquivoMorto.procArquivarLogs(d_ArquivarAte: TDateTime);
var
//  sera usada para copiar as informações do log corrente para a base do arquivo morto
    qCopia  :TIBQuery;
//  sera usada para apagar o log corrente
    qDelete :TIBQuery;

    aParams: array of integer; // array com o mapeamento dos parâmetros
    nFieldID: integer; // indice do campo chave para evitar a busca com FieldByName
    lMsgAtiva: boolean;

//  copia o log corrente para a base do arquivo morto
    procedure procCopiaLog;
    var i :Integer;
        campo :TField;
    begin
       qCopia.Close;
       qCopia.SQL.Text :=
          ' INSERT INTO AUX006 (                              '+
          '    LOG_USUARIO,    LOG_SISTEMA,    LOG_PROGRAMA,  '+
          '    LOG_DATA,       LOG_HORA,       LOG_TIPO,      '+
          '    LOG_HISTORICO,  PRG_DESCRICAO,  LOG_ID)        '+
          ' VALUES(                                           '+
          '    :LOG_USUARIO,   :LOG_SISTEMA,   :LOG_PROGRAMA, '+
          '    :LOG_DATA,      :LOG_HORA,      :LOG_TIPO,     '+
          '    :LOG_HISTORICO, :PRG_DESCRICAO, :LOG_ID)       ';

       // mapeia os parametros (apenas 1 vez) para evitar o scan
       // efetuado pelo FindField e pelo FieldByName
       if Length(aParams) = 0 then
       begin
          SetLength(aParams, qCopia.Params.Count);
          nFieldID := qArquivarLogs.FieldByName('LOG_ID').Index;

          for i := 0 to High(aParams) do
          begin
             campo := qArquivarLogs.FindField(qCopia.Params[i].Name);
             if campo <> nil then
                aParams[i] := campo.index;
             else
                aParams[i] := -1;
          end;
       end;

       for i := 0 to High(aParams) do
          if aParams[i] > -1 then
             qCopia.Params[i].Value := qArquivarLogs.Fields[aParams[i]].Value;

       qCopia.ExecSQL;
    end;

//  Apaga o log corrente do cperfect
    procedure procApagaLog;
    var i :Integer;
    begin
       qDelete.Close;
       qDelete.SQL.Text := ' DELETE FROM AUX006     '+
                           ' WHERE LOG_ID = :LOG_ID ';
       qDelete.ParamByName('LOG_ID').AsInteger := qArquivarLogs.Fields[nFieldID].AsInteger;
       qDelete.ExecSQL;
    end;
begin
   LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Iniciando arquivamento de Logs (AUX006)');

   qDelete := nil;
   qCopia  := nil;
   lMsgAtiva := False;

   //A função criar query dinamica criar uma instância de uma TIBQuery
   //para o banco e transação especificados
   qDelete := CriarQueryDinamica(dmBanco.Banco, Self.TBDCPerfect);
   qCopia  := CriarQueryDinamica(Self.BDArquivoMorto, Self.TBDArquivoMorto);
   try
      ...

      LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Selecionando logs...');
      SetLength(aParams, 0); // zera o array com o mapeamento dos parâmetros

      while .t.
         // qArquivarLogs é unidirecional
         qArquivarLogs.Close;
         qArquivarLogs.SQL.Text :=
                                ' SELECT FIRST 5000 AUX006.*, SIS003.PRG_DESCRICAO FROM AUX006 '+ // seleciona 5000 registros
                                ' INNER JOIN SIS003 ON LOG_SISTEMA = PRG_SISTEMA    '+
                                '                  AND LOG_PROGRAMA = PRG_PROGRAMA  '+
                                ' WHERE LOG_DATA <= :D_ARQUIVAR_ATE                 ';
         qArquivarLogs.ParamByName('D_ARQUIVAR_ATE').AsDateTime := d_ArquivarAte;
         qArquivarLogs.Open;

         if qArquivarLogs.IsEmpty then
            break;

         if not lMsgAtiva then
         begin
            lMsgAtiva := True;
            LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Logs selecionados');
            LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Iniciando arquivamento de logs...');
         end;

         // faz a gravacao de 5000 (cinco mil) registros por vez
         // para evitar o acumulo de registros na memoria
         while not qArquivarLogs.Eof do
         begin
            ...

            try
               procCopiaLog();
               procApagaLog();

               //Realiza um commit a cada 500
               procGravaAlteracoes();
            except
               on E :Exception do
               begin
                  //realiza um rollback
                  procDesfazerAlteracoes();
                  LogProcesso(TLOGP_ERRO,'ARQUIVAR_LOGS',e.Message,dmArquivoMorto.qArquivarLogs);
               end;
            end;

            qArquivarLogs.Next;
         end;

         // Grava o que estiver pendente
         try
            if Self.TBDArquivoMorto.InTransaction then
               Self.TBDArquivoMorto.Commit;

            if Self.TBDCPerfect.InTransaction then
               Self.TBDCPerfect.Commit;
         except
            on e :Exception do
            begin
               procDesfazerAlteracoes();

               LogProcesso(TLOGP_ERRO,'ARQUIVAR_LOGS',e.Message,qArquivarRecados);
            end;
         end;
      end;

      LogProcesso(TLOGP_INFORMACAO,'INFO_GERAL_ARQUIVAMENTO','Arquivamento de logs concluído');
   finally
      qArquivarLogs.Close;
      qCopia.Close;
      qDelete.Close;

      Self.Conectado := false;

      if qDelete <> nil then
         FreeAndNil(qDelete);

      if qCopia <> nil then
         FreeAndNil(qCopia);
   end;
end;


verifique se isso vai dar certo.

fiz todas as alterações no bloco de notas, pois estou sem o Delphi nesta máquina, então nem sei se a sintaxe está correta.

optei por 5.000 registros por vez. talvez a performance ainda seja boa selecionando 10.000 ou 15.000. faça os testes e veja se esta alteração surte algum efeito e se aumentar o número de registros ainda o torna satisfatório.




Responder

Gostei + 0

18/11/2010

Command Informatica

Emerson, entendi suas idéias.  Vou testar e depois informo os resultados.

Muito obrigado.
Responder

Gostei + 0

18/11/2010

Anderson

Bom dia, não utilizo o IBX, mas já verificou:

CommitRetaining na transação geraria perda de performance.

CachedUpdates pode estar amarrando o sistema.

O Tamanho da Página (Page size) do banco de dados (.fdb) está de acordo com a estrutura e utilização ?

A máquina e o sistema operacional são adequados para esta carga de trabalho ? (Hardware bem dimensionado, servidor dedicado rodando Linux ou Windows Server, ...)

Abraços,

Anderson:.

Responder

Gostei + 0

18/11/2010

Command Informatica

Bom dia Anderson,

Muito obrigado pelas dicas

* Eu não utilizo commitRetaining.

* Nenhuma query que uso está com chached updates

* Já em relação ao page size eu vou verificar, mas não sei qual se enquadraria melhor.

* O Sistema operacional que utilizo é o windows XP professional em uma máquina Intel Core 2 Duo com 2 GB de memória ram, porem esse é o meu ambiente de desenvolvimento. Quando a aplicação for executar será em uma máquina provavelmente com sistema windows server 2008 e com bem mais memória que a minha e processador mais potente, mas tenho como realizar testes no ambiente do cliente.


Responder

Gostei + 0

18/11/2010

Anderson

A alguns anos fiz testes com Delphi + Componentes UIB + Windows  e Kylix (agora finado) + Componentes UIB + Linux.

A máquina estava com dual boot, então o teste foi no mesmo hardware.

Em Windows o sistema ficava sentindo a carga, iniciando um processo de lentidão.

Em Linux o sistema manteve a velocidade constante.

Testei também, em ambos os ambientes (IBX e UIB). O UIB foi mais rápido.

O teste consistia em incluir um pouco mais de 53 milhões de registro. Na época, a performance das máquinas era bem inferior as das atuais e o processo levou um pouco mais de 4 horas em ambiente Linux, no Windows XP já estava em mais de 6 horas (desisti). Talves o resultado seja diferente se usar uma versão Server do Windows.

O UIB (Unified Interbase - http://www.progdigy.com/) é compatível com o Firebird.

O IBX não tem garantias de compatibilidade com o Firebird (principalmente em BLOBs'). Dos livros que tenho, quando surge o assunto IBX, todos tem a mesma opinão: não é recomendável usar por não haver garantia de compatibilidade pelo fabricante.

No livro The Firebird Book: A Reference for Database Developers da Helen Borrie, e em alguns outros livros e fóruns, há relatos de que a performance do Firebird é melhor em sistemas Linux (opinião compartilhada entre usuários, segundo a autora).

Utilizar o componente "com garantia" de compatibilidade pelo fabricante, além de evitar problemas de funcionamento, poderá explorar melhor os recursos (e desempenho), pois levará em conta as peculiaridades do banco de dados acessado.

Poderia (sugestão) testar com outros componentes: UIB, IBDAC ou Zeos. Faça uma aplicação de teste bem simples, para poder validar mais rapidamente qual a melhor solução.

Destes, o IBDAC é pago, mas tem um ciclo de atualizações/correções bem mais frequente que os demais.

O UIB e o Zeos não tem DataSets editáveis (tipo TClientDataSet). Mas para esta tarefa, que requer mais músculos que praticidade, deverão cumprir bem o papel, sem maiores complicações.

Há ainda a questão das versões do Firebird (Classic, SuperServer e agora a SuperClassic).

Em meus testes, ficou assim (é opinião pessoal e cada caso é um caso):

A Classic não é tão rápida quanto a SuperServer, porém é mais estável e mais constante em termos de velocidade.

A SuperServer é mais rápida mas, em meus testes se mostrou mais problemática (estabilidade).

A SuperClassic é mais rápida que a Classic (percepção imediata do usuário) e mantém a estabilidade e performance.

Hoje estou usando o Firebird 2.1 + DbExpress (é prático, mas pode não ser adequado para processamento massivo) em ambiente de produção e desenvolvimento. Com mais alguns testes e algum tempo após o lançamento da versão 2.5.1, se tudo correr conforme o planejado, passaremos para a versão SuperClassic.

Para trabalhar com qualquer banco de dados, é bom estar bem armado (conhecimento é tudo) para evitar/resolver os problemas. Tenho todos estes livros (excelentes, com dicas matadoras - Firebird - SQL - Sistema Operacional - Rotinas para o Usuário e Manutenção - Configurações - Hardware - ...):

- Dominando Firebird – HELEN BORRIE
- Programaçao Cliente/servidor com Firebird – CARLOS A. PEDROSO
- Firebird 2.0 – CARLOS H. CANTU
- Firebird Essencial – CARLOS H. CANTU
- Firebird – Dicas de Segurança – LUIZ PAULO SANTOS


Abraços,

Anderson:.
Responder

Gostei + 0

18/11/2010

Command Informatica

Anderson,

Agradeço imensamente pelas dicas e pelo compartilhamento de seus conhecimentos.

Vou procurar os livros que você sugeriu para aprimorar meus conhecimentos.

Em relação às dicas anteriores eu fiz o que sugeriu (FIRST 5000). Melhorou a performance, porem ainda está caindo. A Valocidade em um teste que fiz começou em torno de 140 registros/segundo. agora após mais de 2 horas de processamento está entorno de 130 registros / segundo. Considerando que estou em 14% então essa velocidade vai cair bem mais até o final.

Suas dicas ajudaram bastantes.


Agradeço novamente.
Responder

Gostei + 0

Utilizamos cookies para fornecer uma melhor experiência para nossos usuários, consulte nossa política de privacidade.

Aceitar