Vazamento de memória com pool de conexões

Bom dia a todos,

trabalho numa empresa que está migrando um sistema desktop para a web e nessa odisséia surgiu o seguinte problema:

A parte de relatórios sempre foi um problema aqui dentro, pois o volume de dados no sistema tornou-se consideravelmente grande…

No inicio o select era executado e carregado todos os registros no java, porém, tornou-se inviável quando o volume cresceu…
Recuperar 500mil registros de uma vez, jogar tudo para a memória, manipulá-los…

Para resolver o problema, na época, foi feito a seguinte solução:

Criado um cursor no banco a partir do select, recuperá-los de 1, 10, X registros de cada vez para ai sim processá-los…
Passando todo o consumo de processamento/memória dos dados para o banco, que no caso, são bem robustos…

Ok, sem problemas…

Ao executar esse modelo na web, tivemos um problema com o pool de conexões…
Ocorrendo o seguinte cenário ao tirar um relátorio, com, por exemplo, 20 mil registros:

Banco: Cursor criado;
Java: loop para recuperar os dados
     ResultSet rs = cursor.carregaRegistro( 1); 
     // processamento

Legal, iremos passar por esse loop 20mil vezes e criarmos 20 mil resultsets…

O problema?

Os pools de conexões (c3p0, dbcp) armazenam os resultsets executados durante a conexão em uma lista…
como uma forma de otimização, para caso quiser recuperar algum dado sem precisar executar a instrução novamente…

Legal, o que acham que ocorreu?

vazamento de memória!

Imagine, 20 mil resulsets armazenados, juntamente com seus dados carregados (strings, chars, doubles etc…) sem a possibilidade do GC limpá-los, pois a lista armazena suas referências…

Agora estamos num dilema, e queríamos alguma opinão sobre alguém que tivesse passado pelo mesmo problema…

Pensamos em implantar nosso próprio pool, pois não usamos nenhum recurso extra que os frameswork nos proporcionam… Porém iriamos ficar mais distante de um servidor de aplicação quando/se o mesmo for necessário

Pensamos também em repensar o modelo de como manipulamos nossa gama de dados (o mais obvio)… com certeza está longe do ideal… mas como disse… em nossa arquitetura ele funciona bem e até poderiamos sofrer resistência de uma nova implementação… Caso alguém recomendar algum artigo sobre, ficaria muito grato!

Obrigado pela atenção…

agora é sério, existe a possibilidade de fazer paginação?

Paginação seria um recurso mais no sentido da visualização do relatório…
Caso o usuário queira imprimir os 20k de registros?
Teria que imprimir cada item da paginação…

E a pergunta:
‘tem alguem que imprimi 20k de registros?’

eu respondo:

‘tem, e como tem!’

Tem até um link no forum do DBCP relatando o assunto:

http://issues.apache.org/jira/browse/DBCP-180

Porém parece que os responsáveis não se interessaram muito!

eu falo paginação não no só no sentido de visualização, também na questão de consulta…
ao invés desse seu cursor trazer 1 registro, trazer 50, 100… sei lá…

a impressão não pode ser segmentada? ou seja, joga pra fila de impressão de 10 em 10 páginas… coisas do tipo…

Ola! No momento que voce fecha a conexao, o c3p0 vai liberar esse resultset:

Any Statements (including Prepared and CallableStatements) and ResultSets returned by raw operations will be c3p0-managed, and will be properly cleaned-up on close() of the parent proxy Connection.

A questão de trazer mais registros nos traz de volta ao problema que originou jogar para o banco o consumo de memória/processamento…

Se colocarmos em vez de 1, 100 registros… alias 1.000 registros

em vez de termos 20.000 objetos resultset, termos 20, correto?
Em partes…

Só que se fosse um relatório com… hipotéticamente 20 milhões de registros?
voltaríamos a ter 20 mil objetos resulsets…
Além de consumir memória e processamento da maquina cliente…
E esse consumo de memória causaria até mais chances de lançar um outofmemory…

Quanto a paginação com segmentação de impressão, poderia ser uma solução…
Porém estava procurando uma solução mais coerente com o que já temos, porque bem dizendo, esse modelo funciona bem no desktop… queria mantê-lo também na web…

Bom dia Paulo,
está é a questão!
ele só vai liberar os recursos quando eu fechar a conexão!

Porém eu não fechei!

Estou no loop, com a mesma conexão, recuperando meus objetos… e ele mantendo a referência desses objetos na lista…
quando eu fechar a conexão, ele vai limpar a mesma e com isso o gc vai liberar a memória…

mas o meu problema é exatamente esse…

devido ao modelo que tenho, eu mantenho a conexão e vou somente recuperando os objetos…

oi

o c3p0 guardar referencia para o resultset nao e problema, ja que por default o resultset nao é scrollable, quando voce faz o next() ele perde os dados anteriores. O wrapper deles nao faria o mesmo? se nao fizer, realmente é BEM ruim pra fazer uma query grande. Provavelmente isso esta acontecendo porque voce usa cursor. Nao daria pra voce nao usar cursor, e ir só com o next()?

Então paulo,
na verdade o que eles fazem é guardar todas as conections, statements, resultsets quando forem delegadas mantendo assim um cache…
o dbcp diz que esse cache é para verificação de conexões abandonadas, porém essa lista é independente de configurações do removeAbandoned…

mas enfim… o fato ocorre mesmo pelo uso de cursor…
como eu disse no post inicial do tópico, foi criado esse modelo usando cursor para ocupar o mínimo de memória no cliente… pois o volume de dados é grande…
antes fazíamos tudo no cliente, recuperávamos os dados do banco e processávamos no cliente, ocasionando muita demanda para o mesmo.

decidimos então jogar no banco, com cursor e ir recuperando um a um (ou em alguns casos 10 a 10) pois assim não ocuparia memória nenhuma no cliente, somente ir processando os registros advindo do banco…

Como no desk não usamos pool, não descobrimos esse problema…

Então estamos pensando ou em desenvolver um pool, pois usamos somente os recursos básicos do mesmo…
Ou pesquisarmos algumas formas para manipular essa massa de dados de forma efetiva e performática… você recomendaria algum artigo sobre?

Oi Damiani

Eu recomendaria voce pegar o codigo fonte do dbcp ou do c3p0 e retirar a parte do cache. Deve ser bem facil de implementar isso e registar o seu proprio wrapper de resultset que nao cacheia o cursor (basta pegar o wrapper atual e tira fora o if de cachear)!

Criar um pool na mao vai dar muito trabalho e vai dificilmente ficar tao bom quanto os que ja existem.

abracos

[quote=damianijr]Bom dia Paulo,
está é a questão!
ele só vai liberar os recursos quando eu fechar a conexão!

Porém eu não fechei!

Estou no loop, com a mesma conexão, recuperando meus objetos… e ele mantendo a referência desses objetos na lista…
quando eu fechar a conexão, ele vai limpar a mesma e com isso o gc vai liberar a memória…

mas o meu problema é exatamente esse…

devido ao modelo que tenho, eu mantenho a conexão e vou somente recuperando os objetos…
[/quote]

Cria um buffer para guardar os resultados enquanto você libera as conexões. Dessa maneira não vai perder os resultados nem estourar o heap.

juliocbq,
o problema é que estou no meio do cursor…
se eu fechar a conexão e devolve-la ao pool, meu cursor vai pro beleleu… e precisaria refaze-lo na próxima conexão…

vou seguir o que o Paulo disse e fazer um wrapper sem o código para o cache…
qualquer coisa do um feedback no post…

obrigado a todos…