Hibernate Query Plan Cache

1. 前書き

このクイックチュートリアルでは、Hibernateが提供するクエリプランキャッシュとそのパフォーマンスへの影響について説明します。

2. クエリプランキャッシュ

すべてのJPQLクエリまたは基準クエリは、実行前に解析されて抽象構文ツリー(AST)になり、HibernateがSQLステートメントを生成できるようになります。 *クエリのコンパイルには時間がかかるため、Hibernateはパフォーマンス向上のためにhttps://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/engine/query/spi/QueryPlanCache.html[QueryPlanCache]Cacheを提供します。*
ネイティブクエリの場合、Hibernateは名前付きパラメーターとクエリの戻り値の型に関する情報を抽出し、it_https://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/engine/query/spi/ParameterMetadataに保存します。 html [ParameterMetadata] _。
Hibernateは実行ごとに最初にプランキャッシュをチェックし、使用可能なプランがない場合にのみ、新しいプランを生成し、将来の参照のためにキャッシュに実行プランを保存します。

3. 設定

クエリプランキャッシュの構成は、次のプロパティによって制御されます。
  • hibernate.query.plan_cache_max_size –の最大数を制御します
    プランキャッシュのエントリ(デフォルトは2048)

  • hibernate.query.planパラメーターメタデータmax_size_ –
    キャッシュ内の_ParameterMetadata_インスタンスの数(デフォルトは128)

    そのため、アプリケーションがクエリプランキャッシュのサイズよりも多くのクエリを実行する場合、Hibernateはクエリのコンパイルに余分な時間を費やす必要があります。 そのため、クエリ全体の実行時間が長くなります。

4. テストケースのセットアップ

業界で言われているように、パフォーマンスに関しては、クレームを決して信用しないでください。 したがって、*キャッシュ設定を変更したときにクエリのコンパイル時間がどのように変化するかをテストしましょう*。

4.1. テストに関与するエンティティクラス

この例で使用するエンティティ_DeptEmployee_および_Department_を見てみましょう。
@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<DeptEmployee> employees;

    // standard getters and setters
}

4.2. テストに関係するHibernateクエリ

クエリのコンパイル時間全体の測定のみに関心があるため、テスト用に有効なHQLクエリの任意の組み合わせを選択できます。
この記事では、次の3つのクエリを使用します。
  • _ * 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. パフォーマンスへの影響の測定

5.1. ベンチマークコードのセットアップ

*キャッシュサイズを1から3に変更します* –その後、3つのクエリはすべてキャッシュに格納されます。 したがって、それをさらに増やす意味はありません。
@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. テスト中のコード

次に、クエリのコンパイル中にHibernateにかかる平均時間を測定するために使用されるベンチマークコードを見てみましょう。
@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);
}
ベンチマークの作成にはlink:/java-microbenchmark-harness[JMH]を使用していることに注意してください。

5.3. ベンチマーク結果

次に、上記のベンチマークを実行して準備したコンパイル時間とキャッシュサイズのグラフを視覚化します。
link:/uploads/plan-cache-100x67.png%20100w []
グラフから明らかなように、* Hibernateがキャッシュできるクエリの数を増やすと、コンパイル時間が短縮されます*。
キャッシュサイズが1の場合、平均コンパイル時間は709マイクロ秒で最も高く、キャッシュサイズが2の場合は409マイクロ秒に減少し、キャッシュサイズが3の場合は0.637マイクロ秒に減少します。

6. Hibernate Statisticsを使用する

クエリプランキャッシュの有効性を監視するために、Hibernateは_http://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/stat/Statistics.html [Statistics] _インターフェースを介して以下のメソッドを公開します。
  • getQueryPlanCacheHitCount

  • getQueryPlanCacheMissCount

    したがって、ヒットカウントが高く、ミスカウントが低い場合、クエリのほとんどは、繰り返しコンパイルされるのではなく、キャッシュ自体から提供されます。

7. 結論

この記事では、Hibernateのクエリプランキャッシュとは何か、そしてそれがアプリケーションの全体的なパフォーマンスにどのように貢献できるかを学びました。 全体として、アプリケーションで実行されているクエリの数に応じて、クエリプランのキャッシュサイズを維持するようにしてください。
いつものように、このチュートリアルのソースコードは、https://github.com/eugenp/tutorials/tree/master/persistence-modules/hibernate5 [GitHubで]から入手できます。