Manipulação de milhares de registros (até 500 mil) - Soluções / Alternativas

Venho pedir aqui a ajuda do pessoal na seguinte necessidade. Temos uma funcionalidade no sistema que permite cruzamento de dados e que gera querys dinâmicas no banco, para gerar seleções de dados. Estas seleções são utilizadas em sistemas de terceiros e muitas vezes são exportadas a partir do nosso sistema. Estas seleções podem chegar a gerar números absurdos como 500 mil registros. Os registros são exibidos para seleção do usuário (pois tem casos que o usuário precisa de registros especificos) antes das exportações (que podem ser através de csv ou relatórios jasper).
O problema que encontramos é que como as seleções são muito dinâmicas as vezes demoram minutos para executar no banco de dados. Esta espera em um primeiro momento não é problema, porém como os dados devem ser paginados para exibir na tela, esta consulta demorar a cada paginação acaba sendo um problema.
Os sistemas antigos da empresa utilizavam a técnica de rodar estes selects dentro de uma tabela “fisica temporária” (realizando uma especia de SELECT INTO tb_tmp_nomedousuario) e depois trabalhavam com essa tabela, pois os dados já estavam prontos.
O problema desta primeira solução é que gera muito lixo no banco de dados, causando perda de performance e até mesmo aconteceu de acabar com o espaço em disco no servidor.

Na nova solução, a alternativa foi gravar esta consulta serializada na máquina do usuário e navegar nos objetos serializados. Como o sistema é desktop, ao realizar a consulta mostra-se uma barra de progresso fazendo o usuário aguardar e grava os dados prontos serializando os objetos em um arquivo temporário da máquina. Salve-se outro objeto com as referências para cada objeto. Utiliza-se o RandomAccessFile juntamente do seek nestas referências para fazer a paginação. Ao realizar uma nova consulta apaga-se o arquivo temporário anterior. Cada consulta destas de 500 mil registros consome em torno de 200MB da estação de trabalho, porém o consumo de memória é baixissimo e após realizar a primeira consulta, não é preciso voltar ao banco de dados para cada nova paginação.

Esta funcionando bem, mas gostaria de saber opniões das pessoas a respeito da primeira solução que foi adotada no sistema antigo. Desta nova solução. Os prós e contras. Se conseguem enxergar riscos no processo implementado desta forma. E o principal: Se existe outra alternativa.

Obrigado

Concordo com a abordagem de serializar os resultados da consulta no cliente, dessa maneira você poupa o servidor de uma carga de trabalho significativa. Algumas estratégias para você analisar:

  • Há como acelerar as consultas criando índices secundários nas tabelas envolvidas?
  • Há como condensar todos os resultados possíveis dessa consulta numa única tabela de resumo periodicamente (digamos, uma ou duas vezes por dia), e consultar essa tabela de resumo em vez das tabelas de origem? (Essa tabela de resumo também é forte candidata a ganhar alguns índices secundários).
  • Você já estudou soluções de Data Warehouse e Business Intelligence? O forte delas é justamente o processamento eficiente de grandes volumes de dados.

Eu gostei da tua solução.

Pra adicionar as funcionalidades do sql no desktop, vc poderia usar um embedded DB como Derby ou Hsqldb. Assim vc evitaria de usar um arquivo e teria todas as funcionalidades do sql como “sort”, "select’, “group” etc.

Explica em duas frases no máximo. Simplicity is gold. Objetividade também.

O problema é que vc precisa paginar 500 mil registros que estão no banco e a cada página da sua paginação vc carrega esses 500 mil registros de novo ???

[quote=roger_rf]Concordo com a abordagem de serializar os resultados da consulta no cliente, dessa maneira você poupa o servidor de uma carga de trabalho significativa. Algumas estratégias para você analisar:

  • Há como acelerar as consultas criando índices secundários nas tabelas envolvidas?
  • Há como condensar todos os resultados possíveis dessa consulta numa única tabela de resumo periodicamente (digamos, uma ou duas vezes por dia), e consultar essa tabela de resumo em vez das tabelas de origem? (Essa tabela de resumo também é forte candidata a ganhar alguns índices secundários).
  • Você já estudou soluções de Data Warehouse e Business Intelligence? O forte delas é justamente o processamento eficiente de grandes volumes de dados.[/quote]

Ola obrigado pela colaboração.

  • As tabelas já estão com indices, pelo plano de execução exibido pelo SQL Server não se tem mais como otimizar (pelo menos em muitos casos que estão com gargalo).
  • Não tem como trocar o servidor (infelizmente o mesmo é compartilhado com varias outras bases e usuários)
  • Neste caso especifico o resultado tem de ser quente. Tem outros casos que já utilizamos técnicas de consolidação de dados até mesmo em outros servidores.

[quote=tveronezi]Eu gostei da tua solução.

Pra adicionar as funcionalidades do sql no desktop, vc poderia usar um embedded DB como Derby ou Hsqldb. Assim vc evitaria de usar um arquivo e teria todas as funcionalidades do sql como “sort”, "select’, “group” etc. [/quote]

Então amigo… tentei fazer isso, exatamente com o Deby e HSQLDB mas volumes acima de 50 mil registros acabaram gastando muito mais espaço em disco e memória e tiveram péssima performance. Mas acho que para alguns casos é uma solução bem interessante… é que esse caso especifico é meio bizarro, mas 500 mil registros é o pior caso… a maior parte estamos falando de universos de poucos registros (na casa dos 1000) e talvez até pudesse atender com o embedded.

É que estamos tentando adotar uma solução que atenda todas as necessidades de seleção do usuário. Tem outros casos que temos outros relatórios e outras formas de obter dados, mas neste especifico para comunicação pode gerar algumas coisas malucas mesmo.

[quote=saoj]Explica em duas frases no máximo. Simplicity is gold. Objetividade também.

O problema é que vc precisa paginar 500 mil registros que estão no banco e a cada página da sua paginação vc carrega esses 500 mil registros de novo ???
[/quote]

O problema é que o sistema é SQL Server 2000 e não há uma forma eficaz de realizar a paginação (se fosse mysql o limit funcionaria bem melhor). e mesmo retornando apenas a quantidade de registros de cada página (no caso 1000 registros), a consulta demora demais (parece que internamente o sql de alguma forma esta trabalhando com estes 500 mil).

Voltar os 500 mil e exibir para o usuário é necessário pois mesmo ele exportando e carregando em outro sistema, é um processo demorado e como o arquivo é grande ele tem muito trabalho de abrir em um Excel da vida, então quer dar uma visualizada nesses dados antes de fazer a exportação (visualizada superficial mas é uma necessidade).

Essas seleções geralmente são utilizadas para comunicaçõe segmentadas (que são pagas por quem as solicita).

500 mil registros é o pior caso… a maior parte delas não passa de mil registros e funciona bem. É que quisemos adotar uma solução que atendesse de qualquer forma com baixo consumo de recursos e até funcionou… só não sei se foi uma boa!!! (muitas vezes o simplesmente funcionar pode não ser uma boa idéia).

Obrigado

Minhas observações rápidas:

  • SQL sem limit é inviável.

  • Paginação se faz carregando uma lista de objetos magros, e não objetos gordos.

  • Paginação com mais de 200 itens me parece overkill pois nenhum humano vai checar mais do que 200.

  • Se o cara não achar o que ele quer em 200 itens, então ele precisa fazer uma busca/filtro para limitar o resultset.

  • Uma query para retornar 200 itens não tem como ser lenta, a não ser que tenha problema de índices. Aí é uma questão de tunning.

  • Vc só deve fazer a query uma única vez e na primeira página. Depois cacheia ela em algum lugar enquanto o cara clica nas setinhas de próxima e anterior.

[quote=saoj]Minhas observações rápidas:

  • SQL sem limit é inviável.

  • Paginação se faz carregando uma lista de objetos magros, e não objetos gordos.

  • Paginação com mais de 200 itens me parece overkill pois nenhum humano vai checar mais do que 200.

  • Se o cara não achar o que ele quer em 200 itens, então ele precisa fazer uma busca/filtro para limitar o resultset.

  • Uma query para retornar 200 itens não tem como ser lenta, a não ser que tenha problema de índices. Aí é uma questão de tunning.

[/quote]

OK… o problema aqui é que ele precisa imprimir essas 500 mil etiquetas (pior caso). Ou usar o arquivo para exportar para algum outro sistema (de e-mail marketing por exemplo). É uma espécie de extração de dados. Quando ele precisa de uma informação especifica ele utiliza outras funcionalidades do sistema que entram exatamente no que você falou acima.

Se a cada página o sistema ir até o banco de dados, ele retorna somente a quantidade de registros daquela página. O problema aqui acho que é o SQL Server que não é inteligente para fazer esta paginação com algumas consultas muito complexas. Entretanto como vamos precisar dos registros depois, fazer essa extração antes não é tanto problema assim.

Só que ele quer dar uma “olhada” antes no listão. (Para ver alguns registros que o filtro esta retornando e talvez dar uma mexida nos filtros). Por isso precisa mostrar de alguma forma na tela ao invés de sair exportando direto. Além do que dependendo do arquivo exportado, ele não consegue abrir este em nenhuma outra ferramenta de tão grande que fica. Só consegue fazer o upload pro e-mail marketing ou imprimir as etiquetas (pelo relatório jasper do próprio sistema e isso demora).

O ideal mesmo era usar um outro banco de dados para realizar estas seleções, mas o usuário quer “a base quente”. Ou até mesmo agendar estes processos de extração.

Ou seja, nesse caso… é realmente necessário extrair essa quantidade monstro de registros… se eu conseguisse paginar de forma eficiente no sql server, deixaria a extração completa para quando ele efetivamente fosse exportar. Entretanto ler os 500 mil registros e serializar esta levando quase o mesmo tempo que navegar em cada uma das páginas. Acho que é pq a query é muito complexa e o sql server esta gerando o resultado em areas temporárias para depois efetivamente pegar só os registros da página.

[quote=saoj]Minhas observações rápidas:

  • SQL sem limit é inviável.

  • Paginação se faz carregando uma lista de objetos magros, e não objetos gordos.

  • Paginação com mais de 200 itens me parece overkill pois nenhum humano vai checar mais do que 200.

  • Se o cara não achar o que ele quer em 200 itens, então ele precisa fazer uma busca/filtro para limitar o resultset.

  • Uma query para retornar 200 itens não tem como ser lenta, a não ser que tenha problema de índices. Aí é uma questão de tunning.

  • Vc só deve fazer a query uma única vez e na primeira página. Depois cacheia ela em algum lugar enquanto o cara clica nas setinhas de próxima e anterior.

[/quote]

Estava acompanhando o tópico, e quando li isso

fiquei meio intrigado. Como faria isso? Nunca pensei nisso e não tenho idéia da melhor maneira de fazer :lol:

Não entendi. Como posso fazer este cache ?

[quote=jmmenezes][quote=saoj]

  • Vc só deve fazer a query uma única vez e na primeira página. Depois cacheia ela em algum lugar enquanto o cara clica nas setinhas de próxima e anterior.

[/quote]

Não entendi. Como posso fazer este cache ?[/quote]

Até onde entendi da história, foi exatamente isso que você fez.

O que está usando como cache, é esse arquivo na máquina do usuário.

[quote=jmmenezes][quote=saoj]Minhas observações rápidas:

  • SQL sem limit é inviável.

  • Paginação se faz carregando uma lista de objetos magros, e não objetos gordos.

  • Paginação com mais de 200 itens me parece overkill pois nenhum humano vai checar mais do que 200.

  • Se o cara não achar o que ele quer em 200 itens, então ele precisa fazer uma busca/filtro para limitar o resultset.

  • Uma query para retornar 200 itens não tem como ser lenta, a não ser que tenha problema de índices. Aí é uma questão de tunning.

[/quote]

OK… o problema aqui é que ele precisa imprimir essas 500 mil etiquetas (pior caso). Ou usar o arquivo para exportar para algum outro sistema (de e-mail marketing por exemplo). É uma espécie de extração de dados. Quando ele precisa de uma informação especifica ele utiliza outras funcionalidades do sistema que entram exatamente no que você falou acima.

Se a cada página o sistema ir até o banco de dados, ele retorna somente a quantidade de registros daquela página. O problema aqui acho que é o SQL Server que não é inteligente para fazer esta paginação com algumas consultas muito complexas. Entretanto como vamos precisar dos registros depois, fazer essa extração antes não é tanto problema assim.

Só que ele quer dar uma “olhada” antes no listão. (Para ver alguns registros que o filtro esta retornando e talvez dar uma mexida nos filtros). Por isso precisa mostrar de alguma forma na tela ao invés de sair exportando direto. Além do que dependendo do arquivo exportado, ele não consegue abrir este em nenhuma outra ferramenta de tão grande que fica. Só consegue fazer o upload pro e-mail marketing ou imprimir as etiquetas (pelo relatório jasper do próprio sistema e isso demora).

O ideal mesmo era usar um outro banco de dados para realizar estas seleções, mas o usuário quer “a base quente”. Ou até mesmo agendar estes processos de extração.

Ou seja, nesse caso… é realmente necessário extrair essa quantidade monstro de registros… se eu conseguisse paginar de forma eficiente no sql server, deixaria a extração completa para quando ele efetivamente fosse exportar. Entretanto ler os 500 mil registros e serializar esta levando quase o mesmo tempo que navegar em cada uma das páginas. Acho que é pq a query é muito complexa e o sql server esta gerando o resultado em areas temporárias para depois efetivamente pegar só os registros da página.
[/quote]

Entendi, então vc tem que usar isso e fazer uma query por página mesmo: http://www.petefreitag.com/item/451.cfm

Agora se o famigerado SQL Server 2000 não suportar LIMIT e OFFSET então FERROU.

Salva a lista na session ou num map mesmo. A coisa tem que ser stateful. Se quiser fazer stateless aí só usando LIMIT e OFFSET mesmo. Ou repetindo a query em cada página o que no caso do tópico não tem como porque são milhares de registros que ela retorna.

[quote=saoj][quote=jmmenezes][quote=saoj]Minhas observações rápidas:

  • SQL sem limit é inviável.

  • Paginação se faz carregando uma lista de objetos magros, e não objetos gordos.

  • Paginação com mais de 200 itens me parece overkill pois nenhum humano vai checar mais do que 200.

  • Se o cara não achar o que ele quer em 200 itens, então ele precisa fazer uma busca/filtro para limitar o resultset.

  • Uma query para retornar 200 itens não tem como ser lenta, a não ser que tenha problema de índices. Aí é uma questão de tunning.

[/quote]

OK… o problema aqui é que ele precisa imprimir essas 500 mil etiquetas (pior caso). Ou usar o arquivo para exportar para algum outro sistema (de e-mail marketing por exemplo). É uma espécie de extração de dados. Quando ele precisa de uma informação especifica ele utiliza outras funcionalidades do sistema que entram exatamente no que você falou acima.

Se a cada página o sistema ir até o banco de dados, ele retorna somente a quantidade de registros daquela página. O problema aqui acho que é o SQL Server que não é inteligente para fazer esta paginação com algumas consultas muito complexas. Entretanto como vamos precisar dos registros depois, fazer essa extração antes não é tanto problema assim.

Só que ele quer dar uma “olhada” antes no listão. (Para ver alguns registros que o filtro esta retornando e talvez dar uma mexida nos filtros). Por isso precisa mostrar de alguma forma na tela ao invés de sair exportando direto. Além do que dependendo do arquivo exportado, ele não consegue abrir este em nenhuma outra ferramenta de tão grande que fica. Só consegue fazer o upload pro e-mail marketing ou imprimir as etiquetas (pelo relatório jasper do próprio sistema e isso demora).

O ideal mesmo era usar um outro banco de dados para realizar estas seleções, mas o usuário quer “a base quente”. Ou até mesmo agendar estes processos de extração.

Ou seja, nesse caso… é realmente necessário extrair essa quantidade monstro de registros… se eu conseguisse paginar de forma eficiente no sql server, deixaria a extração completa para quando ele efetivamente fosse exportar. Entretanto ler os 500 mil registros e serializar esta levando quase o mesmo tempo que navegar em cada uma das páginas. Acho que é pq a query é muito complexa e o sql server esta gerando o resultado em areas temporárias para depois efetivamente pegar só os registros da página.
[/quote]

Entendi, então vc tem que usar isso e fazer uma query por página mesmo: http://www.petefreitag.com/item/451.cfm

Agora se o famigerado SQL Server 2000 não suportar LIMIT e OFFSET então FERROU.

Salva a lista na session ou num map mesmo. A coisa tem que ser stateful. Se quiser fazer stateless aí só usando LIMIT e OFFSET mesmo. Ou repetindo a query em cada página o que no caso do tópico não tem como porque são milhares de registros que ela retorna.[/quote]

É… o SQL Server 2000 não tem limit nem offset… (grrrrrrrr)
Talvez o 2008 consiga algo melhor mas ainda não migramos todos os sistemas e isto esta previso mais para frente… (grrrrrr denovo)

Bem… essa serialização em arquivo então acaba sendo uma espécie de cache… pq se fosse guardar tudo em memória, haja memória…

Acho que se conseguisse uma forma eficiente de paginação tipo o limit do mysql, o ideal seria paginar e quando ele clicasse no botão de exportação, fizesse a leitura completa. Mas nesse caso estou começando a achar que a solução não esta tão ruim assim…

Para paginar no SQL Server 2000 é preciso fazer subquery com select top ( select top ( query )).

Pelo que pesquisei na net não tem nada melhor que isso…

Deve ter outra maneira de simular o LIMIT e OFFSET. Não acredito que não tenha. Veja aqui => http://stackoverflow.com/questions/187998/row-offset-in-sql-server

Para um exemplo de paginação sem dores com o Mentawai veja aqui => http://www.mentaframework.org/mtw/Page/PaginatorTag/mentawai-tag-de-paginacao

Esse o Mentawai, sempre transformando coisas complexas em simples. Nenhum outro framework web faz isso. :slight_smile:

Ele tem o Top , que é equivalente ao limit. O offset teria que fazer na mão mesmo.

Exatamente. Se quiser fazer no próprio servidor pode criar tabela temporária “de verdade”.
Basta você utilizar um # no começo do nome da tabela ( #tabela ). Ela deixa de existir no fim da sessão.

Mas se eu fosse você, tentaria criar a paginação com os recursos do servidor e só geraria os 500 pra exportação mesmo.
(Ninguém vai conferir os 500 mil antes mesmo)

Aliás, pra pessoa consultar antes você pode consultar totais, tipo registros por cidade, por produto, depende do seu negócio.
A pessoa teria noção se os filtros estão corretos, de acordo com os totais, sem precisar bater o olho em um por um.

[quote=AbelBueno][quote=jmmenezes]
É… o SQL Server 2000 não tem limit nem offset… (grrrrrrrr)
[/quote]

Ele tem o Top , que é equivalente ao limit. O offset teria que fazer na mão mesmo.

Exatamente. Se quiser fazer no próprio servidor pode criar tabela temporária “de verdade”.
Basta você utilizar um # no começo do nome da tabela ( #tabela ). Ela deixa de existir no fim da sessão.

Mas se eu fosse você, tentaria criar a paginação com os recursos do servidor e só geraria os 500 pra exportação mesmo.
(Ninguém vai conferir os 500 mil antes mesmo)

Aliás, pra pessoa consultar antes você pode consultar totais, tipo registros por cidade, por produto, depende do seu negócio.
A pessoa teria noção se os filtros estão corretos, de acordo com os totais, sem precisar bater o olho em um por um.

[/quote]

Eh exatamente o top que utilizo e dependendo da query dinamica cada nova pagina leva uma eternidade como falei no comeco. Entre usar tab. Temporaria e fisica temporaria, ambas usam mto recurso do servidor mas o sistema antigo faz assim usando os 2 tipos de tabela dependendo da situacao.

Vou tentar uns testes com o sql 2008… Quem sabe vale a pena migrar antes.

Agora se usar a solucao da serializacao em arquivo. Quais riscos vcs enxergam nela?

Obrigado

[quote=jmmenezes][quote=saoj]Explica em duas frases no máximo. Simplicity is gold. Objetividade também.

O problema é que vc precisa paginar 500 mil registros que estão no banco e a cada página da sua paginação vc carrega esses 500 mil registros de novo ???
[/quote]

O problema é que o sistema é SQL Server 2000 e não há uma forma eficaz de realizar a paginação (se fosse mysql o limit funcionaria bem melhor). e mesmo retornando apenas a quantidade de registros de cada página (no caso 1000 registros), a consulta demora demais (parece que internamente o sql de alguma forma esta trabalhando com estes 500 mil).

Voltar os 500 mil e exibir para o usuário é necessário pois mesmo ele exportando e carregando em outro sistema, é um processo demorado e como o arquivo é grande ele tem muito trabalho de abrir em um Excel da vida, então quer dar uma visualizada nesses dados antes de fazer a exportação (visualizada superficial mas é uma necessidade).

Essas seleções geralmente são utilizadas para comunicaçõe segmentadas (que são pagas por quem as solicita).

500 mil registros é o pior caso… a maior parte delas não passa de mil registros e funciona bem. É que quisemos adotar uma solução que atendesse de qualquer forma com baixo consumo de recursos e até funcionou… só não sei se foi uma boa!!! (muitas vezes o simplesmente funcionar pode não ser uma boa idéia).

[/quote]

SQL Server 2000 realmente é um problema. Eu já passei por isso.
Mas veja que a paginação é para o usuário, não para o banco. Em bancos de dados reais (e e não de brinquedo como o SQL 2000) vc facilita a carga no servidor fazendo paginação no servidor acoplada ao cliente. No seu caso a solução é realmente ler todos os objetos e guarda em session. Depois paginar na camada de view. (páginar um list é trivial). Quando o cara mandar exportar os dados todos ou navegar, a querie não será feita mais. O problema é que uma query única vai demorar para o primeiro select.

Eu tentaria usar uma paginação não padrão no SQL Server para mandar a carga de volta para ele com opções alternativas como esta , ou pelo menos usar o TOP como se sugere aqui e ir acumulando em java.

Usar serialização não me parece viável porque inclui um tempo de I/O e objetos serializado ocupam mais espaço que objetos na memoria.

As soluções são: por ordem

  1. trocar o banco de dados (postgres por exemplo é free e excelente)
  2. implementar o limit na marra (tlv no store procedure) usando a gamb do rowID
  3. incluir um TOP ( não é o mesmo que o limit, mas já ajuda a não trazar as primeiras páginas que já foram lidas antes)
  4. deixar a querie no session do cliente e fazer a paginação puramente visual.

Existe a opção de carregar todos os objetos em outro banco , especialmente um banco do lado do cliente (já que é desktop) , mas isto entra com considerações de cache que sendo que vc quer informação quente pode não ser viável (por outro lado, “quente” é relativo pois nenhum sistema é real time)

Sergio, muito obrigado pela contribuicao! (ops nao estou na minha maquina e o teclado esta desconfigurado aqui, por isso nao estao saindo os acentos)

As dicas de paginacao, eu ja tentei e nao fica 100% mesmo… so a partir do 2005 que tem o row_number que facilita a vida.
Vou fazer mais alguns testes de paginacao para evitar ter de carregar tudo em um primeiro momento e testar tambem com o 2008, quem sabe.
Em relacao a gravar na memoria, fica inviavel devido ao alto consumo que gera, pois o app eh desktop e precisa rodar em maquinas com 512mb de ram. Por isso adotamos a solucao de serializar e jogamos este “peso” para o disco… e ateh que funcionou bem!

Vamos as respostas:

  1. Mudar o banco, talvez para o SQL 2008. O banco esta integrado com diversos outros sistemas atraves de procedures e triggers do proprio banco e vejo que esta migracao seria muito custosa nesse primeiro momento. Para o 2008 talvez nao seja tao custosa.
  2. O row_number soh tem no 2005 pra frente, entao ou tem de jogar para temp table que demora ou usar o esquema do select top (select top ( query))
  3. Usamos assim em varias partes do sistema, mas da problema com querys complexas pois parece que o sql insiste em carregar tudo em uma area temporaria em qualquer paginacao. Vou tentar mais algumas dicas.
  4. a questao da memoria que ferrou. Essa foi a primeira tentativa apos os problemas com a paginacao direta no banco.

Tambem testei o hsqldb e derby e ficou bom soh ateh 50 mil registros. Mesmo assim o tempo de ler/inserir dados foi maior que serializar os objetos de forma binaria.

Vou fazer algumas tentativas a mais… vamos ver se consigo fazer a paginacao no banco mesmo, caso contrario, nao estou conseguindo enxergar outra solucao…