AppFabric キャッシュ

実際の使用法と統合

Andrea Colaci

Microsoft Windows Server AppFabric (以前のコードネーム "Velocity") は、Web アプリケーションにもデスクトップ アプリケーションにも統合できる、分散キャッシュを提供します。AppFabric により、パフォーマンス、スケーラビリティ、および可用性が向上しますが、開発者の観点から見れば一般的なメモリ キャッシュのように動作します。DataSet、DataTable、バイナリ データ、XML、カスタム エンティティ、データ転送オブジェクトなど、シリアル化可能なオブジェクトであれば何でもキャッシュできます。

AppFabric のクライアント API はシンプルで使いやすい API です。サーバー API は、1 つ以上のキャッシュ サーバー (およびキャッシュ クラスターで構成される複数のサーバー) を管理できる、完全な機能を備えた分散リソース マネージャー (DRM) を提供します。各サーバーは、メモリ クォータ、オブジェクトのシリアル化と転送、領域のグループ化、タグ ベースの検索、および有効期限を独自に提供できます。また、キャッシュ サーバーでは、高可用性、つまりセカンダリ サーバーにオブジェクトのレプリカを作成する機能もサポートします。

MSDN マガジン 2009 年 6 月号の記事では、Aaron Dunnington が Windows Server AppFabric の概要をわかりやすく紹介しています (msdn.microsoft.com/magazine/dd861287)。今回の記事では、AppFabric キャッシュを、デスクトップ アプリケーションや Web アプリケーションに統合する方法について説明します。この中では、ベスト プラクティスをいくつか概説したり、Microsoft .NET Framework 4 および ASP.NET 4 の新機能を利用するうえでのヒントをいくつか紹介したりします。また、分散キャッシュを使用するときに生じる一般的な問題の解決方法についても調べます。

以下に示すコード サンプルはすべて、Codeplex (velocityshop.codeplex.com、英語) から入手できる、"Velocity Shop" という完全なデモ ソリューションのコードを使用しています。

ここで紹介する Windows Server AppFabric は、Windows Azure Platform AppFabric とは異ります。Windows Azure テクノロジに関する詳細については、microsoft.com/japan/windowsazure/appfabric を参照してください。

はじめに

最新の Windows Server AppFabric Beta 2 Refresh は、開発用に何とおりかの方法でインストールできます。Web Platform Installer (microsoft.com/web/downloads) を使用すると、構成可能なインストールを 1 回行うだけで、さまざまな Web 開発アプリケーションおよびフレームワークを簡単にセットアップできます。その上、Web Platform Installer は、サポート対象のアプリケーションやフレームワークが新しくリリースされるとそれを含むように更新されます。

AppFabric のみをインストールする場合は、Windows Server デベロッパー センターの Windows Server AppFabric ページ (msdn.microsoft.com/windowsserver/ee695849、英語) にある、最新版へのリンクを使用します。

セットアップが完了したら、AppFabric キャッシュを使用する準備はほぼ整います。次の手順では、名前付きキャッシュを作成します。これは、データの格納に使用する論理コンテナーです。この作成には、Windows PowerShell の New-Cache コマンドレットを使用します。

New-Cache -cacheName Catalog

アプリケーションで AppFabric キャッシュを使い始めるには、Visual Studio プロジェクトに CacheBaseLibrary.dll、CASBase.dll、CASMain.dll、および ClientLibrary.dll への参照を追加するだけです。

クライアント ライブラリは単純です。次のコードでは、分散キャッシュにアクセスしてから、名前付きキャッシュにアクセスし、オブジェクトを格納または取得する方法を示しています。

cacheCluster = new DataCacheServerEndpoint[1];
cacheCluster[0] = new DataCacheServerEndpoint(
  "ServerName", 22233, "DistributedCacheService");
DataCacheFactory factory = 
  new DataCacheFactory(cacheCluster, true, false);
DataCache cache = factory.GetCache("Catalog");

// Putting a product in cache
cache.Put("Product100", myProduct);

// Getting Product from cache
Product p = (Product)cache.Get("Product100");

AppFabric キャッシュに取り組む前に、簡単な計画を立てることをお勧めします。まず、キャッシュをどのようにセットアップするかを検討します。セットアップによって、アプリケーションで使用できるキャッシュの機能が決まります。

最初に、先ほど説明したように、プロジェクトの名前付きキャッシュをセットアップします。このとき、有効期限と通知のポリシーを独自に設定することができます。オブジェクトやコレクションには、それぞれ異なるキャッシュ期間が必要になることがあります。また、メモリの負荷が高いときは、キャッシュから適切に削除されるようにします (場合によっては、削除されないようにします)。作成する名前付きキャッシュに、絶対的な有効期限のタイムアウトを設定するには、New-Cache コマンドレットに TTL パラメーターを追加します。

名前付きキャッシュの作成と同時に、領域の構成をお勧めします。この領域は、キャッシュ内でサブグループのように動作し、オブジェクトの整理や、キャッシュ内でのオブジェクト検索プロセスの簡略化に使用できます。たとえば、アプリケーションで顧客の電子機器のカタログを処理するとします。Catalog キャッシュを作成して、製品を Televisions (テレビ)、Cameras (カメラ)、および MP3 Players (MP3 プレイヤー) という名前の領域に分割します。領域は実行時にしか作成できません。作成するには、データの cache.CreateRegion メソッドを使用して、領域の名前を指定します。

// Always test if region exists;
try {
  cache.CreateRegion("Televisions", false);
}
catch (DataCacheException dcex) {
  // if region already exists it's ok, 
  // otherwise rethrow the exception
  if (dcex.ErrorCode != DataCacheErrorCode.RegionAlreadyExists) 
    throw dcex;
}

同じ名前の領域が既に存在すると、DataCacheException がスローされるため、適切な try-catch ブロックを使用する必要があることを覚えておいてください。

では、機能単位に製品を検索する必要がある場合は、どうすればよいでしょうか。このような場合に便利な AppFabric キャッシュのもう 1 つの機能が、タグ ベースの検索です。この機能は、領域を使用している場合にのみ利用可能で、キャッシュに格納される各項目に 1 つ以上のタグを付けて、その後の検索に使用できます。

たとえば、Televisions 領域で、"LED-Panel" (LED パネル) と "46-Inches" (46 インチ) というタグの付いた製品をすべて検索するとします。これは、GetByAllTags メソッドを使用して、DataCacheTag のリストを指定するだけで実行できます。タグ ベースの検索の例を次に示します。

DataCacheServerEndpoint[] cacheCluster = GetClusterEndpoints();
DataCacheFactory factory = 
  new DataCacheFactory(cacheCluster, true, false);
DataCache cache = factory.GetCache("Catalog");
IEnumerable<KeyValuePair<string, object>> itemsByTag = 
  cache.GetObjectsByTag(
  new DataCacheTag("LED-Panel"), "Televisions");

キャッシュ層を既存のアプリケーションまたは新しいアプリケーションに導入するときに、考慮すべき共通のポイントもあります。キャッシュ層により、キャッシュに適した候補のデータ型を特定および分類できます。キャッシュ層には、次のような 3 つのカテゴリがあります。

  • 参照データ: ほとんど変更されることがない、読み取り専用データが含まれます。国名の一覧、通常は在庫がある製品のカタログ、製品のデータシートなどです。
  • アクティビティ データ: ユーザー単位またはセッション単位に変化する可能性のあるデータが含まれます。ショッピング カート、欲しいものリストなどです。
  • リソース データ: 頻繁に変化し、ユーザー アクティビティ以上に影響を受けやすい情報です。顧客からの注文を受けた後の商品在庫の変動などです。

こうした分類は、名前付きキャッシュにそれぞれ有効期限や通知のポリシーを指定する際に役立ち、効率的かつ合理的にリソースを使用することができるようになります。キャッシュ サーバーをクラスターに追加できる場合でも、メモリは常に限りあるリソースであることを覚えておいてください。

キャッシュのライフサイクル

アプリケーションの起動時は、キャッシュが空になります。それでも、ユーザーは Web サイトにアクセスする可能性があります。このような場合にキャッシュが機能できるようにするには、どうすればよいでしょう。

要求されたデータがキャッシュに存在しない場合、キャッシュ対応のアプリケーションでは、cache-aside パターンを使用して、SQL Server データベースなどの固定記憶域に切り替えることができる必要があります。データの使用頻度が高いシナリオでこの操作を行うと、特に、大量の参照データを処理する場合や、キャッシュを読み込む際の一意性が保証されない場合は、負荷が高くなる可能性があります。そこで、オブジェクトのキャッシュをテストした後、複数のスレッドで記憶域からデータを読み込み、その後の要求のためにキャッシュにデータを格納する段階が重要になります。キャッシュされていないデータを取得したり、必要以上に頻繁にデータをキャッシュしたりすると、パフォーマンスが低下する可能性があるためです。

これには、IIS 7.5 の自動開始サービスなどが役に立ちます。AppFabric を使用しているのであれば、リーダー ロックを設定すると、同じような結果が得られます。自動開始サービスを有効にするには、次のようにして、applicationHost.config に一部変更を加える必要があります。

<serviceAutoStartProviders>
  <add name="PrewarmMyApp" 
       type="MyWebApp.PreWarmUp, MyWebApp" />
</serviceAutoStartProviders>

次に、カスタム コードを使って Preload メソッドを実装し、名前付きキャッシュに参照データとリソース データを読み込みます。

using System.Web.Hosting;
namespace DemoApp {
  public class WarmUp : IProcessHostPreloadClient {
    public void Preload(string[] parameters) {
      // Cache preload code here...
    }
  }
}

これで、Preload メソッドが完了した後でしか、Web アプリケーションを使用できなくなります。

図 1 のようなサーバー ファーム シナリオでも、キャッシュを読み込むために個別に固定記憶域にアクセスする、コールド スタートのアプリケーションの問題があります。ASP.NET キャッシュは、既定ではアプリケーションを実行している appDomain に結び付けられるため、一度しかキャッシュが読み込まれることはありません。ただし、AppFabric では、キャッシュが Web サーバー間で共有され、結果として Web アプリケーション間で共有されることになるため、キャッシュを同時に読み込むのは避ける必要があります。

image: AppFabric Cache in a Server Farm
図 1 サーバー ファームにおける AppFabric キャッシュ

単一のサーバーにキャッシュを読み込むようにするオプションはいくつかあります。そのうちの 1 つが、AppFabric Beta 1 で導入されたリーダー ロック機能です。この機能を使用すると、キャッシュ項目をキャッシュする前に、そのキャッシュ項目のキーをロックできます。最初に、キーをロックするコードを事前に読み込まなければ、関連するデータの読み込みを開始できません。これは、キャッシュの読み込み中に使用するキーを、使用前に予約するのと同じです。この技法を使用すると、複数の Web サーバーにキャッシュの読み込みを分散することもできます。予約した特定のキーに関連するデータを読み込むように、事前に各サーバーを構成しておくことができます。

キャッシュがいつ読み込まれるかを把握するのは、キャッシュ クラスターなどの分散リソースではよくある同期の問題です。AppFabric キャッシュを使用するのであれば、キャッシュの準備がいつ整うのかを判断する方法はたくさんあります。その中の 1 つに、サーバーで、そのサーバー専用に予約されたキー データを読み込んだ後、共通のキーをポーリングするという方法があります。他にも、キャッシュの準備が整ったという通知を受け取る方法、個別のサービスやアプリケーションで事前読み込みフェーズを実行する方法などがあります。こうした方法を実現できるのは、キャッシュが分散され、Web アプリケーションからでも、デスクトップ アプリケーションからでもアクセスできるようになったためです。

また、図 2 のように、.NET Framework 4 の System.Threading.Tasks.Parallel クラスを利用して、キャッシュを並列に読み込むことができます。

図 2 キャッシュの並列読み込み

// load catalog items from DB
SQLServerCatalogProvider SQLServerCatalogProvider = 
  new SQLServerCatalogProvider();
itemsInCategory = 
  SQLServerCatalogProvider.GetItemsByCategoryId(categoryId);
_helper.CreateRegion(categoryId);

Parallel.ForEach(itemsInCategory, item =>{
  // put each catalog item in cache with tags
  if (item.Tags==string.Empty)
    _helper.PutInCache(item.ProductId, item, categoryId);
  else
    _helper.PutInCache(item.ProductId, item, categoryId, item.Tags);
});

// Code from Helper class
public void PutInCache(string key, object value, 
  string region, string tags) {

  List<DataCacheTag> itemTags = new List<DataCacheTag>();

  foreach (var t in tags.Split(',').ToList())
    itemTags.Add(new DataCacheTag(t.ToLower()));
  _cache.Put(key, value, itemTags , region);
}

AppFabric キャッシュの今後のリリースでは、キャッシュスルーの機能が検討されています。この機能により、データがキャッシュに存在しなければ、自動的にカスタム コードを実行してキャッシュを読み込んだり、反対に、キャッシュ内の情報が更新された場合は、データを固定記憶域に保存したりできるようになるでしょう。

ASP.NET 統合

ASP.NET プロバイダー モデルにより、開発者は InProc、StateServer、および SQLServer の 3 つから、セッション プロバイダーを選択することができます。AppFabric キャッシュを使用すれば、技術的には 4 つ目のセッション プロバイダーが利用可能です。ただし、セッションとキャッシュを混同しないように注意してください。キャッシュの目的はパフォーマンスを向上することで、セッションの目的はアプリケーションをステートフルにすることです。

AppFabric キャッシュに用意されている、ASP.NET 用のセッション プロバイダーでは、(可用性が向上する可能性のある) 分散キャッシュを、ASP.NET セッションのリポジトリとして使用します。開発者は特に意識することなく、既存のコードの機能を停止しないでこのセッション プロバイダーを使用できます。このようなプロバイダーを使用すると、ASP.NET セッションがプロセス外で AppFabric キャッシュに格納されるため、Web サーバーがクラッシュしたりオフラインになった場合でも、セッションが保持されます。

AppFabric キャッシュをインストールして構成したら、ASP.NET セッションを格納するための名前付きキャッシュを作成する必要があります。次に、図 3 のように、Web.config に変更を加えて、DataCacheSessionStoreProvider を有効にします。

図 3 AppFabric キャッシュにおける ASP.NET セッションの有効化

<?xml version="1.0"?>
<configuration>
  <configSections>
    <section name="dataCacheClient" 
      type="Microsoft.Data.Caching.DataCacheClientSection, CacheBaseLibrary" 
      allowLocation="true" allowDefinition="Everywhere"/>
    <section name="fabric" 
      type="System.Data.Fabric.Common.ConfigFile, FabricCommon" 
      allowLocation="true" allowDefinition="Everywhere"/>
    <!-- Velocity 1 of 3 END -->
  </configSections>
  <dataCacheClient deployment="routing">
    <localCache isEnabled="false"/>
    <hosts>
      <!--List of services -->
      <host name="localhost" cachePort="22233" 
        cacheHostName="DistributedCacheService"/>
    </hosts>
  </dataCacheClient>
  <fabric>
    <section name="logging" path="">
      <collection name="sinks" collectionType="list">
        <!--LOG SINK CONFIGURATION-->
        <!--defaultLevel values: -1=no tracing; 
            0=Errors only; 
            1=Warnings and Errors only; 
            2=Information, Warnings and Errors; 
            3=Verbose (all event information)-->
        <customType 
          className="System.Data.Fabric.Common.EventLogger,FabricCommon" 
          sinkName="System.Data.Fabric.Common.ConsoleSink,FabricCommon" 
          sinkParam="" defaultLevel="-1"/>
        <customType 
          className="System.Data.Fabric.Common.EventLogger,FabricCommon" 
          sinkName="System.Data.Fabric.Common.FileEventSink,FabricCommon" 
          sinkParam="DcacheLog/dd-hh-mm" defaultLevel="-1"/>
        <customType 
          className="System.Data.Fabric.Common.EventLogger,FabricCommon" 
          sinkName="Microsoft.Data.Caching.ETWSink, CacheBaseLibrary" 
          sinkParam="" defaultLevel="-1"/>
      </collection>
    </section>
  </fabric>
<appSettings/>
<connectionStrings/>
<system.web>
  <sessionState mode="Custom" customProvider="Velocity">
    <providers>
      <add name="Velocity" 
        type="Microsoft.Data.Caching.DataCacheSessionStoreProvider, ClientLibrary" 
        cacheName="session"/>
    </providers>
  </sessionState>
...

AppFabric キャッシュは、サーバー部分では .NET Framework 4 が必要ですが、クライアント ライブラリは .NET Framework 3.5 でも .NET Framework 4 でも機能するため、既存の ASP.NET 3.5 アプリケーションに統合できます。

ASP.NET 4 でのもう 1 つの拡張ポイントに、出力キャッシュがあります。ASP.NET 1.0 の時代から、出力キャッシュを使用して、生成したページやコントロールの出力を、メモリ内キャッシュに格納できました。それ以降の要求では、再びこのような出力を生成しなくても、メモリから取得することができます。ASP.NET 4 では、出力キャッシュのカスタム プロバイダーを 1 つ以上構成することがサポートされます。AppFabric の出力キャッシュ プロバイダーは、ASP.NET 4 のリリース後に使用可能になります。OutputCacheProvider クラスを拡張したり、このクラスが有効になるように Web.config に変更を加えて、自由にプロバイダーを構成することができます。

ORM 統合

最もよく利用されているオブジェクト リレーショナル マッピング (ORM) フレームワークには、第 2 レベルのキャッシュと呼ばれる機能、エンティティを格納するリポジトリ、構成可能な時間量に関するコレクションおよびクエリ結果が用意されています。エンティティが固定記憶域から読み込まれるときに、ORM ではまず、第 2 レベルのキャッシュをテストして、そのエンティティが既に読み込まれているかどうかを確認します。既に読み込まれていれば、データベースにアクセスすることなく、要求されたエンティティのインスタンスが呼び出し側のコードに渡されます。

ORM の各実装には、エンティティの関連付け、コレクション、およびクエリ結果の処理に関する、それぞれ固有の戦略があります。これは、第 2 レベルのキャッシュにも同じように当てはまります。使用する ORM によっては、第 2 レベルのキャッシュをカスタマイズしたり使用したりするためのオプションが制限され、使用中のキャッシュの変化に特定の方法で対処しなければならないかもしれません。たとえば、有効期限のポリシーとキャッシュの依存関係をカスタマイズするのが困難な場合は、オブジェクトの競合が頻繁に発生する可能性があるため、キャッシュの効率が低下します。

NHibernate と Entity Framework では、どちらも AppFabric キャッシュを第 2 レベルのキャッシュとして使用できます。Nhibernate では nhibernate.caches.velocity を通じて、この機能を使用することができます (sourceforge.jp/projects/sfnet_nhcontrib/)。Entity Framework では、Jaroslaw Kowalski が開発した EFCachingProvider を通じて使用することができます (code.msdn.microsoft.com/EFProviderWrappers、英語)。

AppFabric の使用

これまで説明したように、Windows Server AppFabric キャッシュを使用すると、新しいアプリケーションまたは既存のアプリケーションで、クラスター レベルのキャッシュを簡単に実行できます。キャッシュを最大限に利用するには、適切なデータ オブジェクトの候補を特定する必要があります。ただし、ローカル サーバー キャッシュでは既に特定されている可能性があります。また、有効期限と通知のポリシーも必要になります。

AppFabric は Windows Server 2008 のライセンスがあれば使用できます。また、以下のサイトからダウンロードして使用することもできます (msdn.microsoft.com/windowsserver/ee695849、英語)。AppFabric は、Windows Azure でもキャッシュの役割を果たします。

AppFabric をダウンロードして、単一サーバーののキャッシュ クラスターまたはマルチサーバーのキャッシュ クラスターをセットアップし、必要に応じて、CodePlex の Velocity Shop をダウンロードおよび実行し、ソリューション内に示されるすべての AppFabric 機能をすぐに評価およびご利用ください。

Andrea Colaci は、コンサルタントやトレーナーの経験が 10 年以上あります。最新の言語や開発ツールに強い関心があり、情熱を持って取り組んでいます。

この記事のレビューに協力してくれた技術スタッフの Scott Hanselman に心より感謝いたします。