つかびーの技術日記

(情報)工学修士, 元SIer SE, 現Web系 SEの技術blogです。Scala, Java, JS, TS, Python, Ruby, AWS, GCPあたりが好きです。

spray-canのサンプルにabコマンド打つと16000reqくらいで止まる

   

題名通りです。

abコマンドで自分のサーバに対して負荷をかけるとなぜか止まってしまう・・・という話です。

自分の今回の場合はspray-canという高速HTTPサーバライブラリで発生しました。これを起動してabコマンドで大量にリクエストを投げるとなぜか16000リクエストを投げたあたりで止まります。

結論としては以下のどれかで解決します。詳しく知りたい人は続きを読んでください。読んだ方が良いと思います。

  1. sudo sysctl -w net.inet.tcp.msl=1000 でTIME_WAITを短くする
  2. wrkコマンドを入れてこちらを使う
  3. そのままabコマンド使うが-kオプションを足す

spray-canサンプル

https://github.com/spray/spray/tree/master/examples/spray-can/simple-http-server/src/main

このあたりにあります。これを適当にコピって来て、libraryDependenciesにspray-canとakka-actorを追加すれば動きます。

サンプルを見れば分かりますが、/pingというエンドポイントがあるので、ここにHTTP GETすれば結果が取れます。

実際にココに対して適当にabコマンドを叩くと・・・

[tsukaby@tsukamac tsukaby]% ab -n 100 -c 10 http://127.0.0.1:8080/ping
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient).....done


Server Software:        spray-can/1.3.2
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /ping
Document Length:        5 bytes

Concurrency Level:      10
Time taken for tests:   0.031 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      15476 bytes
HTML transferred:       530 bytes
Requests per second:    3212.34 [#/sec] (mean)
Time per request:       3.113 [ms] (mean)
Time per request:       0.311 [ms] (mean, across all concurrent requests)
Transfer rate:          485.49 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.3      0       2
Processing:     1    3   1.7      2      10
Waiting:        1    2   1.5      2       7
Total:          1    3   1.6      3      10

Percentage of the requests served within a certain time (ms)
  50%      3
  66%      3
  75%      4
  80%      4
  90%      5
  95%      6
  98%      7
  99%     10
 100%     10 (longest request)

1秒あたり約3200reqさばけています。凄い。

abコマンドの問題

さて、ここでリクエスト数を増やしてみます。

[tsukaby@tsukamac tsukaby]% ab -n 20000 -c 10 http://127.0.0.1:8080/ping
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
Completed 8000 requests
Completed 10000 requests
Completed 12000 requests
Completed 14000 requests
Completed 16000 requests
apr_socket_recv: Operation timed out (60)
Total of 16368 requests completed

何度やっても16000reqくらいでtime outが発生するように・・・意味が分からない・・・

別にプログラムが落ちる訳でもなく・・・

解決方法

意味が分からんし、どうぐぐったものか・・・と思っていたのですが、意外と見つかりました。

https://groups.google.com/forum/#!topic/spray-user/fiT_mNNd8Xg

こっちはrubyのthinサーバですが、自分とほぼ全く同じ状況に

http://stackoverflow.com/questions/9156537/why-does-a-simple-thin-server-stop-responding-at-16500-requests-when-benchmarkin

sudo sysctl -w net.inet.tcp.msl=1000 を実行するか、wrkコマンドを使えばOKです。wrkはデフォルトでは入っていませんので、homebrewユーザならばbrew install wrkで入れてください。

解説

そもそもなぜ16000reqほどで止まってしまうのでしょうか。

それは空きポートが無いから、です。まずはこれを見てみます。

[tsukaby@tsukamac tsukaby]% sysctl -a | grep ip.portrange
net.inet.ip.portrange.lowfirst: 1023
net.inet.ip.portrange.lowlast: 600
net.inet.ip.portrange.first: 49152
net.inet.ip.portrange.last: 65535
net.inet.ip.portrange.hifirst: 49152
net.inet.ip.portrange.hilast: 65535

49152 port以上は特に予約されておらず自由に使えます。HTTP通信するときに受信側は当然port80で待ち受けますが、送信側は適当なポートを使います。ここで自由に使えるポートの数はというと、65535 – 49152 = 16383、という訳で約16000です。

通信なんざすぐに終わるし、使えるポートが16000でも問題ないのでは、と思いますが、実は一度使ったポートはすぐには使えません。一定の時間(TIME_WAIT)待ち時間があり、待ち時間が終わると再度ポートが使えるようになります。

なぜこうなっているかというと、遅延パケットの伝達によりアプリが狂ってしまうことを防ぐ為です。アプリAがportXで2回通信した後でportXを解放して、その直後にアプリBがportXを使ったとき、初めのアプリAに対するresponseがportXに到達したとき、アプリBが狂ってしまう可能性があります。

という訳でabコマンドで大量リクエストが送れない云々はこういう理由でした。

みなさんTCP portの空きやTIME_WAITやabコマンドには気をつけましょう。

 - サーバ , ,