スケーラブルで “強い” WCF サービスの開発

こんにちは。

本投稿のテーマは、WCF サービスの開発で、”スケーラブルなサービス”、”ミッションクリティカルなサービス”、”結構大きなサービス” を作りはじめると、「いろいろと問題に遭遇する」という話題です。ここ数日で、実はこの話題が何度か取り上げられることがありましたので記載しておきます。

この内容は、実は 一昨年の Tech Ed (2 回前の Tech Ed) でお話したものですが、月日が経ち、皆さんも “本格的なサービス開発” に取り組まれてきて、同様の問題で “はまる” ケースも多くなってきているでしょう。Microsoft の開発ツールは常にそうですが、ウィザードなどで簡単に構成してくれますが、一方で、こうしたツールが構成するものは “初期開発を簡素化するための雛型” であって、トランザクション数の多い本格的なサービスを構築する場合にはチューニングが必要になってきます。よくある問題としては、先日ユーザーの方 (WCF サービスのノウハウをお持ちの開発者の方) も言われていましたが、「サービス (IIS) が応答しなくなる」とか、「異常に遅くなる」などのトラブルに遭遇することがあります。

ここで、一昨年前の Tech Ed でご紹介した 「WCF / WF を活用した実践的アプリケーションの開発」 の内容をあえて 今 おさらいしておきたいと思います。

必要最小限度の構成を

ここは、もっとも大切なポイントです。 (いろいろあるので、少し長くなりますが。。。)

Visual Studio が自動で作成する WCF サービスは、既定では WsHttpBinding を使用した構成になっています。この WsHttpBinding ですが、メッセージレベルのセキュリティ、分散アトミックトランザクションへの対応、などなど、数々の非機能要件に対処できる優れたバインディングですが、その一方で、「本来必要ない余計な処理」をおこなって重くなっている場合もあるので注意してください。

まず「バインディング」について正しく理解しておきましょう。WCF の通信メカニズムは、OSI のプロトコルスタックのように、「認証をおこなう」→「暗号化する」→「MTOM でエンコードする」→「HTTP で通信する」 といった各手順の組み合わせになっています。メッセージを受信した際には、この逆の処理がおこなわれます。(実際には、OSI とはまったくレイヤが異なります。あくまでも例として記載していますのでご注意を。) この各ステップを組み合わせたものが「バインディング」です。つまり、既定で存在している BasicHttpBinding、WsHttpBinding、NetTcpBinding などは、この各ステップが既に組み込まれた状態になっています。
もし皆さんが、「そろそろ WCF の仕組みもわかってきて、いろいろチューニングを」と思われるならば、CustomBinding もお勧めです。CustomBinding は、この各ステップを自分で組み合わせて通信する方法で、ある意味、上述のような「既定の余計な設定」を含んでおらず、必要なもののみを組み合わせることができます (勿論、自分で組み合わせる必要があるので、相応の知識が必要です)。例えば、以下は、バイナリでデータを渡し、HTTP で通信をするだけの、軽量な「myLightWeightBinding」という名前の独自のバインディングを構成しています。

<bindings>
  <customBinding>
    <binding name=”myLightWeightBinding”>
      <binaryMessageEncoding />
      <httpTransport />
    </binding>
  </customBinding>
</bindings>

このカスタムバインディングを使わずに上述した既存のバインディング (WsHttpBinding など) を使って構成した場合には、チューニングの際に、既定で「オン」になっている数多くのプロパティなどを順番に「オフ」にしていく必要がありますが、カスタムバインディングを使えば、カスタムバインディングでは逆に、「必要なもののみをくみ上げていく」ことになるためチューニングもしやすく、かつ「何が使われているか」といった理解性もよくなります。

カスタムバインディングを使用せずに既定の構成を使用する場合は、不要な既定値はオフにしておいてください。例えば、各バインディングでは、接続 (Connection) の最適化のために、以下の既定値が設定されていますので、このあたりもチューニングに際してよく問題にされるポイントです。

  • 例えば BasicHttpBinding では、既定で HTTP の KeepAlive の設定がおこなわれており、一定のルールで接続されたままの状態になっています (つまり、これが原因で余計なサーバーリソースを消費している場合があります)。処理のスタイルを考慮し、断続的で頻繁な接続などが不要な場合には、この設定はかえって重くなりますので、keepAliveEnabled = false としてこの既定値をオフにしておいてください。
  • また WsHttpBinding では、上記の KeepAlive と同様に、セキュリティコンテキストと呼ばれるセッションのコンテキストが既定で保持されていて、セッション (状態を維持した処理) などで活用されています。これも不要な場合には、EstablishSecurityContext = false として「オフ」にしておきましょう (ただし false にすると、当然、「セッション」を保持した処理ができなくなります)
  • NetTcpBinding では、最適化のため、内部でコネクションが Pool  されていおり、既定では 5 分間保持されています。このプールが不要な場合も、カスタムバインディングを作成し、leaseTimeout でこのリリース時間をカスタムに設定する必要があります。

また、例えば、netTcpBinding では、認証として、既定で「Windows 認証」が使用されているなど、 「接続」(Connection)という観点以外にも認証、セキュリティ設定などの配慮が必要になります。

パフォーマンスだけに注目するならば、トランスポートレベルのバインド要素としては tcpTransport が最も高速で (注 : 同一ノード内なら namedPipeTransport がもっとも早そうに思われるかもしれませんが、必ずしもそうではありません)、エンコーディングについては、無論、BinaryMessageEncoding が早くなります。 

また、こうした WCF の構成以外にも、.config 内のその他の余計な構成 (例えば、よくあるケースは、使ってもいないのに Windows 認証がそのまま入っている、とか) は、パフォーマンス劣化という観点以外に、可読性という観点でもちゃんと削除しておきましょう。

クライアントサイドも必要最小限度に

クライアントサイドも似た留意点があてはまります。WCF をこれからはじめる多くの方が、下図のように [サービス参照の追加] によってクライアントプロキシを構成し、プログラミングをおこなうことでしょう。(私も、初学者の方向けの Web キャストではこの方法を紹介しています。)

この方法は無論、「いけないこと」ではありませんが、もし、クライアントからも呼び出しが頻繁におこなわれ、この点での最適化 (チューニング) も必要になりそうな大規模なアプリケーションの場合には、ChannelFactory を使用して自身で .config を構成して接続する方法をお勧めします。
パフォーマンスの面でも ChannelFactory を使用したほうが高速に処理されますが、よく陥る問題としては、複数のサービスへの接続や、頻繁な [サービス参照の更新] (サービス側のコントラクトが変更され、クライアント側でプロキシを作り直し) によって、.config がぐちゃぐちゃになってしまう (使っていない構成などが大量に残り、最終的に可読性が低下する) ことがあり、最悪なケースでは、そうしたぐちゃぐちゃな構成をそのまま本番用に適用する結果となり、メンテナンスやチューニングに際して大きなオーバーヘッドが発生するためです。

まめなテストと現象の解析

さらに、”大きなサービス” を構成する場合には、負荷テスト (及び、必要に応じ解析) も欠かさないようにしてください。構成が大きくなって原因が特定しづらい状況になってからでは遅い場合があります。以下でもご紹介しているように、Visual Studio のロードテストを使用すると、この一見「大変そうな」負荷テストも、プログラマーがいつでも簡単に実施することができます。

IT Pro 道場自主トレシリーズ : 負荷テストの実施
http://technet.microsoft.com/ja-jp/events/dd696124.aspx

普段からまめにテストを実施することで、「・・・の構成を入れたら動かなくなった」など、問題の特定もピンポイントになりますので、是非大きな開発ではこまめなテストを実施してプロジェクトを進めてください。

また、現象を解析する際に、WCF 内の細かな動き (上述したような、WCF 内のスタックの動き、など) を解析する必要がある場合には、トレースを出力して、Visual Studio (.NET Framework SDK) に付属しているサービストレースビューアツール (svctraceviewer.exe) を使用すると、内部の呼び出しのスタックや処理時刻などが確認できます。(メッセージのトレースも出力すると、どこでどの内容のメッセージが飛んだか確認することもできます。) 問題なく動作しているケースなどと比較して、「認証関連でやたらと時間がかかっている」など、どの処理がオーバーヘッドになっているか細かに確認できます。

さらに慣れてきたら、WCF Load Test Tool なども活用して、より効率的なテスト計画と反復テストをおこなうこともできます。

ワーカープロセスの最適化

WCF サービスでは、.svc を使って IIS にホストするケースも多いでしょう。この場合、ロードテスト (負荷テスト) により、前述したように、ワーカープロセスが肥大化して、応答しなくなってしまうケースがたまに発生することがあります。
こうした場合には、まず、ワーカープロセスの状態を確認してください。IIS 7 を使用されている場合には、インターネットインフォメーションサービスマネージャ (IIS 管理マネージャ) を起動して、下図の通り、ワーカープロセス内で実行されているスレッドや、それぞれのスレッドの状態などを確認できますので、この方法で問題を特定できる場合があります。(例えば、認証処理で止まっている、とか。) 

問題を特定したら、上述のように構成 (.config) を編集するなどして可能な最適化を実施し、それでもスレッドが追いつかない (スレッドが蓄積されて要求を処理しきれない) 場合には、負荷分散などを考慮する必要があります。

負荷の分散 (ロードバランス) 

要求が大量な場合、サービスに処理が蓄積され、要求をさばききれないケースがあります。この場合には、負荷の分散を検討する必要があります。

IIS ホストの場合には、非常に簡単にできる負荷分散の方法として、上述のワーカープロセス数を変更する手法があります。インターネットインフォメーションサービスマネージャ (IIS 管理マネージャ) のアプリケーションプールの詳細設定画面で、簡単にワーカープロセスの最大数を変更することが可能であり、既定ではこの値は「1」 になっています。(最大数を変更すると、要求が増えていくと、ワーカープロセス (w3wp.exe) が増加し、複数のプロセスで要求が処理されるようになります。)

一方、セルフホストの場合などには、下記の通り、スロットルを構成して処理のスループットを向上させる手法も可能です。

<behavior name=”Throttled”>
<serviceThrottling maxConcurrentCalls=”1″
    maxConcurrentSessions=”1″
    maxConcurrentInstances=”1″ />
</behavior>

また、ハードウェアロードバランシングや、NLB を構成するなどして、マシンをスケールアウトさせて処理を分散させることも、勿論、可能です。(StockTrader と呼ばれる WCF を使用した著名なサンプルでは、アプリケーション自身が要求を別のマシンに委譲させるような仕組みを独自に構築していますが、一般の負荷分散の仕組みを使用することも可能です。)

ただし、こうした負荷の分散をおこなうと、ステートを保持しているサービス (前の処理の結果によって次の結果が変わるようなサービス) では、要求ごとにプロセスが変わってしまい、期待しない結果を招く場合があるため、この制御をちゃんとおこなう必要が生じます。

補足 (2011/05/09 追記) : 下記の .NET Endpoint チームブログに、Concurrency に配慮した WCF サービス構築に関するより細かな留意点が掲載されていますので、是非参考にしてみてください。
[.NET Endpoint Team Blog] WCF scales up slowly with bursts of work
http://blogs.msdn.com/b/endpoint/archive/2011/05/04/wcf-scales-up-slowly-with-bursts-of-work.aspx

ロードバランスされた状況での、状態 (ステート) の保持

そこで、”ロードバランスされた環境” でかつ、”ステート (状態) を効率的に維持” したいというケースが出てきますが、WCF には、こうした処理をおこなういくつかの手法があります。これについては、講演で解説の時間が充分とれなかったため “怪我の功名” と言いますか、一昨年前の Tech Ed セッションのフォローブログで記載していましたので、以下を参照してください。

http://blogs.msdn.com/tsmatsuz/archive/2008/08/27/t2-302-follow-up-wcf-asp-net-compatibility-model-stateful-n-tier-wcf-load-balancing.aspx

結論だけを述べると、いくつか手法が存在し、用途に応じて (例:永続化が必要、WF と一緒に使う、などなど) 使い分けることができますが、単にステートを維持する目的であれば、ASP.NET 互換モード (ASP.NET Compatibility Mode) の手法がもっとも “軽量” に動作します。

なお、”簡易な WCF サービス” で単に状態 (ステート) を維持したいだけであれば、Session を使用した WCF サービスを構築 (PerSession で WCF を構成) できますので、上記のような面倒な考察は不要です。(“ロードバランスされた環境 (かつ、スティッキーでないケース)” という前提で記載しています。なお、「WCF のセッション」では、Web アプリケーションの Session オブジェクトは使用されていないため注意してください。Web アプリケーションの Session オブジェクトなどを使用したい場合に、上記の ASP.NET 互換モードを使用します。)

補足 (2011/05/09 追記) : 上記 (リンク) は WF 3.5 までのエンジンをベースに検証していますが、WF 4 (.NET 4 の WF) 以降で状態を保持するには、こちらに記載している Correlation を使用してください。 
また、WF 4 以降では、それまでの WF に比べて非常に軽量になっています。(パフォーマンスについては、以前、こちら に掲載した通りです。) さらに、この WF 4 に Windows Server AppFabric を組み合わせて使用すると、独自のロック機構による複数マシンでの負荷分散シナリオへの対応や、障害発生時のワークフロー (長時間実行されているワークフロー) の再ロードなどの分散環境における高可用性シナリオにも完全に対応しています。
新しく WF 4 の使用を検討されている方は、是非評価しなおしてみてください。

RESTful サービスの考慮

さて、話がどんどん面倒なほうに行ってしまいましたが、パフォーマンスや最適化を柔軟に構成する上で、REST による WCF サービスも視野に入れておくと良いでしょう。REST は、無論、 パフォーマンスをあげるために存在するアーキテクチャスタイルではありませんが、例えば、以下のような観点で、上述のような考察も “簡素化” してくれるというメリットがあります。特に商用サービスを提供するケースなどでは、他アプリからの接続性という観点など、さまざまなメリットもあわせて享受できる場合があります。

  • REST スタイルでは、処理のスタイルが明快です。例えば、上述したようなステート (状態) の維持といった観点は、実は、アーキテクチャスタイルを再設計すれば本来は不要かもしれません。 
  • このため、チューニングも容易です。例えば、IIS の設定を変更するなどして、”キャッシュ” (Cache) を有効活用できます。

 

上記は「チューニングのすべて」ではありませんが、いくつか代表的な手法をピックアップして紹介しました。一昨年前の Tech Ed でもお話しましたが、WCF は、実は、高速に処理できるように過去のテクノロジーよりもさまざまな観点で最適化されています。データの受け渡しも、XmlDocument を使用しないストリームによるパース、接続情報のキャッシュ、など、さまざまな最適化を内部で実施しています。しかし、上述した構成の話など、条件によっては、過去のテクノロジーより遅くなってしまうケースがあるので注意してください。(例えば、DataSet などの型定義がゆるい情報の受け渡しでは、こうしたメリットが損なわれてしまいます。バインディング構成によっても遅くなります。)

 

 

One thought on “スケーラブルで “強い” WCF サービスの開発

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s