次の方法で共有


ツールボックス

キャッシュ、オブジェクト間マッピング、ブログ、その他

Scott Mitchell

分散キャッシュによる Web アプリケーションのパフォーマンス向上

ほとんどのデータ ドリブン Web アプリケーションでは、データベースから取得したさまざまな情報が各ページに表示されます。たとえば、Amazon.com の代表的な Web ページには製品の詳細情報が表示され、ユーザー レビュー、関連製品、買い物客に関する情報などが表示されます。そのため、Web ページが要求されると必ず、アプリケーションからデータベースに一連のクエリが発行され、ページに表示する情報が取得されます。この "対話の多い" 動作は、トラフィックの少ない Web アプリケーションではうまく機能しますが、スケール変換はあまりできません。

キャッシュは、読み取りの多いデータ ドリブン Web アプリケーションでの負荷を軽減し、スケーラビリティを向上するための最も効果的なツールの 1 つです。ASP.NET には、組み込みのキャッシュ API が含まれています。このキャッシュ API は、メモリ内バッキング ストアを使用し、時間ベースの有効期限、ファイル システムやデータベースの依存関係のような機能を含みます。Enterprise Library の Caching Application Block もあります。この機能は、ASP.NET アプリケーションの外部で使用でき、キャッシュ データが格納される方法と場所に高い柔軟性をもたらします。ただし、ASP.NET キャッシュと Caching Application Block では、いずれもキャッシュ データがローカルに格納されます。そのため、あるサーバーにキャッシュされたデータにはファーム内の他のサーバーからはアクセスできないため、Web ファーム環境のパフォーマンスは最適な状態とは言えません。1 つの選択肢は、ファーム内の 1 台のサーバーを集中管理キャッシュ サーバーに指定し、そのサーバーにキャッシュのコピーのみを格納して、そのコピーを他のサーバーと共有することです。ただし、この方法では、単一障害点や潜在的なボトルネックが発生します。

分散キャッシュを使用すると、複数のキャッシュをローカルに配置する欠点、または単一の集中管理キャッシュ ストアを利用する欠点を克服できます。簡単に言うと、分散キャッシュは、複数のサーバー間でキャッシュ ストアをレプリケートするか、パーティション分割します。これによって、Web ファーム環境のキャッシュ方法の効率が高まります。

さまざまな分散キャッシュ ツールを使用できます。最もよく利用されているツールは "memcached" (バージョン 1.2.8) です。これは、Danga Interactive 社が開発した無料のオープン ソース ツールで、LiveJournal、Wikipedia、SourceForge などの注目度の高いサイトで使用されています。memcached は LAMP スタック (Linux、Apache、MySQL、PHP) で最初に開発されましたが、コミュニティによって作成された利用可能な Windows ポートと .NET ライブラリがあり、ASP.NET のセッション状態と統合するためのオープン ソースのカスタム プロバイダー クラスもあります。マイクロソフトは、"Velocity" というコード ネームの独自の分散キャッシュ ライブラリの開発に取り組んでおり、このコラムの執筆時点では Community Technology Preview として利用できます。また、ScaleOut Software 社の ScaleOut StateServer と ScaleOut SessionServer、Alachisoft 社の NCache など、市販の分散キャッシュ ツールもあります (NCache については、2007 年 10 月号: msdn.microsoft.com/ja-jp/magazine/cc163343.aspx で説明しました)。

分散キャッシュ ツールの使用を開始するには、まず、分散キャッシュのトポロジを定義する必要があります。memcached を使用する場合は、キャッシュ データを格納するコンピューターで、コマンド ライン スイッチによってキャッシュ サイズなどのパラメーターを指定して、memcached サービスまたはアプリケーションを起動するだけです。Velocity やほとんどの市販の提供物には、トポロジを作成および管理するためのコマンド ライン アクセス インターフェイスとグラフィカル ユーザー インターフェイスが用意されています。

分散キャッシュからの読み取りと分散キャッシュへの書き込みのパターンは、ASP.NET の組み込みのキャッシュ API で使用されるものと同様です。どちらの種類のキャッシュも巨大なハッシュテーブルとして動作し、キャッシュ内の各項目は一意の文字列で参照されます。図 1 の擬似コードは、データがキャッシュから読み取られる方法を示しています。"Get" ステートメントが実行されると、分散キャッシュ ライブラリによって、キャッシュされた項目がトポロジ内のどこに存在するかが特定され、データが取得されます。キャッシュ内のデータの有効期限が切れたか、データがユーザー コードによってまたは依存関係のために削除されたか、メモリの制約のために削除された可能性があるため、クライアント アプリケーションでは、データがキャッシュ内に存在することを想定できません。代わりに、クライアント アプリケーションによって必ず、データがキャッシュから返されたかどうかが確認されます。項目が見つからない場合は、再び取得してキャッシュに追加する必要があります。

図 1 一般的な "Get" 要求

Function GetUserProfile(UserID)
UserProfile = Cache.Get(
“UserProfile" + UserID)
If UserProfile = NULL Then
UserProfile = _
Database.GetUserProfile(UserID)
Cache.Add("UserProfile" +
UserID, UserProfile)
End If
Return UserProfile
End Function

データが更新されるたびに、キャッシュ内のそのデータへの参照は古くなります。古いデータが表示されないようにするには、キャッシュされたすべての参照を削除または更新する必要があります。図 2 には、サイトにアクセスしているユーザーがプロファイルを更新したときに実行される擬似コードが含まれています。この方法では、各インスタンスのデータベースが更新されるだけでなく、新しいデータに関連付けられているキャッシュ項目も更新されます。キャッシュ内に新しいデータを保持する他の技法としては、有効期限やキャッシュ依存関係などがあります。

図 2 一般的な "Update" 要求

Function UpdateUserProfile(UserProfile)
Database.UpdateserProfile(UserProfile)
Cache.Update("UserProfile" +
UserProfile.UserID, UserProfile)
End Function

キャッシュは、スケーラブルなデータ ドリブン Web アプリケーションの構築に不可欠な構成要素です。Web ファームを使用する、トラフィックの多い大規模な Web サイトの場合、分散キャッシュを使用してパフォーマンスを最大限にすることを検討してください。memcached や Velocity などのツールには、キャッシュを操作するための使いやすい API が用意されており、これらのツールによって、分散キャッシュの管理、更新、およびアクセスに関する低レベルの詳細がカプセル化されます。

memcached:

danga.com/memcached (英語)

Velocity:

msdn.microsoft.com/ja-jp/data/cc655792.aspx

注目のブログ

私が購読しているほとんどの技術関連ブログは、ASP.NET、AJAX、Web 設計など、日常使用するテクノロジを中心に取り上げたものです。しかし、なるべく他の分野の専門家によるブログも探して読むようにしています。私が考える専門家とは、知識だけでなく実務経験も豊富で、その知恵をテクノロジにあまり詳しくない開発者にも明確かつ有意義な方法で共有させてくれる人物です。


Udi Dahan のブログ: udidahan.com/?blog=true

Udi Dahan はこの定義にぴったりの人物です。Dahan は、分散システムのソフトウェア アーキテクチャと設計に関する講演者、トレーナー兼コンサルタントで、数々の大規模エンタープライズ向けサービス指向アプリケーションに従事してきました。彼は、自身のブログ、オンライン リソース サイト、および MSDN Magazine (msdn.microsoft.com/ja-jp/magazine/dd569749.aspx および msdn.microsoft.com/ja-jp/magazine/cc663023.aspx 参照) で自身の考え方を示しています。

Dahan のブログにアクセスしたことがなければ、まず、「First time here?」 (初めての方へ) ページ (udidahan.com/first-time-here、英語) をご覧ください。このページでは、サービス指向アーキテクチャ (SOA)、ドメイン モデル、およびスマート クライアント アプリケーションに関する記事の中から最も人気のある記事、ブログ記事、インタビュー、および Web キャストを紹介しています。また、Dahan の公開コンテンツへのリンクが記載されている「Articles」(記事) ページ (udidahan.com/articles、英語) もご覧ください。SOA を実装する準備が整ったら、NServiceBus をチェックしてください。これは、Dahan が無料で提供しているオープン ソースの .NET アプリケーション用メッセージング フレームワークです。

NServiceBus:

nservicebus.com (英語)

Udi Dahan のブログ:

udidahan.com/?blog=true (英語)

オブジェクト間マッピングに役立つユーティリティ

オブジェクト指向アプリケーションのアーキテクチャは、オブジェクトおよびオブジェクト指向の原則 (継承、モジュール化、カプセル化など) を使用して、実環境で問題のある領域をモデル化します。販売時点管理アプリケーションでは、Employee、Customer、OrderItem、Product のようなクラスが用意されます。図 3 は、これらのクラスから構成して Sale オブジェクトをモデル化するようすを示しています。

図 3 Sale オブジェクトの一部

public class Sale {
public Employee SalesPerson { get; set; }
public Customer Customer { get; set; }
public IEnumerable<OrderItem> Items {
get; set; }
}
public class OrderItem {
public Product Product { get; set; }
public int Quantity { get; set; }
public decimal Discount { get; set; }
}

このようなモデルはアプリケーション ドメイン内ではうまく機能しますが、ドメイン外にデータを移動する場合には理想的とは言えない場合があります。たとえば、このアプリケーションにはビジネス パートナーに販売データを公開する Windows Communication Foundation (WCF) サービスがあるとします。このサービスは Sale オブジェクトのコレクションを返すことができますが、こうしたオブジェクトには公開したくない余分なデータが含まれています。Sale オブジェクトに含まれている Employee オブジェクトや Customer オブジェクトには、社会保障番号や支払い明細などの機密情報が含まれているかもしれません。OrderItem に含まれる Product には、送信ペイロードのサイズを不必要に大きくする、あまり重要ではない詳細情報が含まれているかもしれません。

一般にこうした問題を克服するには、SalesDTO、EmployeeDTO、CustomerDTO といったデータ転送オブジェクト (DTO) を定義します。これらの DTO には、共有してもかまわない一連のプロパティを含めます。サービスのコードでは、Sale や Employee などのドメイン モデルを内部処理することになりますが、データを返す前に、それぞれ対応する DTO を作成し、それらの DTO にドメイン オブジェクトから適切なプロパティのみを設定します。

DTO マッピング コードにドメイン オブジェクトを記述する作業は面倒です。いつもそのようなオブジェクト間マッピングのコードを記述している方は、"AutoMapper" バージョン 0.3.1 をチェックしてください。AutoMapper は無料のオープン ソース ユーティリティで、わずか 2 行のコードで、あるオブジェクトを別のオブジェクトに自動的にマップできます。

まず、Mapper.CreateMap メソッドを呼び出し、Mapper.CreateMap*<SourceType, DestinationType>*() のような形式で、マッピング元とマッピング先のオブジェクトの型を指定します。これによって、2 つのオブジェクト型間のマッピングを定義する MappingExpression オブジェクトが作成されます。入れ子になった型 (図 3 参照) がある場合、マップする必要がある型ごとに 1 回 CreateMap メソッドを呼び出します。

マッピングの作成後、Mapper.Map メソッドを呼び出し、Mapper.Map*<SourceType, DestinationType>*(sourceObject) のような形式で、Mapper.Map メソッドにマッピング元のオブジェクトを渡します。Map メソッドが、マッピング先オブジェクトのプロパティをマッピング元オブジェクトの対応するメンバーに割り当てて、マッピング先オブジェクトのインスタンスを返します。

図 4 は、エンド ツー エンドの例を示します。図 4 では、Product と ProductDTO の 2 つのオブジェクトを定義しています。これは、WCF サービスのコードです。Product オブジェクトは ProductDTO オブジェクトにマップしてからクライアントに返す必要があります。このマッピングが、わずか 2 行のコードで AutoMapper の Mapper クラスによってどのように実行されるかに注目してください。

図 4 指定した製品に基づいて新しい ProductDTO オブジェクトを作成する、AutoMapper の Mapper.Map メソッド

public ProductDTO GetProduct(Guid productId) {
Product product = Product.Load(productId);
// Map Product onto a ProductDTO object
Mapper.CreateMap<Product, ProductDTO>();
ProductDTO productDto =
Mapper.Map<Product, ProductDTO>(product);
return productDto;
}

AutoMapper を使用すると、Product オブジェクトのリストを ProductDTO オブジェクトの配列にマップするなど、型のコレクション間でマップすることもできます。

現実には、マッピング元とマッピング先のオブジェクトの型間でプロパティ名またはプロパティの型を適切に関連付けることが常に可能なわけではありませんが、AutoMapper の場合は問題ありません。1 行のコードを使用して、マッピング元のオブジェクトの型のプロパティをマッピング先のオブジェクトの型の別の名前のプロパティに割り当てることができます。マッピング対象のプロパティの型が関連付けられていない場合、.NET Framework の適切な型コンバーターがあれば、AutoMapper によって、マッピング元のプロパティの型をマッピング先のプロパティの型に自動的に変換できます。そのような型コンバーターが存在しない場合は、カスタム型コンバーターを作成できます。

価格:無料 (オープン ソース)

codeplex.com/AutoMapper (英語)

Scott Mitchell は、多数の著書を出版し、4GuysFromRolla.com の創設者でもあります。また、1998 年からは Microsoft MVP としてマイクロソフトの Web テクノロジに携わっています。現在は、フリーのコンサルタント、トレーナー、およびライターとして活躍しています。Scott の連絡先は、Mitchell@4guysfromrolla.com (英語のみ) です。また、ScottOnWriting.net (英語) にブログを公開しています。