Hatena::ブログ(Diary)

スティルハウスの書庫 このページをアンテナに追加 RSSフィード

2009-10-27

今度はServletContext+UUIDで負荷分散状況を調べてみた #appengine

@higayasuoさんのつぶやき:

#appengine でリクエストを処理するスレッドは1インスタンスあたり1つという仮定は正しい。ただし、インスタンスの特定にRuntimeのhashCodeを使うのは間違いでFilterなどで起動時にServletContextにUUIDなどを突っ込んで調べるのが正しい
#appengine でJavaのRuntimeは多くのアプリケーションで共有されていて同じアプリケーションの複数インスタンスで共有されることもある

なるほど、、そこで今度は、Runtime.getRuntime().hashCode()の代わりに、ServletContextにUUIDを入れて識別する方法でTask Queueのコンテナインスタンス単位の負荷分散状況を調べてみました。こんなコードをサーブレットに書いて、ランダムIDを取得します。

final ServletContext sc = super.getServletContext();
String instId = (String) sc.getAttribute("instId");
if (instId == null) {
	instId = UUID.randomUUID().toString();
	sc.setAttribute("instId", instId);
}

これに対してTask Queueで69個のタスクを実行し、得られたログからidを集計した結果がこちら:

[091f09e0-5255-4208-a6d7-4df64bd70f2a] 6
[22920a1f-17ec-4d8b-be3f-4a5881518903] 6
[26171bd4-8f66-46cd-a754-758b93bd71b3] 6
[32a23443-8c65-411a-b984-714fbccc958e] 3
[3753cb1d-da36-4765-b320-97d585f804f8] 5
[3c29e854-f25c-4816-b0f6-1c23f4f366e7] 4
[586ed664-3696-48a7-9fdb-10661309c7c9] 5
[5925465f-33c7-4f43-92ee-c34bc15c44b5] 3
[81e017c5-ea84-46d3-9f8e-1db420cc26bd] 3
[83a78921-7d7c-4939-b8d9-a6fa6f8a844c] 4
[a5cc6e5e-94ea-4c04-b7ad-6c7941baedf1] 1
[ac1f62b7-053f-4f7e-8ba7-c0eca0f21f02] 5
[ad3981bc-c7cb-4eb9-bee6-a6c077bd68c5] 7
[af789ef5-aecc-408f-a265-07847c4d2919] 1
[c42db46a-427a-4ec5-b5a3-30e37cbd7888] 4
[dcb34116-7d10-4302-b1be-8a52aed21bed] 6

総合計	69

今回も前回と同様に、16個のコンテナインスタンスに対して負荷分散してる状況がわかります。すると1JVM=1コンテナインスタンスってことなのかなぁ。。

追記

ひがさんのコメントでご指摘いただいた点(識別の対象がコンテナではなくアプリケーションのインスタンスである点)を修正しました。ついでに、RuntimeのハッシュコードとServletContextのUUIDのペアでも集計してみました(r=がRuntime、i=がインスタンス)

[r=10292103:i=ca08a349-6bed-4ccc-9951-288e08b6bc1b] 4
[r=1119993:i=90228109-e8e6-496e-a1d2-884544b701b7] 6
[r=1122818:i=84ae4f57-135f-4ac2-ba3c-0745cd222190] 2
[r=13620718:i=56dce314-e413-4c4f-9164-33b8e17afd0e] 6
[r=14624872:i=5ef591f9-f411-4fa2-9e92-e0eb268dbd81] 7
[r=1496906:i=a60606ab-c065-4f3d-baa3-e18ba34532ad] 6
[r=17350388:i=1563cf15-59da-4150-8f05-0743fd337b3d] 6
[r=21924150:i=571a0e16-cd07-4031-8097-281ba9d5e584] 9
[r=30299896:i=2ad9a29c-d2b9-4bdc-9441-97ff92c0ea78] 6
[r=30652173:i=905cae70-56b7-4c99-8c4e-ec51d082c0ab] 4
[r=33245819:i=f2859bbb-fc04-4061-befb-13dc994e5082] 3
[r=3598249:i=985ae732-d0cc-457c-869a-26eabe096032] 5
[r=3872307:i=51f066a5-7b95-4682-a101-73e1ae0be752] 4
[r=5141948:i=60be17e8-61f0-4420-a9e9-b9edd1c800bf] 1
総合計	69

このデータを見る限りは、

  • 1JVM = 1インスタンス

という対応付けになっているようです。一方、ひがさんが指摘されてたように、appengineのすべてのアプリでリクエストのThread IDは「11」に統一されてます。

#appengine どのアプリのThreadのidも11だしhashCodeも28409161っておかしくない? ThreadのidとhashCodeみんなも調べてみて

これは私の環境でも確認できました。どのアプリでもThreadのidは11で、hashCodeは28409161で固定されています。そのため、

#appengine でリクエストを処理するスレッドは1インスタンスあたり1つという仮定が正しいとする根拠は、Threadのidが同じVMでかぶることはないから

よって、

  • 1リクエスト=1インスタンス

ということになります。てことは、たしか@WdWeaverさんが推測されてたように、個々のリクエストを個別のJVMに割り振ってるのかな〜〜という気がしてきました。

さらに追記

ひがさんからのコメントをふまえて、ここまでの理解をメモっておきます:

  • RuntimeのhashCodeはJVMプロセスを識別しており、上記例では14のJVMプロセスに負荷分散していると考えてよいだろう
  • JVMプロセス内部では複数のスレッドが存在するが、個々のアプリのインスタンスのリクエストはそのいずれか1つで処理される。
    • アプリからみてどのスレッドもid=11に見えるのは、処理順序がいつも決まってるためで、おそらく作為的なものではないだろう

higayasuohigayasuo 2009/10/27 20:43 そのテストでわかることは、アプリケーションが16のインスタンスに負荷分散されているということです。

コンテナというのがWebApplication Server(Jetty)をさしているなら、1つのJettyが複数のインスタンスを担当するので必ずしも16のコンテナに分散されているとは限りません。
どのJettyが使われているのかは、おそらくJavaのRuntimeのhashCodeで判断できるはずです。

OSGiで管理されているので、通常のJavaのjarやJettyのjarは別のバンドルとして管理されていてアプリケーションのバンドルとは切り離されています。
Javaのjarはずっとそのままで、アプリケーションをdeployするとアプリケーションのバンドルだけが更新されます。

複数のアプリケーションがJavaだとかJettyのバンドルを共用しているのです。

higayasuohigayasuo 2009/10/27 21:14 アプリケーションの1インスタンスは1(OSGiの)バンドルで、1つのJVMの中に複数のバンドルが共存します。

間違った例えですが、普通のアプリケーションサーバに複数のwarをデプロイしたときは、1つのJVMに複数のwar用のClassLoaderが割り当てられますよね。あれと似た感じです。

kazunori_279kazunori_279 2009/10/27 21:19 丁寧な解説ありがとうございました!OSGiの仕組みよく分かってませんでした。。修正ついでにRuntimeのハッシュと合わせて集計してみました。

higayasuohigayasuo 2009/10/27 21:37 私の説明も正確じゃなくてごめんなさい。
アプリケーションから見たJVMはすべて違うものに見えますが、実際は、サーバー(コンテナ)のJVMがアプリケーションごとに違うClassLoaderとして分けて使われているだけです。
リクエストを処理するThreadはアプリケーション側のClassLoaderにロードされていて、それは1つで固定ですが、1つで固定なのは、アプリケーションから見たJVMの話で、サーバーのJVMから見るとThread自体は複数存在するはずです。

私のところで動かしたアプリで、RuntimeのhashCodeが同じ(同じサーバー)でUUID(インスタンス)が違うThreadが存在してましたから。

kazunori_279kazunori_279 2009/10/27 21:49 そうですね、JVM全体がシングルスレッドということはないですよね。JVM内の複数のスレッドのいずれかがアプリのClassLoaderに入り、それをアプリからみると全部id=11に見える、と。

以下の理解で合ってますでしょうか?ちょっと自信ありません。。

- RuntimeのhashCodeはJVMプロセスを識別しており、上記例では14のJVMプロセスに負荷分散している
- JVMプロセス内部では複数のスレッドが存在するが、個々のアプリのインスタンスのリクエストはそのいずれか1つで処理される。アプリからみるとどれもid=11に見える(ように細工されてる?)

higayasuohigayasuo 2009/10/27 22:48 RuntimeのhashCodeはサーバーのJVMごとに違う可能性が高いですが、偶然かぶることもあると思います。まぁhashですから。ただ、RuntimeのhashCodeでサーバーのJVMを区別しても間違うことは少ないでしょう。

個々のアプリのインスタンスは別々のClassLoaderで処理され、そこではThreadが9(ThreadGroupのactiveCount)つ起動され、そのうちid=11のThreadでリクエストは処理されています。
これは細工ではなく、いつも決まった順序でオブジェクトが起動されているんでしょう。hashCodeも同じなので。

ためしにJavaのmainメソッドで
System.out.println(new Object().hashCode());
とだけ書いて実行してみてください。
たぶん、毎回同じ値が表示されるはずです。

hashCodeもClassLoaderごとなんですよ。
個々のアプリごとにClassLoaderが異なるので、同じidで同じhashCodeでもJVMプロセス的には別のThreadなのです。

kazunori_279kazunori_279 2009/10/27 23:08 > これは細工ではなく、いつも決まった順序でオブジェクトが起動されているんでしょう。

> ためしにJavaのmainメソッドで

やってみました。本当ですね! なるほど、作為的なidじゃなくて、順序が決まってるからidも同じになると。

いろいろアドバイスいただきありがとうございます〜。appengine奥が深い。。

higayasuohigayasuo 2009/10/28 11:59 ごめんなさい。twitterでも書きましたが、昨日書いた複数のアプリケーションでJavaのRuntimeが共用されるというのは間違いでしたm(_ _)m Threadのidはstaticフィールドで連番で管理されているので共用されたら同じidにはならないためです。

違うインスタンスでRuntimeのhashCodeが同じケースもたまにありますが、偶然だと思われます。

最初に佐藤さんが言っていた1インスタンス1JVMで正しいと思います。

kazunori_279kazunori_279 2009/10/28 13:16 ひがさん、ご丁寧にお知らせいただきありがとうございます。なるほど、となるとThreadのidが同じということは複数インスタンス同居の可能性は低いですね。。皆さんからの情報でApp Serverの中身がおぼろげに分かって来たような気がします。

はてなユーザーのみコメントできます。はてなへログインもしくは新規登録をおこなってください。

トラックバック - http://d.hatena.ne.jp/kazunori_279/20091027/1256642767