SeamFramework.orgCommunity Documentation
In almost all enterprise applications, the database is the primary bottleneck, and the least scalable tier of the runtime environment. People from a PHP/Ruby environment will try to tell you that so-called "shared nothing" architectures scale well. While that may be literally true, I don't know of many interesting multi-user applications which can be implemented with no sharing of resources between different nodes of the cluster. What these silly people are really thinking of is a "share nothing except for the database" architecture. Of course, sharing the database is the primary problem with scaling a multi-user application — so the claim that this architecture is highly scalable is absurd, and tells you a lot about the kind of applications that these folks spend most of their time working on.
データベースの共有をなるべく少なくできる方法があれば、すべて実行する価値があります。
そこでキャッシュの登場です。しかも1種類ではありません。 Seamアプリケーションを正しく設計すれば、何層にもわたる豊富なキャッシング戦略をアプリケーションのすべての層で利用することができるのです。
データベースは当然ながら独自のキャッシュを持っています。このことはたいへん重要ですが、アプリケーション層のキャッシュのような拡張性はありません。
ORMソリューション(Hibernateやその他のJPA実装など)はデータベースからデータを2次キャッシュに置きます。 これはとても強力な機能なのですが、間違った使われ方をされがちです。 トランザクションのキャッシュデータをクラスタ環境の全ノードで一貫性を持たせ、データベースとも同期させると、非常に重い処理になります。 複数ユーザーで共有され、更新がまれなデータには良いかも知れません。 一般的なステートレスなアーキテクチャでは、2次キャッシュに対話状態をキャッシュしようとしますが、これは良いことではありません。 特にSeamの場合は、誤りです。
Seamの対話コンテキストは、対話状態のキャッシュです。 対話コンテキストに保存したコンポーネントは、ユーザーのインタラクションに関連した状態をキャッシュし、保持します。
特に、Seamが管理する永続コンテキスト(あるいは対話スコープのステートフルセッションBeanに関連付けられたEJBコンテナ管理の拡張永続コンテキスト)は、現在の対話に読み込まれたデータのキャッシュとして振舞います。このキャッシュのヒット率は通常はとても高くなります!クラスタ環境では、Seamが管理する永続コンテキストはSeamによってレプリケーションが最適化され、データベースのトランザクションの一貫性を気にする必要はありません(楽観的ロックで充分です)。一つの永続コンテキストに何千ものオブジェクトを読み込まない限り、このキャッシュの効率についてあまり気にする必要はありません。
トランザクションに関連しない状態をSeamのアプリケーションコンテキストにキャッシュすることもできます。 アプリケーションコンテキストに保持された状態は、クラスタ内の他のノードにはもちろん見えません。
トランザクションの状態は、JBossCacheやJBoss POJO Cache、EHCacheなどを利用するSeamのcacheProvider
コンポーネントにキャッシュできます。 これらのキャッシュがクラスタモードをサポートしていれば、この状態は他のノードにも見えます。
最後に、レンダリングされたJSFページの断片をキャッシュすることができます。 ORMソリューションの2次キャッシュと違い、データが変更されても自動的に無効になることはないので、 明示的に無効化するアプリケーションコードを書くか、適切な有効期限ポリシーを設定する必要があります。
2次キャッシュは非常に複雑な概念ですので、詳細についてはお使いのORMソリューションの文書を参照してください。この章では、cacheProvider
コンポーネントや<s:cache>
によるページ断片のキャッシュなどを直接利用する方法について説明します。
組み込みのcacheProvider
コンポーネントは以下のインスタンスを管理します。
org.jboss.cache.TreeCache
org.jboss.cache.Cache
org.jboss.cache.aop.PojoCache
net.sf.ehcache.CacheManager
不変のJavaオブジェクトであれば安全にキャッシュに置くことができ、オブジェクトはクラスタ内でレプリケーションされます(レプリケーションが有効な場合)。変更の可能性があるオブジェクトをキャッシュに持ちたい場合は、使用するキャッシュ実装の関連文書を読み、キャッシュの変更をキャッシュに知らせる方法を調べてください。
cacheProvider
を使うには、使用するキャッシュ実装の以下のjarファイルをプロジェクトに含めてください。
jboss-cache.jar
- JBoss Cache 1.4.1
jgroups.jar
- JGroups 2.4.1
jboss-cache.jar
- JBoss Cache 2.2.0
jgroups.jar
- JGroups 2.6.2
jboss-cache.jar
- JBoss Cache 1.4.1
jgroups.jar
- JGroups 2.4.1
jboss-aop.jar
- JBoss AOP 1.5.0
ehcache.jar
- EHCache 1.2.3
JBossアプリケーションサーバー以外のコンテナでJBossCacheを使用する場合は、他にも依存関係がありますので、JBossCacheのwikiページを参照してください。
SeamにEARをデプロイする場合は、キャッシュのjarファイルと設定ファイルをEARに直接含めることをお勧めします。
JBossCacheを使う場合はさらに設定ファイルが必要です。treecache.xml
に適切なキャッシュ設定を記述し、クラスパスに含めます(たとえばEJB JARファイルやWEB-INF/classes
など)。JBossCacheには恐ろしく厄介で紛らわしい設定がたくさんあるので、ここでは説明しません。詳細はJBossCacheの文書を参照してください。
treecache.xml
のサンプルはexamples/blog/resources/treecache.xml
にあります。
EHCacheは設定ファイルがなくてもデフォルトの設定で動作します。
設定ファイルを変更するには、components.xml
のキャッシュ設定を変更してください。
<components xmlns="http://jboss.com/products/seam/components"
xmlns:cache="http://jboss.com/products/seam/cache">
<cache:jboss-cache-provider configuration="META-INF/cache/treecache.xml" />
</components
>
Seamコンポーネントにキャッシュをインジェクトするには以下のように記述します。
@Name("chatroomUsers")
@Scope(ScopeType.STATELESS)
public class ChatroomUsers
{
@In CacheProvider cacheProvider;
@Unwrap
public Set<String> getUsers() throws CacheException {
Set<String> userList = (Set<String>) cacheProvider.get("chatroom", "userList");
if (userList==null) {
userList = new HashSet<String>();
cacheProvider.put("chatroom", "userList", userList);
}
return userList;
}
}
キャッシュを複数設定する場合は、components.xml
を使用してください。
<components xmlns="http://jboss.com/products/seam/components"
xmlns:cache="http://jboss.com/products/seam/cache">
<cache:jboss-cache-provider name="myCache" configuration="myown/cache.xml"/>
<cache:jboss-cache-provider name="myOtherCache" configuration="myother/cache.xml"/>
</components
>
Seamのキャッシュの利用でもっとも興味深いのは、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
には、すべてのバージョンを保存するキャッシュまたはリージョンのノードを指定します。異なるノードは異なる有効期限ポリシーを持つ場合があります。(前述の厄介な設定で指定できます。)
そして、 <s:cache>
の大きな問題は、 対象のデータがいつ変更されるか (たとえば、 新しい blog がいつ投稿されるか) を知り得ないということです。 つまり、 キャッシュされた断片は、 明示的にキャッシュから排除する必要があります。
public void post() {
...
entityManager.persist(blogEntry);
cacheProvider.remove("welcomePageFragments", "recentEntries-" + blog.getId() );
}
あるいは、変更を即座にユーザーに見せる必要がないのであれば、JBossCacheノードの有効期限を短く設定しても良いでしょう。