ほとんどの場合、 企業アプリケーションにおける主なボトルネックはデータベースです。 そして、 データベースは実行環境の中ではもっともスケーラブルしにくい部分です。 PHP や Ruby の人々は、 いわゆる "shared nothing" アーキテクチャにすれば拡張があると言うでしょう。 確かにそれは事実かもしれませんが、 クラスタ構成の複数ノード間で何もリソースを共有しないで設計できるアプリケーションなど、 あまり見たことがありません。 愚かな彼らが本当に考えているのは「データベース以外は」 "shared nothing" というアーキテクチャでしょう。 マルチユーザ用アプリケーションを拡張しにくくしているのは、 もちろんデータベース共有です。 したがって、このアーキテクチャに高い拡張性があると主張するのは無理がありますが、 彼らは数々のアプリケーションにたくさんの時間を割いています。
データベースの共有をなるべく少なくできる方法があれば、すべて実行する価値があります。
そこでキャッシュの登場です。しかも1種類ではありません。 Seamアプリケーションを正しく設計すれば、何層にもわたる豊富なキャッシング戦略をアプリケーションのすべての層で利用することができるのです。
データベースは当然ながら独自のキャッシュを持っています。 このことは大変重要ですが、アプリケーションのキャッシュのような拡張性はありません。
ORMソリューション (Hibernateやその他のJPA実装など) はデータベースからデータを2次キャッシュに置きます。 これはとても強力な機能なのですが、間違った使われ方をされがちです。 トランザクションのキャッシュ・データをクラスタ環境の全ノードで一貫性を持たせ、データベースとも同期させると、非常に重い処理になります。 複数ユーザで共有され、更新がまれなデータには良いかも知れません。 一般的なステートレスなアーキテクチャでは、2次キャッシュに対話状態をキャッシュしようとしますが、これは良いことではありません。 特にSeamの場合は、誤りです。
Seamの対話コンテキストは、対話状態のキャッシュです。 対話コンテキストに保存したコンポーネントは、ユーザのインタラクションに関連した状態をキャッシュし、保持します。
特に、Seamが管理する永続コンテキスト (あるいは対話スコープのステートフルセッションビーンに関連付けられたEJBコンテナ管理の拡張永続コンテキスト) は、 現在の対話に読み込まれたデータのキャッシュとして振舞います。 このキャッシュのヒット率は通常はとても高くなります! クラスタ環境では、Seamが管理する永続コンテキストはSeamによってレプリケーションが最適化され、データベースのトランザクションの一貫性を気にする必要はありません (楽観的ロックで充分です) 。 ひとつの永続コンテキストに何千ものオブジェクトを読み込まない限り、このキャッシュの効率についてあまり気にする必要はありません。
トランザクションに関連しない状態をSeamのアプリケーション・コンテキストにキャッシュすることもできます。 アプリケーション・コンテキストに保持された状態は、クラスタ内の他のノードにはもちろん見えません。
トランザクションの状態は、JBossCacheを使ったSeamのpojoCacheコンポーネントにキャッシュできます。 JBossCacheをクラスタで使用していれば、この状態は他のノードにも見えます。
最後に、レンダリングされたJSFページの断片をキャッシュすることができます。 ORMソリューションの2次キャッシュと違い、データが変更されても自動的に無効になることはないので、 明示的に無効化するアプリケーション・コードを書くか、適切な有効期限ポリシーを設定する必要があります。
2次キャッシュは非常に混み入った概念ですので、詳細についてはお使いのORMソリューションの文書を参照してください。 この章では、pojoCacheコンポーネントや、<s:cache>によるページ断片のキャッシュなど、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"/>
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ノードの有効期限を短く設定しても良いでしょう。