SeamFramework.org Community Documentation

- 前のページ
- 次のページ

## 第23章 キャッシュ

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

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

そこでキャッシュの登場です。しかも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>`によるページ断片のキャッシュなどを直接利用する方法について説明します。

## 23.1. Seamでキャッシュを利用する

組み込みの`cacheProvider`コンポーネントは以下のインスタンスを管理します。

JBoss Cache 1.x （JBoss 4.2.x 以降や他のコンテナでの利用に適している）

  `org.jboss.cache.TreeCache`

JBoss Cache 2.x （JBoss 5..xや他のコンテナでの利用に適している）

  `org.jboss.cache.Cache`

JBoss POJO Cache 1.x （JBoss 4.2.x 以降や他のコンテナでの利用に適している）

  `org.jboss.cache.aop.PojoCache`

EHCache （すべてのコンテナでの利用に適している）

  `net.sf.ehcache.CacheManager`

不変のJavaオブジェクトであれば安全にキャッシュに置くことができ、オブジェクトはクラスタ内でレプリケーションされます（レプリケーションが有効な場合）。変更の可能性があるオブジェクトをキャッシュに持ちたい場合は、使用するキャッシュ実装の関連文書を読み、キャッシュの変更をキャッシュに知らせる方法を調べてください。

`cacheProvider`を使うには、使用するキャッシュ実装の以下のjarファイルをプロジェクトに含めてください。

JBoss Cache 1.x

  - `jboss-cache.jar` - JBoss Cache 1.4.1

  - `jgroups.jar` - JGroups 2.4.1

JBoss Cache 2.x

  - `jboss-cache.jar` - JBoss Cache 2.2.0

  - `jgroups.jar` - JGroups 2.6.2

JBoss POJO Cache 1.x

  - `jboss-cache.jar` - JBoss Cache 1.4.1

  - `jgroups.jar` - JGroups 2.4.1

  - `jboss-aop.jar` - JBoss AOP 1.5.0

EHCache

  - `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

    >

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

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ノードの有効期限を短く設定しても良いでしょう。

- 前のページ第22章 非同期性とメッセージング
- 上に戻る
- ホーム
- 次のページ第24章 Webサービス