Hibernateのキャッシュアクセスの罠

少し前に会社であった開発のお話。



Hibernateで二次キャッシュを使うようehcache.xmlの設定や、マッピングファイルにタグを入れるなどして設定をしていました。
確かに、キャッシュの設定が入ったことは確認しました。

でも今週、あるパフォーマンスの悪い処理を見ていたところ、どうも本来ならキャッシュから取得して早いはずの箇所で処理が遅いことがわかりました。
実際にdebugでshow_sql=trueにして見てみると、確かにキャッシュ設定したテーブルにもかかわらず何度もDBアクセスが発生している。


半日、家に帰ってからもいろいろ調べてみた。

そしてようやく、このケースに関しては原因がわかりました。


Hibernateのget()を使って取得していたのですが、第2引数のIdentifierオブジェクトにnull値が入っていることがあり、その場合キャッシュがクリアされてしまっているようです。
そもそも主キーにnull値が来ること自体想定外なので、nullがきたらnullの結果を返すようにしget()が呼び出されないようにしたところ、再びキャッシュを使用するようになりました。

これはここでよかったのですが、何か気になって他のキャッシュ設定したものに関しても調査しました。

すると同様にキャッシュが効いておらず、テーブルへのアクセスが発生している。
今度はfind()を使ったもの。


なぜだろうとと調べたところ、どうもget()もしくはload()でかつidentifierを指定した場合しかキャッシュが効かないようなのです。


ただ、今回のテーブルは主キーが「ID」という名前のnumber型10桁のシーケンシャルな値。(SEQUENCEを使って発行)
実際にID指定のwhere文なんてほとんどなく、ユニークインデックスをはっているカラムの方をメインに使っています。

要するに、Hibernate向けにテーブルが設計されていない状態っちゅうことになります。
しかも外部キー制約もないためなおさら救えない。。。

中には意味を持たせたIDの生成をしているテーブルがあったので、これはまだなんとか救えました。
Java側でIDを生成するユーティリティを作成し、それをキーとして使う)

問題は前述のシーケンシャルなID。



さすがにどうしようもないので、使用頻度が高く更新頻度が極端に少ないテーブルに的を絞り、Hibernateが使っているEhCacheを少し流用することにしました。


実際に行ったことは、Cacheクラスを使い、ユニークになるキーとそのキーに紐づくエンティティ(これもHibernateで使っているEntityクラスをそのまま流用)をキャッシュするようにしました。
もちろんメモリの大量消費につながってはいけないため、必要最低限に絞っています。

実際に実装し、稼働してみると最初のアクセスでは当然DBアクセスが発生するのですが、そこからはキャッシュを返すようになりますのでDBアクセスがなくなりました。
これはかなり大きい。


他のチームのシステムではDBアクセス数が半端無くパフォーマンスに苦しんでいるとこもがありますので、この仕組をうまく活用できなかなと思っています。
ただ他チームで使っているフレームワークはほとんどがSAStruts + S2JDBC
よってEhCacheがデフォルトでは入っていませんので、別途入れる必要があります。
このあたりの動きはまだ検証できていないので、別途確認する必要がありますね。

ただ、細かいアクセスも含みかなりのDBアクセスが発生しているので、おそらく効果絶大ではないかと思っています。
Oracleを使ってコネクションプールをはっているとはいえ、結局DBアクセスそのものがコストになりますので。

Hibernate辞典 設定・マッピング・クエリ逆引きリファレンス (DESKTOP REFERENCE)

Hibernate辞典 設定・マッピング・クエリ逆引きリファレンス (DESKTOP REFERENCE)

HIBERNATE イン アクション

HIBERNATE イン アクション