第18章 キャッシュ

ほとんどの場合、 企業アプリケーションにおける主なボトルネックはデータベースです。 そして、 データベースは実行環境の中ではもっともスケーラブルしにくい部分です。 PHP や Ruby の人々は、 いわゆる "shared nothing" アーキテクチャにすれば拡張があると言うでしょう。 確かにそれは事実かもしれませんが、 クラスタ構成の複数ノード間で何もリソースを共有しないで設計できるアプリケーションなど、 あまり見たことがありません。 愚かな彼らが本当に考えているのは「データベース以外は」 "shared nothing" というアーキテクチャでしょう。 マルチユーザ用アプリケーションを拡張しにくくしているのは、 もちろんデータベース共有です。 したがって、このアーキテクチャに高い拡張性があると主張するのは無理がありますが、 彼らは数々のアプリケーションにたくさんの時間を割いています。

データベースの共有をなるべく少なくできる方法があれば、すべて実行する価値があります。

そこでキャッシュの登場です。しかも1種類ではありません。 Seamアプリケーションを正しく設計すれば、何層にもわたる豊富なキャッシング戦略をアプリケーションのすべての層で利用することができるのです。

2次キャッシュは非常に混み入った概念ですので、詳細についてはお使いのORMソリューションの文書を参照してください。 この章では、pojoCacheコンポーネントや、<s:cache>によるページ断片のキャッシュなど、JBossCacheを直接利用する方法について説明します。

18.1. SeamでJBossCacheを利用する

ビルトインの pojoCache コンポーネントは org.jboss.cache.aop.PojoCache のインスタンスを管理します。 不変の Java オブジェクトであれば安全にキャッシュに置くことができ、 オブジェクトはクラスタ内でレプリケーションされます (レプリケーションが有効な場合)。 変更の可能性があるオブジェクトをキャッシュに持ちたい場合は、 JBossCache のバイトコード・プロセッサを実行し、 オブジェクトの変更が自動的に検知され、 レプリケーションされるようにする必要があります。

pojoCacheを使うには、クラスパスにJBossCacheのjarを置き、treecache.xmlというリソースに適切なcacheの設定を記述するだけです。 JBossCacheには恐ろしく厄介で紛らわしい設定がたくさんあるので、ここでは説明しません。 詳細はJBossCacheの文書を参照してください。

SeamにEARをデプロイする場合は、JBossCacheのjarと設定ファイルをEARに直接含めることをお勧めします。 application.xmlにjarを記述することを忘れないでください。

次のように、Seamコンポーネントにキャッシュをインジェクトします。

@Name("chatroom")
public class Chatroom {
    @In PojoCache pojoCache;
    
    public void join(String username) {
      try
      {
         Set<String> userList = (Set<String>) pojoCache.get("chatroom", "userList");
         if (userList==null) 
         {
            userList = new HashSet<String>();
            pojoCache.put("chatroom", "userList", userList);
         }
         userList.put(username);
      }
      catch (CacheException ce)
      {
         throw new RuntimeException(ce);
      }
    }
}

JBossCacheを複数設定する場合は、components.xmlを使用してください。

<core:pojo-cache name="myCache" cfg-resource-name="myown/cache.xml"/>

18.2. ページ断片のキャッシュ

SeamのJBossCache の利用でもっとも興味深いのは、 JSFにおけるページ断片のキャッシュ問題を解決する、 <s:cache> タグです。 <s:cache> は内部的に pojoCache を使うので、 使用する場合は前述の手順を行ってください (EAR に jar を入れる、 やっかいな設定を切り抜ける、などの手順です。)

<s:cache>は、あまり変更されないレンタリング・コンテンツに使用してください。 たとえば、最新のblogエントリを表示するblogのウェルカムページです。

<s:cache key="recentEntries-#{blog.id}" region="welcomePageFragments">
   <h:dataTable value="#{blog.recentEntries}" var="blogEntry">
      <h:column>
         <h3>#{blogEntry.title}</h3>
         <div>
            <s:formattedText value="#{blogEntry.body}"/>
         </div>
      </h:column>
   </h:dataTable>
</s:cache>

key を指定することによって、 各ページ断片のキャッシュバージョンを複数持つことができます。 この例では、 ひとつの blog に対してひとつのキャッシュバージョンが存在します。 region には、 すべてのバージョンを保存する JBossCache のノードを指定します。 異なるノードは異なる有効期限ポリシーを持つ場合があります。 (前述の厄介な設定で指定できます。)

そして、 <s:cache> の大きな問題は、 対象のデータがいつ変更されるか (たとえば、 新しい blog がいつ投稿されるか) を知り得ないということです。 つまり、 キャッシュされた断片は、 明示的にキャッシュから排除する必要があります。

public void post() {
    ...
    entityManager.persist(blogEntry);
    pojoCache.remove("welcomePageFragments", "recentEntries-" + blog.getId() );
}

あるいは、変更を即座にユーザに見せる必要がないのであれば、JBossCacheノードの有効期限を短く設定しても良いでしょう。