Hibernateのキャッシュについて考えてみる(その5)

前回は Cache Concurrency Strategy を全体的に扱いました。今回からは各 Cache Concurrency Strategy のそれぞれを個別にフォーカスしていこう思います。今回は、ReadOnlyCache について掘り下げてみようと思います。

マニュアル曰く

If your application needs to read but never modify instances of a persistent class, a read-only cache may be used. This is the simplest and best performing strategy. It's even perfectly safe for use in a cluster.

http://www.hibernate.org/hib_docs/v3/reference/en/html/performance.html#performance-cache-readonly

おいら的意訳:
アプリケーションが、永続化クラスのインスタンスの読み込みを行う必要があるが変更を行う必要がない場合、read-only キャッシュが使用可能です。これは、最もシンプルで最高のパフォーマンスを実現する戦略です。クラスタ環境においても安全に使用可能です。

マニュアルからわかること

以下の条件が満たされる場合に、

  • 対象エンティティが、「変更」されないことが保障されている。
  • 対象エンティティの Cache Concurrency Strategy が read-only である。

以下の効果が得られると解釈してよいと思います。

  • キャッシュ戦略として最もシンプルかつ最高のパフォーマンスが実現される。
  • クラスタ環境においても安全に使用可能。

マニュアルからはわからないこと

  • "modify" の具体的な意味。insert, update, delete のすべてと考えれば実害はなさそうですが、厳密にはどうなんでしょうか。

Hibernate初期化時のキャッシュ関連の主要な振る舞いを確認してみる

設定例
hibernate.cache.provider_class=jp.objectfanatics.ofcache.hibernate.OfCacheProvider
hibernate.ejb.classcache.jp.objectfanatics.ofcache.hibernate.integration.SampleEntityImpl=read-only
振る舞い

Hibernate の初期化の際には、CacheProvider 実装のインスタンスと、2次キャッシュを使用するように設定された全エンティティに対応する Cache 実装のインスタンスが生成されます。

  • 指定されたキャッシュプロバイダ実装クラスのインスタンスが new される。
  • CacheProvider#isMinimalPutsEnabledByDefault() が呼び出される。
  • CacheProvider#start() が呼び出される。
  • 2次キャッシュを使用するように設定された各エンティティに対応して、CacheProvider#buildCache が呼び出される。

Session初期化時のキャッシュ関連の主要な振る舞いを確認してみる

  • SessionFactoryImpl#openSession(Interceptor sessionLocalInterceptor) にて、 openSession メソッドの引数にするために CacheProvider#nextTimestamp() でタイムスタンプを取得。

CRUD操作の検証(CacheConcurrencyStrategy が org.hibernate.cache.ReadOnlyCacheクラスのエンティティの場合)

CREATE : エントリの挿入
  • 普通にDBに保存される。
  • キャッシュには put されない。
READ : DB上にも2次キャッシュ上にも存在しないエントリの読み込み
  • キャッシュミス。
  • DBからデータは取得できず。
  • 2次キャッシュには何も put もされず。
READ : DB上に存在し、2次キャッシュ上に存在しないエントリの読み込み
  • キャッシュミス。
  • DBからのデータ取得に成功。
  • 2次キャッシュに put。
READ : 2次キャッシュ上に存在するエントリの読み込み
  • キャッシュヒット。
READ : DB上に存在しないキーのエントリを読み込み、Hibernateを経由しないでDBに直接そのキーのエントリを書き込み、再度同一のキーのエントリを読み込む *1
  • DB上に存在しないキーのエントリを読み込み
    • キャッシュミス。
    • DBからデータは取得できず。
    • 2次キャッシュには何も put もされず。
  • Hibernateを経由しないでDBに直接そのキーのエントリを書き込み後、再度同一のキーのエントリを読み込む
    • キャッシュミス。
    • DBからのデータ取得に成功。
    • 2次キャッシュに put。
UPDATE : 2次キャッシュ上に存在するエントリに対して、Hibernate 経由で update
  • ReadOnlyCache#lock(Object key, Object version) にて、UnsupportedOperationException を throw。その際、キャッシュの内容には影響を与えない。
DELETE : 2次キャッシュ上に存在するエントリに対して、Hibernate 経由で delete
  • ReadOnlyCache#lock(Object key, Object version) にて、UnsupportedOperationException を throw。その際、キャッシュの内容には影響を与えない。

CRUD操作の検証まとめ

CREATE
  • 可能
  • キャッシュへの put は行われない。
  • ※2次キャッシュのみの視点であれば、同一の SessionFactory を経由しないDBへの insert (JDBC経由とかコンソールから直接とか)も可能。しかし、クエリキャッシュやコレクションキャッシュ適用時には不整合が起こる。たとえばクエリキャッシュの場合は、2次キャッシュが適用されているエンティティに対して insert, update, delete が発生した場合には、タイムスタンプを更新してクエリキャッシュの invalidate をするのだが、同一の SessionFactory を経由しないDBへの insert を行うと、この invalidation ができず、クエリキャッシュのデータが腐ってしまう。
READ
  • 可能
  • 2次キャッシュからの取得が試行され、キャッシュミス時のみDBに対しての問い合わせが行われる。
  • キャッシュミス時にDBからデータが取得されると、キャッシュに put する。
UPDATE
  • 不可能
  • 同一 SessionFactory 上から update をかけると、UnsupportedOperationException が throw される。その際、2次キャッシュには影響を与えない。
  • 同一 SessionFactory を経由せずに update をかけると、2次キャッシュは古いデータを返し続け、DB と2次キャッシュのデータの整合性を保証できなくなる。
DELETE
  • 不可能
  • 同一 SessionFactory 上から delete をかけると、UnsupportedOperationException が throw される。その際、2次キャッシュには影響を与えない。
  • 同一 SessionFactory を経由せずに delete をかけると、2次キャッシュは古いデータを返し続け、DB と2次キャッシュのデータの整合性を保証できなくなる。

まとめ

今回は ReadOnlyCache について掘り下げてみました。次回ReadWriteCache について掘り下げてみたいと思います。

*1:これは、DBに存在しなかったという情報がキャッシュされるかどうかを確認するためのものです。たとえば、DB から read 時にデータが存在しなかった場合に、DB に存在しなかったという情報をキャッシュしておけば、次回以降はDBを読む必要がなくなります。