キャッシュパフォーマンスチューニング(eagerフェッチ関連)

eager フェッチが絡んでくると、ちょっと状況が複雑になってきます。

Hibernateのデフォルトのフェッチ戦略

Hibernateのデフォルトのフェッチ戦略では、MenyToOne と OneToOne 関連は eager フェッチが適用されます。しかし、それではうれしくない状況もあります。

以下の状況を考えてみます。

  • エンティティ A と エンティティ B が OneToOne 関連を持っている。
  • あるクエリ Q が、A エンティティを select する。

キャッシュにより性能を上げようと考えた場合、以下の方法が考えられます。

  • 1.クエリ Q のクエリキャッシュを有効にする。
  • 2.エンティティ A の2次キャッシュを有効にする。
  • 3.エンティティ B の2次キャッシュを有効にする。
キャッシュを有効にしない場合の動作
  • クエリ Q が実行され、エンティティ A と エンティティ B がフェッチされる。(eager関連のため、通常は join を用いた SQL により一度にフェッチされる)
キャッシュを有効(1,2,3)にした場合の動作(キャッシュミス時)
  • クエリ Q がアクセスされ、join を用いた SQL により、エンティティ A とエンティティ B がまとめて取得される。(eager関連のため)
  • エンティティ A と エンティティ B が2次キャッシュに格納される。
  • クエリ Q の結果がクエリキャッシュとして格納される。(HQL, パラメータ、Aの主キーが格納されます。A や B エンティティの実体はクエリキャッシュには格納されません。)
キャッシュを有効(1,2,3)にした場合の動作(キャッシュヒット時)
  • クエリ Q のエンティティキャッシュが参照される。
  • クエリ Q のエンティティキャッシュに格納された A の主キーから、2次キャッシュが検索され、A が取得される。
  • A は eager フェッチなので、2次キャッシュ中から B も取得される。

eagerフェッチ系禁則事項

先ほど、1と2と3のキャッシュ設定例を挙げましたが、それらの組み合わせによっては逆効果になる場合がありますし、他の要因により推奨できない状況もあります。

クエリキャッシュ経由で取得されるエンティティが2次キャッシュに乗っているが、そのエンティティからの eager フェッチ対象のエンティティが2次キャッシュにのっていない場合。(1と2のみの場合)
  • キャッシュミスした場合
    • クエリが発行され、対象エンティティと、そこから eager フェッチ対象のすべてのエンティティが取得される。多くの場合は、join を用いて1回のSQLで取得される。
  • キャッシュヒットした場合
    • クエリキャッシュがヒットし、2次キャッシュから対象エンティティを取得する。
    • 対象エンティティから eager フェッチ対象のエンティティを取得しようとするが、2次キャッシュに乗っていないため、SQL を発行して DB から情報を取得する。

このように、キャッシュヒットしても結局DBアクセスされてしまいます。
キャッシュのおいしさ半減です。( ゚Д゚)マズー

eagar 元のエンティティに対して、eager 先のエンティティのヒット率が著しく低い。

このケースの場合は、せっかくクエリ Q で A と B を join して持ってきても、実際には A のみが使用されるケースが多い場合、毎回 join するコストはムダです。このような場合は、B を lazy フェッチ対象にする方法が有効です。

フェッチ戦略を eager フェッチから lazy フェッチに変更するには、下記のようなアノテーションで対応可能です。

@OneToOne(fetch=FetchType.LAZY)

しかし、単純にそうもいかないケースもあります。(続く)