PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Spring + Hibernate:uso da memória cache do plano de consulta


Eu também bati nessa questão. Basicamente se resume a ter um número variável de valores em sua cláusula IN e o Hibernate tentando armazenar em cache esses planos de consulta.

Existem dois ótimos posts sobre este tópico. O primeiro:

Usando Hibernate 4.2 e MySQL em um projeto com uma consulta dentro da cláusula, como:select t from Thing t where t.id in (?)

O Hibernate armazena em cache essas consultas HQL analisadas. Especificamente o HibernateSessionFactoryImpl tem QueryPlanCache com queryPlanCache e parameterMetadataCache . Mas isso provou ser um problema quando o número de parâmetros para a cláusula é grande e varia.

Esses caches crescem para cada consulta distinta. Portanto, esta consulta com 6000 parâmetros não é igual a 6001.

A consulta na cláusula é expandida para o número de parâmetros na coleção. Os metadados são incluídos no plano de consulta para cada parâmetro na consulta, incluindo um nome gerado como x10_, x11_ etc.

Imagine 4.000 variações diferentes no número de contagens de parâmetros na cláusula, cada uma delas com uma média de 4.000 parâmetros. Os metadados de consulta para cada parâmetro são adicionados rapidamente na memória, preenchendo o heap, pois não podem ser coletados como lixo.

Isso continua até que todas as variações diferentes na contagem de parâmetros de consulta sejam armazenadas em cache ou a JVM fique sem memória de heap e comece a jogarjava.lang.OutOfMemoryError:Java heap space.

Evitar in-clauses é uma opção, assim como usar um tamanho de coleção fixo para o parâmetro (ou pelo menos um tamanho menor).

Para configurar o tamanho máximo do cache do plano de consulta, consulte a propriedadehibernate.query.plan_cache_max_size , padrão para 2048 (facilmente toolarge para consultas com muitos parâmetros).

E segundo (também referenciado do primeiro):

O Hibernate usa internamente um cache que mapeia instruções HQL (asstrings) para planos de consulta. O cache consiste em um mapa delimitado limitado por padrão a 2048 elementos (configurável). Todas as consultas HQL são carregadas por meio desse cache. Em caso de falha, a entrada é automaticamente adicionada à cache. Isso o torna muito suscetível a thrashing - cenário em que constantemente colocamos novas entradas no cache sem nunca reutilizá-las e, assim, evitando que o cache traga ganhos de desempenho (ele até adiciona alguma sobrecarga de gerenciamento de cache). Para piorar as coisas, é difícil detectar essa situação por acaso - você precisa definir o perfil explicitamente do cache para perceber que há um problema lá. Direi algumas palavras sobre como isso pode ser feito mais tarde.

Portanto, o cache thrashing resulta de novas consultas sendo geradas em altas taxas. Isso pode ser causado por uma infinidade de problemas. Os dois mais comuns que eu vi são - bugs na hibernação que fazem com que os parâmetros sejam renderizados na instrução JPQL em vez de serem passados ​​como parâmetros e o uso de uma cláusula "in".

Devido a alguns bugs obscuros na hibernação, há situações em que os parâmetros não são tratados corretamente e são renderizados na consulta JPQL (como exemplo, confira HHH-6280). Se você tiver uma consulta afetada por esses defeitos e for executada em altas taxas, ela destruirá o cache do plano de consulta porque cada consulta JPQL gerada é quase exclusiva (contendo IDs de suas entidades, por exemplo).

A segunda questão está na maneira como o hibernate processa as consultas com uma cláusula "in" (por exemplo, forneça todas as entidades de pessoa cujo idfield da empresa seja 1, 2, 10, 18). Para cada número distinto de parâmetros na cláusula "in", o hibernate produzirá uma consulta diferente - por exemplo,select x from Person x where x.company.id in (:id0_) para 1 parâmetro,select x from Person x where x.company.id in (:id0_, :id1_) para 2 parâmetros e assim por diante. Todas essas consultas são consideradas diferentes, no que diz respeito ao cache do plano de consulta, resultando novamente em cachethrashing. Você provavelmente poderia contornar esse problema escrevendo uma classe de utilitário para produzir apenas um certo número de parâmetros - por exemplo, 1,10, 100, 200, 500, 1000. Se você, por exemplo, passar 22 parâmetros, retornará uma lista de 100 elementos com os 22 parâmetros incluídos nele e os 78 parâmetros restantes definidos para um valor impossível (por exemplo, -1 para IDs usado para chaves estrangeiras). Concordo que este é um hack feio, mas poderia fazer o trabalho. Como resultado, você terá apenas no máximo 6 consultas exclusivas em seu cache e, assim, reduzirá o thrashing.

Então, como você descobre que tem o problema? Você pode escrever algum código adicional e expor métricas com o número de entradas no cache, por exemplo. sobre JMX, ajustar o log e analisar os logs, etc. Se você não quiser (ou não puder) modificar o aplicativo, basta despejar o heap e executar esta consulta OQL nele (por exemplo, usando mat):SELECT l.query.toString() FROM INSTANCEOF org.hibernate.engine.query.spi.QueryPlanCache$HQLQueryPlanKey l . Ele produzirá todas as consultas atualmente localizadas em qualquer cache de plano de consulta em seu heap. Deve ser muito fácil identificar se você é afetado por algum dos problemas acima mencionados.

No que diz respeito ao impacto no desempenho, é difícil dizer, pois depende de muitos fatores. Eu vi uma consulta muito trivial causando uma sobrecarga de 10 a 20 msof gasta na criação de um novo plano de consulta HQL. Em geral, se houver um cache em algum lugar, deve haver uma boa razão para isso - errar provavelmente é caro, então você deve tentar evitar erros o máximo possível. Por último, mas não menos importante, seu banco de dados também terá que lidar com grandes quantidades de instruções SQL exclusivas - fazendo com que ele as analise e talvez crie planos de execução diferentes para cada uma delas.