ラベル IIS の投稿を表示しています。 すべての投稿を表示
ラベル IIS の投稿を表示しています。 すべての投稿を表示

2012年2月18日土曜日

ARRでの動的コンテンツのキャッシュ制御

ARR、頑張ってくれてます。立ち上げ当初は設定ミスなんかも重なっててんやわんやなこともあったけど、今となっては素晴らしいパフォーマンスを発揮してくれてます。

ARRを使うメリットとして、コネクションプーリングによるリクエストの制御の他に、SSLオフロードがかなり効果が出てます。証明書の管理も楽になるし。

ただキャッシュに関してはちょっと問題があって使ってなかったんです。基本的にVaryByCustomを使ったキャッシュ制御をしてるんだけど、どうもVaryByCustomが効いてない時があるみたい、っていう報告があって。

なので、原因がわかるまで、ARRでのキャッシュを泣く泣くオフ(もったいない!)にして、運用。

でも、やっと原因らしきものがわかった。

通常、出力キャッシュを利用する場合、自分でレスポンスヘッダを指定するなりResponseオブジェクトに指定するなり、MVCならOutputCache属性を使いましょう。ASP.NETならVaryByCustomを指定することで、自分でキャッシュ単位を細かく制御出来るようになるので、その機能を使えばPCサイトにケータイでアクセスしてきたらケータイサイトにリダイレクトしたい!なんてときに有効です(普通にキャッシュするとケータイにPCサイトが出ちゃうもんね)。

なので、↓こんな感じのコードを書いてザックリとUA判定をするようにします。

public static string GetDeviceName(HttpContextBase context)
{
	var ua = context.Request.UserAgent;
	if (string.IsNullOrWhiteSpace(ua))
		return "Unknown";

	var type = context.Request.Browser.Browser;
	if (!string.IsNullOrWhiteSpace(type) && type != "Unknown")
		return type;

	return ua.Split('/').First();
}

public override string GetVaryByCustomString(HttpContext context, string custom)
{
	if(custom != "device")
		return base.GetVaryByCustomString(context, custom);
	
	return GetDeviceName(new HttpContextWrapper(context));
}

UserAgentを見てればだいたいOKです。こうするとPCブラウザや、ケータイキャリア毎にそれっぽくキャッシュ制御できますよね。auはうるさいことになるけど。やりたい事はPCとモバイルでの判定だから、コレでほとんどうまくいきます。UAをSplict(‘/).First()だけでも大丈夫。

これを仕込んでるのと仕込んでないのとでの挙動の違いを分かりやすくテストしてみたら↓こんな感じに。Durationは10秒。

Viewの定義

@using ArrCacheTest
@{
    ViewBag.Title = "Home Page";
}

<h3>Browser Type : @MvcApplication.GetDeviceName(ViewContext.HttpContext)</h3>
<h4>User Agent : @Request.UserAgent</h4>

<div>Server Now = @DateTime.Now</div>
<div>Client Now = <span id="client_now"></span></div>

<script type="text/javascript">
    $(function (){$("#client_now").text(new Date().toString())})
</script>
arr1

すべて同じBrowser扱いだし、Server Nowが同じ。だって、キャッシュしてるHTMLが同じだから。

今度はVaryByCustomをOnにします。

arr2

ちゃんとブラウザ毎の判定になってるし、10秒以内のアクセスにもかかわらず、違うキャッシュをそれぞれのブラウザで利用。

arr3arr4

拡大しないと見えないけど、Server時間が同じなのにJSでしてるClient時間はちゃんとずれてるので、キャッシュを利用してるのがわかります。

これをARR配下に入れると、どーなるか。

ARRの設定として、ARRでリクエストを受けるサイトにポート80をバインドして、ノードサーバー用のサイトにポート8080をバインドするようにして、同一マシン内に構成。Application Request Routing Cacheを有効にして、ディスクにキャッシュもするようにしてます。

arr5

あららー。IEがFirefoxと同じキャッシュを見ちゃってます。これ、IEでアクセスする直前にChromeでアクセスするとChromeのキャッシュが出る。要するに直前のアクセスで生成されたキャッシュを利用しちゃってる。VaryByCustomどこ行った。

Chrome/Firefoxは問題なくて、ちゃんと自分用のキャッシュだけを利用。

不思議!摩訶不思議!

何がおきてこうなってるのかサッパリわからない状況だったので、ちょこちょこ設定を変えながら様子見。

最初に考えたのが、ディスクにHTMLがキャッシュされてて、それが返されてしまうんじゃないか説。でも、ディスクにはそんなものなかった。

次にIEだけ不思議とHTTP.sysのキャッシュを横取りして見ちゃうんじゃないか説。ローカルマシンでしか発生しないんじゃない?と疑って、IEだけVMからアクセスさせてみたけどそんなことなかった。

次。レスポンスヘッダのCache-Controlを見て、ARRがディスク以外のどこか(メモリしか無いけど)にキャッシュをしてしまうんじゃないか説。もしコレだとかなり厳しい。どうやってARRにHTMLだけスルーさせるのがいいか。悩んだ挙句、URL RewriteのoutboundRulesを思いついた。

<outboundRules>
    <!-- This rule changes the domain in the HTTP location header for redirection responses -->
    <rule name="CacheControl">
        <match serverVariable="RESPONSE_CACHE_CONTROL" pattern=".*" />
        <conditions>
            <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
        </conditions>
        <action type="Rewrite" value="no-cache"/>
    </rule>
    <rule name="Expires">
        <match serverVariable="RESPONSE_EXPIRES" pattern=".*" />
        <conditions>
            <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
        </conditions>
        <action type="Rewrite" value=""/>
    </rule>
    <rule name="LastModified">
        <match serverVariable="RESPONSE_LAST_MODIFIED" pattern=".*" />
        <conditions>
            <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
        </conditions>
        <action type="Rewrite" value=""/>
    </rule>
</outboundRules>            

こんなルールを用意して、レスポンスヘッダの書き換え。ちなみにコレどこに書くのかスゴイ悩んだ。applicationHost.configに書いてもまるで効かないんだよ。ARR CacheのCache Control Rulesがどこに反映されるのか、探しに探した結果、inetpub\temp\appPoolsっていうフォルダにサイトごとのconfigを自動生成して、そっちに書きこんでた。すごー。何この仕様。ビックリよ。

なので、ARRサイト(転送先のノードサイトに書いちゃうとまるでキャッシュしなくなるの意味がない)のconfigに上記ルールを追記。

追記する前のレスポンスが↓これ。

arr6

Cache-Controlにpublic, max-age=10が入っててExpiresとLast-Modifiedにそれぞれ日付が入ってますね。

追記した後のレスポンスが↓これ。

arr7

Cache-Controlがno-cacheになって、ExpiresとLast-Modifiedがなくなりました。素晴らしい!これで再度3ブラウザでアクセスしてみる。

残念!まるで効果なし!

もう、そういう仕様だと諦めたくなったけど、リクエストヘッダに違うところがあるんじゃないかと確認。

arr8

IEだけCache-Controlがno-cache。他はmax-age=0。これかなー。コレによってサーバーの挙動が変わるのかー?

と、Fiddlerで確認しようと思ったら、コンテンツ圧縮してたから内容確認できない。ショーがないないーと思って、ARRサイト/ノードサイトそれぞれの動的コンテンツ圧縮をオフにしてみた。

ら!なんと!Fiddlerで確認するまでもなくちゃんと動き出した!outboundRulesも不要。

arr9

正確にはノードサイトだけ、動的コンテンツ圧縮をオフ。ARRサイトはそのまま圧縮オン設定。

ちゃんとVaryByCustom効いてる。ナンテコッタイ。ノードから圧縮したコンテンツをARRのリバースプロキシがどっかでキャッシュしちゃってるくさい。んで、それを返しちゃってるくさい。リクエストヘッダがCache-Control:no-cacheだと。Pragma:no-cacheの時も。なんでそうなってるのかの理由はよくわからない。仕様?

ノードの圧縮をオフにして、ARRの圧縮をオンにしておけば、外向けのトラフィックは少なくなるし、ノードのCPU負荷は下がるから、これが正しい設定だよねー。

これで、やっとキャッシュも有効にして、内部トラフィックを減らしつつ、ARRで更に効率良くリクエストを捌けるようになるね!

2012年1月29日日曜日

効率のいいリクエスト処理

Webサイトの運用、大変ですよね。お疲れ様デスよ。まったく。

普通にネットワーク構成するとハードウェアロードバランサの下にWebサーバー並べて負荷分散する感じになるでしょう。処理が追いつかなかったらWebサーバー追加だぜ!とか、富豪富豪。ふぇっふぇっふぇ。でも、そんな富豪ばかりじゃない。

Application Request Routing | IIS 拡張機能 | TechNet

略してARR。NLBみたいにネットワークの負荷分散じゃなくてHTTPでのリクエスト負荷分散。ソフトウェアLB。

ARRが単なるリバースプロキシだと思ったら大間違いでした。この子かなり優秀。

そもそも、SSLオフロードさせようと思って、パフォーマンステストをしてたんですけど、思いの外Request / Secが高くなる。変だなーと思って、HTTPSじゃなくてHTTPでも試してみたら、それでもRequest / Secが高い。なんで!?と思いパフォーマンスモニターでいろいろ見てたら、どーもノードサーバーの処理が重い時にはARRがリクエストのルーティングをちょっと保留してるくさい。レスポンスタイムを見て、重くなってきたのかどーかを判断してるのかなー。それ以外に指標がないと思うので、そーじゃないかという予想。

arr1

arr2

VS のロードテスト(今はまだUltimate使えるからね)でチェックしてみました。10~250ユーザーまで徐々に増やしていく設定でWebアプリケーションの動きをシミュレートさせる。下のグラフはパフォーマンスモニタ。

黄色がCPU、青がWeb Service:Current Connections、緑がW3SVC_W3WP:Request / Sec、赤がASP.NET v2.0:Request Queued。

まずは、ARRを通さずに直接リクエストした状態です。CPUは概ねフルに使いつつ、コネクション数が段階的に増え、リクエストキューに処理待ちリクエストが溜まっていく様子です。直感的にそーなるなー、っていう動きがそのまま再現できてますね。

arr3

どんどん負荷を上げる(ユーザー数を増やす)と、キューにたまる数が増えて、グラフ上Request / Secを上回ります。これはつまり同時処理性能を超えたリクエストがWebアプリケーションサーバーに来てるっていう状態ですね。さばけないからキューイング。ASP.NETのキューイング。

最終的にすべて終了すると↓こうなって、何もかも0に戻る。

arr4

ロードテスト結果。

arr5

Request / Secが281でページ/秒の平均が26.0です。

今度は全く同じ事をARR経由させて実行します。

arr6

arr7

この段階ですでに挙動が違うんですよね。下のグラフはWebアプリケーションサーバーの状態なんですけど、コネクション数の増え方が違う。段階的なんだけど、伸びかたが明らかに違う。この時のARRの同じグラフを見てみるとどーなってるか。

arr8

なるほどね。こっちにはユーザー数と同じ増え幅でコネクション数が増えてます。

ARRからノードサーバー(Webアプリケーションサーバー)へはTCP接続を使いまわしてる?コネクションプーリング。

arr9

ユーザー数をどんどん増やしていくと、リクエストキューにたまるのが直接接続時の挙動でしたが、ARRを経由した場合、キュー(赤)がたまるのに合わせてコネクション数(青)が増えてる。関連があるような挙動ですね。

で、観測を続けると、キューの数が減少に転じてます。不思議ですね。ユーザー数は同じでリクエスト数も同じように発生してるのに、Webアプリケーションサーバーではキューがたまりません。

その時のロードテストの状態。

arr10

Request / Secは298で落ちてない。若干上がってるくらい。

arr11

最終的にテスト実行完了時点でCPUやRequest/Sec、キューの数は0に落ちるのに、コネクション数(青)はまだ0にならない。しばらく高止まり。これはつまりARRがコネクションをすぐに切ってないということですね。

arr12

Request / Secが347でページ/秒の平均が32.4.0。ページの応答時間平均にいたっては6.45から4.20ですよ!約1.5倍。これはどういうことなのかというと、Webアプリケーションサーバーの同時処理性能が下がるようなリクエストをルーティングせず、しばらくARRでキューイングしておくことで、Webアプリケーションサーバーがベストパフォーマンスを出せるように調整してるから、じゃないでしょうか。実際にWebアプリケーションサーバーのキューの数はARRを経由させたほうが少ない状態を維持し、コネクション数も少ない。何度か試した結果コネクション数が半分の時もあるのに、ページの応答時間平均は常に概ね1.5倍早い。

無理に同時実行させないことで早くレスポンスを返す。結果的にRequest/Secも上がる。SSLオフロードでWebアプリケーションサーバーのCPU利用を実処理に専念させようとしただけなのに、この結果。恐るべしARR!

ちなみにARRとWebアプリケーションサーバーが同居した場合にはまるで性能向上しませんでした。してもいいかと思ったんだけど、そういう訳でもないんですね。IISとASP.NETの双方でのCPU利用(ネットワークも思いのほかCPU利用するのかもね)を最適化するならARRは別サーバーで用意しましょう。

ARR賢い。単なるリバースプロキシじゃない。ノードサーバーはIISに限定するものじゃないので、性能判断の指標はレスポンスタイムしか無いと思うけど、その辺の情報は見つけることが出来ませんでした。

今回はARR1台、ノードサーバー(Webアプリケーションサーバー)1台なので、こういう結果でしたが、ノードサーバーの台数が増えた場合にはどのように効率化されるのかは、環境次第だと思うので、自身の環境で試してみたらどーでしょー。

もし、仮に同じような性能向上をするならWebアプリケーションサーバーが4台以上ある場合、1台をARRにしたほうがレスポンス性能がいいということになるかもよー。

目指せ、続ハイパフォーマンスWebサイト!

2011年8月28日日曜日

IIS7でnode.js

Installing and Running node.js applications within IIS on Windows - Are you mad? - Scott Hanselman

node.jsをIISで動かす!ファイル更新で自動リサイクルは楽チンでいいですね!受け口がIISだからIISのその他の便利モジュールがそのまま使えるのもいい。

install.batでやってることも紹介してくれてるので、同じ手順でIIS Expressでも大丈夫!なはず。

iisnode.dllっていうネイティブモジュールが橋渡し。よさそうな雰囲気。プロセス数の設定とか、いろいろ調整できるのも素晴らしす。

と、いうわけで、試しに動かしてみました。

インストール手順。

  1. node.jsのバイナリダウンロードしてc:\nodeにコピー
  2. iisnodeのバイナリをc:\inetpub/iisnodeにコピー
    ※ソースから自分でビルドしても可
  3. iisnode内のinstall.batを実行

以上。簡単ですね!node.exeのパスを変えたい場合は、install.batの確認場所の変更と、web.configでnodeProcessCommandLine指定でどーぞー。

node2

node

なんかいいね。6000req/sec。たいしたもんだ。ただ、iisnode.dllがちょいちょい落ちる(アプリケーションプールごと道連れ)。まだまだ発展途上。でもネイティブモジュールのスゴさを垣間見れるし、手軽にnode.jsをWindows環境で走らせることが出来ていいです!

ちなみにハンセルマンさんのエントリでも書いてる通り、AppPoolのアカウントをLocalSystemにしとかないと動きません。あと、wcatのsettings.ubrはちゃんとsettings{}で囲んでおきましょう。wcatについてはよく知りませんが(すません)、wcclientが動いてくれなくて、ちゃんと走りませんでした(手動起動で動かした)。

こんなに簡単にできるなら、何か面白そうなことを考えて試してみたり、node.jsでできることいろいろ追いかけて見たくなりますね!