Cache do plano de consulta de hibernação

Cache do plano de consulta de hibernação

1. Introdução

Neste tutorial rápido, vamos explorar o cache do plano de consulta fornecido pelo Hibernate e seu impacto no desempenho.

2. Cache do plano de consulta

Toda consulta JPQL ou consulta de Critérios é analisada em uma Árvore de Sintaxe Abstrata (AST) antes da execução, para que o Hibernate possa gerar a instrução SQL. Since query compilation takes time, Hibernate provides a QueryPlanCache for better performance.

Para consultas nativas, o Hibernate extrai informações sobre os parâmetros nomeados e o tipo de retorno da consulta e as armazena emParameterMetadata.

Para cada execução, o Hibernate primeiro verifica o cache do plano, e somente se não houver nenhum plano disponível, ele gera um novo plano e armazena o plano de execução no cache para referência futura.

3. Configuração

A configuração do cache do plano de consulta é controlada pelas seguintes propriedades:

  • hibernate.query.plan_cache_max_size - controla o número máximo de entradas no cache do plano (o padrão é 2048)

  • hibernate.query.plan_parameter_metadata_max_size - gerencia o número deParameterMetadata instâncias no cache (o padrão é 128)

Portanto, se nosso aplicativo executar mais consultas que o tamanho do cache do plano de consultas, o Hibernate terá que gastar mais tempo na compilação de consultas. Portanto, o tempo geral de execução da consulta aumentará.

4. Configurando o caso de teste

Como diz o ditado no setor, quando se trata de desempenho, nunca confie nas reivindicações. Portanto,let’s test how the query compilation time varies as we change the cache settings.

4.1. Classes de entidade envolvidas no teste

Vamos começar dando uma olhada nas entidades que usaremos em nosso exemplo,DeptEmployee eDepartment:

@Entity
public class DeptEmployee {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

    private String employeeNumber;

    private String title;

    private String name;

    @ManyToOne
    private Department department;

   // standard getters and setters
}
@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;

    private String name;

    @OneToMany(mappedBy="department")
    private List employees;

    // standard getters and setters
}

4.2. Consultas de hibernação envolvidas no teste

Estamos interessados ​​em medir apenas o tempo de compilação geral da consulta, portanto, podemos escolher qualquer combinação de consultas HQL válidas para nosso teste.

Para os fins deste artigo, usaremos as três consultas a seguir:

  • * findEmployeesByDepartmentName *

session.createQuery("SELECT e FROM DeptEmployee e " +
  "JOIN e.department WHERE e.department.name = :deptName")
  .setMaxResults(30)
  .setHint(QueryHints.HINT_FETCH_SIZE, 30);
  • findEmployeesByDesignation

session.createQuery("SELECT e FROM DeptEmployee e " +
  "WHERE e.title = :designation")
  .setHint(QueryHints.SPEC_HINT_TIMEOUT, 1000);
  • findDepartmentOfAnEmployee

session.createQuery("SELECT e.department FROM DeptEmployee e " +
  "JOIN e.department WHERE e.employeeNumber = :empId");

5. Medindo o impacto no desempenho

5.1. Configuração do código de referência

We’ll vary the cache size from one to three - depois disso, todas as nossas três consultas já estarão no cache. Portanto, não há motivo para aumentá-lo ainda mais:

@State(Scope.Thread)
public static class QueryPlanCacheBenchMarkState {
    @Param({"1", "2", "3"})
    public int planCacheSize;

    public Session session;

    @Setup
    public void stateSetup() throws IOException {
       session = initSession(planCacheSize);
    }

    private Session initSession(int planCacheSize) throws IOException {
        Properties properties = HibernateUtil.getProperties();
        properties.put("hibernate.query.plan_cache_max_size", planCacheSize);
        properties.put("hibernate.query.plan_parameter_metadata_max_size", planCacheSize);
        SessionFactory sessionFactory = HibernateUtil.getSessionFactoryByProperties(properties);
        return sessionFactory.openSession();
    }
    //teardown...
}

5.2. Código em teste

A seguir, vamos dar uma olhada no código de benchmark usado para medir o tempo médio gasto pelo Hibernate durante a compilação das consultas:

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(1)
@Warmup(iterations = 2)
@Measurement(iterations = 5)
public void givenQueryPlanCacheSize_thenCompileQueries(
  QueryPlanCacheBenchMarkState state, Blackhole blackhole) {

    Query query1 = findEmployeesByDepartmentNameQuery(state.session);
    Query query2 = findEmployeesByDesignationQuery(state.session);
    Query query3 = findDepartmentOfAnEmployeeQuery(state.session);

    blackhole.consume(query1);
    blackhole.consume(query2);
    blackhole.consume(query3);
}

Observe que usamosJMH para escrever nosso benchmark.

5.3. Resultados de referência

Agora, vamos visualizar o gráfico de tempo de compilação versus tamanho do cache que preparamos executando o benchmark acima:

image

Como podemos ver claramente no gráfico,increasing the number of queries that Hibernate is allowed to cache consequently reduces the compilation time.

Para um tamanho de cache de um, o tempo médio de compilação é maior em 709 microssegundos, depois diminui para 409 microssegundos para um tamanho de cache de dois e até 0,637 microssegundos para um tamanho de cache de três.

6. Usando estatísticas de hibernação

Para monitorar a eficácia do cache do plano de consulta, o Hibernate expõe os seguintes métodos por meio da interfaceStatistics:

  • getQueryPlanCacheHitCount

  • getQueryPlanCacheMissCount

Portanto, se a contagem de ocorrências for alta e a contagem de faltas for baixa, a maioria das consultas será atendida a partir do próprio cache, em vez de ser compilada repetidamente.

7. Conclusão

Neste artigo, aprendemos qual é o cache do plano de consulta no Hibernate e como ele pode contribuir para o desempenho geral do aplicativo. No geral, devemos tentar manter o tamanho do cache do plano de consultas de acordo com o número de consultas em execução no aplicativo.

Como sempre, o código-fonte deste tutorial está disponívelover on GitHub.