Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

Vert.x がいいね!(第3回:Event LoopsとVerticle Instances)

id:KenichiroMurataです。

f:id:acro-engineer:20131221154226j:plain:w200

皆さん、Vert.x 使っていますか?

前回の「Vert.x がいいね!(第2回:開発環境を構築する) - Taste of Tech Topics」に続き、今回はVert.xの肝であるEvent LoopsとVerticle Instancesについて書きます。

目次は以下の通りです。

  1. Vert.x's Threads
  2. Event Loops
  3. Verticle Instances
  4. Event Loops & Verticle Instances
  5. Performance Test

1. Vert.x's Threads

まずはVert.xのスレッドがどのようになっているのか確認してみましょう。前回も利用したHelloWorldVerticleを利用して、リモートデバッグによってbreak pointで停止させ、動作しているスレッドを見てみます。

f:id:acro-engineer:20130822025227p:plain

図の通り、main threadが1個、vert.x-eventloop-threadが0〜3の4個、vert.x-worker-threadが0〜19の20個が動作していることが分かります。

今回はeventloop-threadに注目したい訳ですが、デフォルトで4個のeventloop-threadが動作しています。ここがEvent Loopが1つのnode.jsとVert.xが異なる点ですね。

2. Event Loops

さて、ではなぜeventloop-threadは4個なのでしょうか。これはVert.xのソースを確認すると分かります。見るべきソースはVertxExecutorFactory.javaです。

  public static EventLoopGroup eventLoopGroup(String poolName) {
    int poolSize = Integer.getInteger("vertx.pool.eventloop.size", Runtime.getRuntime().availableProcessors());
    return new NioEventLoopGroup(poolSize, new VertxThreadFactory(poolName));
  }
Runtime.getRuntime().availableProcessors()

とある通り、Vert.xはCPUのプロセッサ数を判別して、その環境にあったeventloop-threadの数になるように実装されています。

実際に私の環境で以下のように確認してみるとcpu.thread_countが4なので、2coreのハイパースレッディングで4core相当に見えるために、eventloop-threadも4個になっているようです。

ken@my-vertx-module $ sysctl -a machdep.cpu
(略)
machdep.cpu.core_count: 2
machdep.cpu.thread_count: 4

3. Verticle Instances

一方で、公式サイトのドキュメントを見ると、vertxコマンドでVerticleやModuleを実行する際に、「-instances」オプションを指定することで、VertilcleやModuleのインスタンス数を指定することができ、コア数に応じてアプリケーションをスケールすることができるぜ!と書かれています。

ここは試しに何も考えず、「-instances 10」と指定してvertxコマンドを実行してみましょう。

ken@my-vertx-module $ vertx runmod com.mycompany~my-module~0.0.1 -cp bin -instances 10
Verticle start. 
Verticle start. 
Verticle start. 
Verticle start. 
Verticle start. 
Verticle start. 
Verticle start. 
Verticle start. 
Verticle start. 
Verticle start. 

ご覧の通り、10個のHelloWorldVerticleのインスタンスが生成され、動作していることが分かります。これも実際にVert.xのソースを確認すると分かります。見るべきソースはDefaultPlatformManager.javaです。

      for (int i = 0; i < instances; i++) {
        // Launch the verticle instance
        Runnable runner = new Runnable() {
          public void run() {
            Verticle verticle;
            try {
              verticle = verticleFactory.createVerticle(main);
            } catch (Throwable t) {
              handleDeployFailure(t, deploymentID, aggHandler);
              return;
            }
            try {
              addVerticle(deployment, verticle, verticleFactory, modID, main);
              setPathResolver(modID, modDir);
              DefaultFutureResult<Void> vr = new DefaultFutureResult<>();
              verticle.start(vr);
              vr.setHandler(new Handler<AsyncResult<Void>>() {
                @Override
                public void handle(AsyncResult<Void> ar) {
                  if (ar.succeeded()) {
                    aggHandler.complete();
                  } else {
                    handleDeployFailure(ar.cause(), deploymentID, aggHandler);
                  }
                }
              });
            } catch (Throwable t) {
              handleDeployFailure(t, deploymentID, aggHandler);
            }
          }
        };

        if (worker) {
          vertx.startInBackground(runner, multiThreaded);
        } else {
          vertx.startOnEventLoop(runner);
        }
      }

この他、関連する以下のソースを見るとだいたいの動作が分かります。

簡単に書くと、Verticleを生成する際に、EventLoopGroupから1つのeventloop-threadを取得し、対象のVerticleに対応づけて、その組み合わせを保持しています。この処理によって、1つのVerticleインスタンスは最初に対応づけられたeventloop-threadからしか呼ばれないことが保証されます。

Verticleを開発する場合に、他のスレッドから呼ばれることがないため、同期や排他などを考えることなく、シンプルなコードを書くことができる、というVert.xの特長はこのように実現されています。

4. Event Loops & Verticle Instances

ここまでの内容から、Event LoopsとVerticle Instancesの関係について整理してみましょう。

  • Event Loopsは環境のコア数によってスレッド数が決まる
  • Vertcile Instancesは「-instances」オプションの指定によってインスタンス数が決まる
  • 1つのVerticleインスタンスが生成される際に、Event Loopsから1つのeventloop-threadが割り当てられる

例えば、4個のeventloop-threadに対して、10個のVerticleインスタンスを生成した場合、

  1. vertx-eventloop-thread-0
    • Vertcile-0
    • Vertcile-4
    • Vertcile-8
  2. vertx-eventloop-thread-1
    • Vertcile-1
    • Vertcile-5
    • Vertcile-9
  3. vertx-eventloop-thread-2
    • Vertcile-2
    • Vertcile-6
  4. vertx-eventloop-thread-3
    • Vertcile-3
    • Vertcile-7

このような割り当てとなります(あくまで例です)。

ここで考えてみましょう。

Verticleを複数インスタンス生成して処理をスケールさせたいとしても、実際の所はEvent Loopsのスレッド数以上にはマルチに処理できないため、Event Loopsのスレッド数以上のVerticle Instancesを指定しても効果がないと考えられます。

5. Performance Test

そこで実際に性能計測を行ってみましょう。私の環境ではeventloop-threadは4個なので、Verticleのインスタンス数を1、2、4、8と変化させた場合の処理性能を計測し、その違いを確認したいと思います。(注意:この計測はあくまでも私の環境で、各条件での処理性能を相対的に確認するものであり、Vert.xの性能値を評価するものではありません)

5.1 事前準備

測定に使うのは、再三登場しているHelloWorldVerticleです。クライアントからの要求をシミュレートするために、Http Performance exampleのClientを利用します。

また、OSのチューニングとして、maxfiles、somaxconnを適当な大きさに設定しています。

ken@my-vertx-module $ launchctl limit
(略)
	maxfiles    65535          65535          
ken@my-vertx-module $ sysctl -a | grep somax
kern.ipc.somaxconn: 2048

5.2 検証手順

まずはサーバを起動します。

ken@my-vertx-module $ vertx runmod com.mycompany~my-module~0.0.1 -cp bin (-instances 2/4/8)

クライアントは別のターミナルを2つ開いて、それぞれで以下を実行します。

ken@java $ vertx run perf/RateCounter.java -cluster
Starting clustering... 
No cluster-host specified so using address 10.0.1.6 
ken@java $ vertx run httpperf/PerfClient.java -instances 4 -cluster
Starting clustering... 
No cluster-host specified so using address 10.0.1.6 

負荷をかけるため、クライアント側も「-instances 4」を指定してマルチコアを使うようにしています。
PerfClientは1インスタンス辺り100接続をするため、計400接続を同時に行って、リクエストを送信します。

5.3 測定結果

5.3.1 Verticleインスタンス数1(「-instances」指定なし)
71968 Rate: count/sec: 124666.66666666667 Average rate: 120609.15962650067
74968 Rate: count/sec: 124666.66666666667 Average rate: 120771.52918578594
77968 Rate: count/sec: 123333.33333333333 Average rate: 120870.10055407346
80967 Rate: count/sec: 125375.12504168056 Average rate: 121036.96567737473
83967 Rate: count/sec: 123333.33333333333 Average rate: 121119.01104005145
86967 Rate: count/sec: 123333.33333333333 Average rate: 121195.39595478745
89967 Rate: count/sec: 126000.0 Average rate: 121355.60816743918
92967 Rate: count/sec: 126000.0 Average rate: 121505.48043929566
95968 Rate: count/sec: 123958.68043985339 Average rate: 121582.19406468823
98968 Rate: count/sec: 125333.33333333333 Average rate: 121695.9017056018
5.3.2 Verticleインスタンス数2(「-instances 2」)
72824 Rate: count/sec: 179393.13104368123 Average rate: 168241.23915192793
75825 Rate: count/sec: 176607.79740086637 Average rate: 168572.37059017475
78825 Rate: count/sec: 178666.66666666666 Average rate: 168956.54931810973
81825 Rate: count/sec: 176000.0 Average rate: 169214.78765658417
84825 Rate: count/sec: 178000.0 Average rate: 169525.49366342468
87825 Rate: count/sec: 172000.0 Average rate: 169610.01992598918
90825 Rate: count/sec: 171333.33333333334 Average rate: 169666.94192127718
93825 Rate: count/sec: 175333.33333333334 Average rate: 169848.12150279776
96825 Rate: count/sec: 178000.0 Average rate: 170100.69713400464
99825 Rate: count/sec: 176666.66666666666 Average rate: 170298.02153769095
5.3.3 Verticleインスタンス数4(「-instances 4」)
72069 Rate: count/sec: 189270.24325224926 Average rate: 173805.6584661921
75068 Rate: count/sec: 190730.24341447148 Average rate: 174481.8031651303
78068 Rate: count/sec: 190000.0 Average rate: 175078.13700876158
81068 Rate: count/sec: 189333.33333333334 Average rate: 175605.66438052006
84069 Rate: count/sec: 191936.02132622458 Average rate: 176188.60697760174
87068 Rate: count/sec: 190063.3544514838 Average rate: 176666.51352965497
90068 Rate: count/sec: 188666.66666666666 Average rate: 177066.21663631924
93069 Rate: count/sec: 193268.91036321226 Average rate: 177588.6707711483
96068 Rate: count/sec: 193397.79926642214 Average rate: 178082.19178082192
99068 Rate: count/sec: 192666.66666666666 Average rate: 178523.84220939153
5.3.4 Verticleインスタンス数8(「-instances 8」)
70314 Rate: count/sec: 185333.33333333334 Average rate: 161646.32932275222
73315 Rate: count/sec: 186604.4651782739 Average rate: 162667.93971220078
76314 Rate: count/sec: 186728.90963654552 Average rate: 163613.4916267002
79313 Rate: count/sec: 187395.79859953318 Average rate: 164512.7532686949
82314 Rate: count/sec: 184605.1316227924 Average rate: 165245.28026824113
85313 Rate: count/sec: 186728.90963654552 Average rate: 166000.492304807
88314 Rate: count/sec: 186604.4651782739 Average rate: 166700.63636569513
91315 Rate: count/sec: 177940.68643785405 Average rate: 167070.0323057548
94314 Rate: count/sec: 179393.13104368123 Average rate: 167461.882647327
97314 Rate: count/sec: 178666.66666666666 Average rate: 167807.3041905584

5.4 まとめ

(1)に比べて、(2)では2つのeventloop-threadで処理しているので、処理性能が向上しています。

(2)と(3)では(3)の方が処理性能は確かに向上しているものの、増分は想定よりも低くなっています。
ただし、これは(2)の時点でCPU使用率が既に80〜90%近くになっており、これ以上性能を出せない状況にあることが考えられます。

(4)は(3)に比べて、処理性能が下がっています。
Verticleのインスタンス数がeventloop-threadの数よりも大きいことが逆に処理性能を悪化させているのか、マシンリソースの問題かは分かりませんが、より性能が向上しないことは分かりました。


と言うわけで、Event LoopsとVerticle Instancesの関係について確認してきました。

Vert.xによるアプリケーションの開発で処理性能を引き出したい場合には、Event Loopの数と、Vertcileのインスタンス数について、ここまで見てきた関係を頭に入れながら開発することが重要となります。

次回は?

Vert.xのテストについてその手法を確立したいので、次回はその辺りを取り上げたいと思います。