キャッシュパフォーマンスチューニング(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)
しかし、単純にそうもいかないケースもあります。(続く)