Skip to content

Instantly share code, notes, and snippets.

@nota-ja
Last active July 6, 2018 01:54
Show Gist options
  • Save nota-ja/8082495 to your computer and use it in GitHub Desktop.
Save nota-ja/8082495 to your computer and use it in GitHub Desktop.
Gatlingを使った性能測定 (「Gorouter と Ruby Router v2 の性能比較」 その3)

 このgistは Cloud Foundry Advent Calendar 2013 の19日目の記事です。またしても大幅に遅れて本当に申し訳ありません。

 今日は,「Gorouter と Ruby Router v2 の性能比較」の3回目として,Gatling を使って Gorouterと Ruby Router v2 の負荷テストを行います。

準備

 まずは準備から。

 (準備1) Gatlingのインストール。面倒なので詳細は省きます。以下のURL等を参考にしました。

 (どうでもいい余談) Gatlingって,gatleみたいな動詞の動名詞形だと思っていましたが,Gatlingという人名に由来するものでした。知らなかった..。

 (準備2) massregistrarをさらに修正しました。主な修正点(531b5c0の箇所)は,

  • ダミーURLのドメインをオプションで指定できるようにした
  • 生成したダミーURLをファイル出力可能にした
  • ダミーDEA数と,1ダミーDEAあたりのダミーインスタンス数の上限を修正した
  • client.Greet()を行わないようにした

の4点です。

 最初の修正は,Gatlingからの(数千の)ダミーURLへのアクセスが全てrouterのVMに向かうようにするための名前解決の問題への対処を容易にするものです。異なるFQDNへのアクセスを同一のIPアドレスに向かわせるには,ロード・バランサーやラウンドロビンDNSを使うことになるのですが,今回は後者の無料サービスを行っているxip.ioを使いました。そして,これを使いやすくするために,ドメイン名を指定可能にしました。

 2番目の修正は,Gatlingのfeedで読み込むアクセス先URLのファイルの作成を容易にするためのものです。

 3番目の修正も,Gatlingでの負荷テストを行いやすくするためのもの。

 4番目の修正は,前回までの版のバグ修正になります。実はclient.Greet()を行うと,コード上で指定した時間間隔とは別に,router.registerの発行を全力で行ってしまうことがわかったので,これを行わないようにしました。client.Greet()の方を修正することも考えたのですが,修正範囲が広がってしまって advent calendar の記事で扱うには面倒なのでやめました。

測定環境のセットアップ

 準備が終わったところで,測定環境のセットアップを行って行きます。

 まずはmassregistrarを使ったrouter.registerの発行を開始します。今回想定した環境は,

  • DEA数: 100
  • 1DEAあたりのインスタンス数: 200
  • URL数: 6000

です。本当は,【1DEAあたりのインスタンス数:20】×【DEA数:1000】で総インスタンス数2万としたかったのですが,1000個ものgoroutineを走らせるのが不安だったので,総インスタンス数を変えずにDEA数を減らす方向で調整しました。

 実際に実行したコマンドは以下の通り:

bin/massregistrar -domain .192.168.14.111.xip.io -natsAddresses=192.168.14.111:4222 -natsPassword=*** -natsUsername=*** -writeUrl -numDea=100 -insPerDea=200 -numUrl=6000

 30秒間隔で2万回の'router.register'が発行され続けるので,natsサーバー(とrouter)はだいたい666件/秒のpublishを処理する計算になります。

 次に,routerを起動します。Ruby Router v2 の場合は,Ruby Router v2 とnginxを,Gorouterの場合はGorouterのみを起動することになります。起動方法については前回前々回で書いた気がするので,省略します。

測定

 最後に,Gatlingのシナリオを実行します。今回使用したシナリオはこちら:

package cfrouter

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._
import io.gatling.http.Headers.Names._
import scala.concurrent.duration._
import bootstrap._
import assertions._

class RandomUrlSimulation extends Simulation {
  val httpProtocol =
    http
    .baseURL("")

  val urls = csv("urls.csv").random
  val scn =
    scenario("Test")
    .repeat(10) {
      feed(urls).exec(
        http("test")
        .get("${url}")
        .check(status.is(200)))
    }

  setUp(scn.inject(ramp(1000 users) over (10 seconds)))
  .protocols(httpProtocol)
}

 26行目のusersの左の数字(ユーザー数)をいろいろ変えていく感じでテストを実施しました。

#当初は19行目のrepeat()の括弧の中の数字も試行錯誤して変えていましたが,最終的に10に落ち着きました。

 上の例だと,10秒間に,1人あたり10のURLへのアクセスを,1000人のユーザーが順次発行するという流れになるので,10秒間にトータル10000アクセス,平均1000件/秒のアクセスということになります。

 では,測定結果を見て行きましょう。

 性能比較,といっても何を基準にするかは迷ったんですが,Ruby Router v2 で何度か試験していると,ユーザー数の増加に伴い200以外のレスポンス(以下,この比率を「エラー率」と呼ぶ)が増えていくことがわかったので,エラー率が5%を超える直前のユーザー数で比較することにしました。

 まず,Ruby Router v2 (以下「RRv2」と略)から。エラー率5%前後のユーザー数は,前:110,後:120でした。

  • ユーザー数:110
---- Global Information --------------------------------------------------------
> numberOfRequests                                    1100 (OK=1059   KO=41    )
> minResponseTime                                        0 (OK=0      KO=480   )
> maxResponseTime                                    27620 (OK=800    KO=27620 )
> meanResponseTime                                     863 (OK=440    KO=11780 )
> stdDeviation                                        3277 (OK=147    KO=12799 )
> percentiles1                                         570 (OK=530    KO=27590 )
> percentiles2                                       26760 (OK=580    KO=27620 )
> meanNumberOfRequestsPerSecond                         13 (OK=12     KO=0     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                          1057 ( 96%)
> 800 ms < t < 1200 ms                                   2 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                41 (  3%)
  • ユーザー数:120
---- Global Information --------------------------------------------------------
> numberOfRequests                                    1200 (OK=1123   KO=77    )
> minResponseTime                                       10 (OK=10     KO=360   )
> maxResponseTime                                    26580 (OK=2220   KO=26580 )
> meanResponseTime                                    1108 (OK=473    KO=10365 )
> stdDeviation                                        3884 (OK=210    KO=11952 )
> percentiles1                                        1410 (OK=570    KO=26540 )
> percentiles2                                       26410 (OK=1410   KO=26570 )
> meanNumberOfRequestsPerSecond                         13 (OK=12     KO=1     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                          1096 ( 91%)
> 800 ms < t < 1200 ms                                   9 (  0%)
> t > 1200 ms                                           18 (  1%)
> failed                                                77 (  6%)

 これよりさらにユーザー数が多くなると,たとえば150ではエラー率は20%に,200では26%に達し,500では46%と急激に上昇することがわかりました。なお,ログを見る限り,エラー・レスポンスのレスポンス・コードは(全てまたはほとんど)404でした。

 以上より,RRvv2 のテスト結果としては,ユーザー数110が上限ということになりました。

 次にGorouterの結果を。こちらはエラー率5%前後のユーザー数は,前:330,後:340でした。

  • ユーザー数:330
---- Global Information --------------------------------------------------------
> numberOfRequests                                    3300 (OK=3141   KO=159   )
> minResponseTime                                        0 (OK=0      KO=60000 )
> maxResponseTime                                    63720 (OK=63720  KO=60540 )
> meanResponseTime                                    6219 (OK=3474   KO=60454 )
> stdDeviation                                           0 (OK=2305   KO=0     )
> percentiles1                                       59720 (OK=31530  KO=60500 )
> percentiles2                                       60490 (OK=56670  KO=60510 )
> meanNumberOfRequestsPerSecond                          9 (OK=8      KO=0     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                          2795 ( 84%)
> 800 ms < t < 1200 ms                                   2 (  0%)
> t > 1200 ms                                          344 ( 10%)
> failed                                               159 (  4%)
  • ユーザー数:340
---- Global Information --------------------------------------------------------
> numberOfRequests                                    3400 (OK=3212   KO=188   )
> minResponseTime                                        0 (OK=0      KO=60000 )
> maxResponseTime                                    63710 (OK=63710  KO=60530 )
> meanResponseTime                                    6690 (OK=3545   KO=60432 )
> stdDeviation                                           0 (OK=2425   KO=0     )
> percentiles1                                       60180 (OK=31530  KO=60510 )
> percentiles2                                       60500 (OK=56690  KO=60520 )
> meanNumberOfRequestsPerSecond                          9 (OK=8      KO=0     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                          2832 ( 83%)
> 800 ms < t < 1200 ms                                   2 (  0%)
> t > 1200 ms                                          378 ( 11%)
> failed                                               188 (  5%)

 Gorouterのユーザー数上限は,330でした。Gorouterの場合,エラー率の上昇が非常に緩やかで,ユーザー数400でも7%程度です。このことがユーザー数上限でRRv2を大きく上回った要因でしょう。

 ただ,ユーザー数の上限だけで見るとGorouterの圧勝のなのですが,結果を詳細に見ると,妙なことに気がつきます。meanResponseTimeやmeanNumberOfRequestsPerSecondでは,Ruby Router v2 の数字の方が良いのです。

  • RRv2/ユーザー数:110
> meanResponseTime                                     863 (OK=440    KO=11780 )
> meanNumberOfRequestsPerSecond                         13 (OK=12     KO=0     )
  • RRv2/ユーザー数:120
> meanResponseTime                                    1108 (OK=473    KO=10365 )
> meanNumberOfRequestsPerSecond                         13 (OK=12     KO=1     )
  • Gorouter/ユーザー数:330
> meanResponseTime                                    6219 (OK=3474   KO=60454 )
> meanNumberOfRequestsPerSecond                          9 (OK=8      KO=0     )
  • Gorouter/ユーザー数:340
> meanResponseTime                                    6690 (OK=3545   KO=60432 )
> meanNumberOfRequestsPerSecond                          9 (OK=8      KO=0     )

 これはおそらく,以下のような理屈なのではないかと考えています。

  • Gorouterは,(Gatlingの)タイムアウトぎりぎりまでレスポンスを返そうと努力する
    • その結果,レスポンスの成功数は増えるが,応答時間は平均的に長くなる
  • 一方RRv2は,(おそらくnginxが)短い時間でレスポンスを返すのをあきらめる
    • その結果,応答時間の平均は短くなるが,レスポンスの失敗数が増える

 これ,どちらがいいかは微妙なところですね..。

 なお,Gorouterのテスト中,エラーレスポンスがで始めると,Gorouterのコンソールに

2013/12/xx xx:xx:xx http: Accept error: accept tcp [::]:80: too many open files; retrying in 1s

のような出力の頻出が観察されたので,(GorouterのREADME.md(今見たら最新版には記述がないので,今回ベースにしている一つ前の版を参照)に従い)fd数の上限を増やして,もう一度テストしてみることにしました。

fd数上限2048での測定結果

 現状のfd数の上限は1024:

ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 31486
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 31486
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

 これを2048に増やします。

ulimit -n 2048
ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 31486
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 2048
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 31486
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

 この状態で,再びRRv2から負荷テストを実行していきました。すると今度は,RRv2が全くエラー・レスポンスを返さなくなりました。処理時間はかかるのですが,100%成功レスポンス(200)が返ってくるのです。この調子で5%まで行こうとすると,さすがに時間がかかり過ぎると感じたので,キリのいいところで1000ユーザーの時の性能を比較することにしました。

 まずはRRv2の結果から。

---- Global Information --------------------------------------------------------
> numberOfRequests                                   10000 (OK=10000  KO=0     )
> minResponseTime                                        0 (OK=0      KO=-     )
> maxResponseTime                                     5660 (OK=5660   KO=-     )
> meanResponseTime                                     407 (OK=407    KO=-     )
> stdDeviation                                         184 (OK=184    KO=-     )
> percentiles1                                         520 (OK=520    KO=-     )
> percentiles2                                         560 (OK=560    KO=-     )
> meanNumberOfRequestsPerSecond                         24 (OK=24     KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                          9998 ( 99%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            2 (  0%)
> failed                                                 0 (  0%)

 完走しました。meanResponseTimeは407msでユーザー数110の時(863ms)より向上し(OKレスポンスの値だけ見るとあまり変わらない:ユーザー数110の時440ms),meanNumberOfRequestsPerSecondもユーザー数110の時(13)のほぼ倍近くになりました。

 一方,Gorouterの結果はどうなったかというと,

---- Global Information --------------------------------------------------------
> numberOfRequests                                   10000 (OK=10000  KO=0     )
> minResponseTime                                        0 (OK=0      KO=-     )
> maxResponseTime                                    55170 (OK=55170  KO=-     )
> meanResponseTime                                    3008 (OK=3008   KO=-     )
> stdDeviation                                        7573 (OK=7573   KO=-     )
> percentiles1                                       27650 (OK=27650  KO=-     )
> percentiles2                                       32360 (OK=32360  KO=-     )
> meanNumberOfRequestsPerSecond                         16 (OK=16     KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                          8684 ( 86%)
> 800 ms < t < 1200 ms                                   5 (  0%)
> t > 1200 ms                                         1311 ( 13%)
> failed                                                 0 (  0%)

 同じく完走したのですが,性能指標的には, meanResponseTime:3008ms, meanNumberOfRequestsPerSecond:16 と,RRv2に完敗しました。Gorouterのコンソールには,時々先に述べたtoo many open filesが出ていたので,その影響もありそうです。RRv2とは出力が異なるので安易な推測は危険ですが,GorouterはRRv2(というよりnginx)に比べて,fd数上限の影響を受けやすい傾向にあるようです。

まとめ

 ということで,3回に亘って書いてきた「Gorouter と Ruby Router v2 の性能比較」ですが,結果はまさかのRRv2(というよりおそらくnginx)強し,というものでした。特定の一環境の,しかも1回の測定だけの結果なので,これだけで断定するのは危険ですが,RRv2,というよりnginxのパフォーマンスには,やはり一日の長があるといった感じでしょうか。ただ造り的にはかなりわかりやすくなった(RRv2だとRuby,nginxのconfig,luaの3種類の文法を扱わなければならなかったのが,GorouterではGoだけになった)こともあり,やはり今後はGorouterかと思うのですが,(ulimit系も含めた)パフォーマンス・チューニングはもうちょっと詰めていく必要がありそうです。

 では,長々とお付き合いありがとうございました。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment