Tsuyoshin blog

所属団体とは関係なく、個人的なblog

Nginx with Goのパフォーマンスを検証した

はじめに

そろそろAPIをちゃんと切り出して運用したほうが良いよなーとか、ネイティブアプリやる時に必ず必須だしなぁーとか思ってた。 でせっかくならGolangでやった場合どんな感じになるかなと思いながらかる~く調べていた。

Golang自体の書き方云々はまぁ良いとして、実際のAPIを公開して運用するときにさてどんな構成が一番良いんだろうとかちょっと疑問に思った。

んで何通りかあって、どれが一番ベストパフォーマンスが出るんだろうなーと疑問に思ったのでそのあたりのbenchmarkとったのでまとめる

検証環境

f:id:tsuyoshi_nakamura:20161222222201j:plain

  • NginxでまずはHTTP通信を受ける
    • GoのフロントにNginxを置くのはキャッシュ、Logging等々、インターネットからのアクセスを受け付ける役割としてはNginxを使ったほうが最終的に良いだとうと最終的になりそうだったので入れた
  • その後、Golangに投げて、Golangではmysqlからサンプルデータをselectしてレスポンスするという環境にしている
    • Hellow Worldでは無くて少しでも実際のパターンに近づけた?
  • nginx version: nginx/1.10.2
    • worker_processes 1;
    • worker_connections 1024;
    • keepalive_timeout 65;
  • go version go1.7.4 darwin/amd64
  • gomaxprocs=4 いつからのバージョンからはわからないけど動かしているマシンのCPUの数がセットされる様子
  • 自身のローカルマシーンのmacで完結させている

検証ツール

GitHub - wg/wrk: Modern HTTP benchmarking tool

GitHub - tsenart/vegeta: HTTP load testing tool and library. It's over 9000!

検証パターン

NginxとGoをつなぐ方法として下記のパターンを試した

Go(http.ListenAndServe) + Nginx(proxy)

一番サンプルででてくるパターンかな

vegetaでの検証結果

echo "GET http://localhost:8081" | vegeta attack -rate=100 -duration=30s | tee results_8081.bin | vegeta report

Requests      [total, rate]            3000, 100.03
Duration      [total, attack, wait]    29.996695502s, 29.98999987s, 6.695632ms
Latencies     [mean, 50, 95, 99, max]  8.262946ms, 6.36174ms, 16.602405ms, 20.103509ms, 249.542167ms
Bytes In      [total, mean]            486000, 162.00
Bytes Out     [total, mean]            0, 0.00
Success       [ratio]                  100.00%
Status Codes  [code:count]             200:3000
Error Set:

cat results_8081.bin | vegeta report -reporter='hist[0,2ms,4ms,6ms]'

Bucket         #     %       Histogram
[0,     2ms]   0     0.00%
[2ms,   4ms]   18    0.60%
[4ms,   6ms]   1108  36.93%  ###########################
[6ms,   +Inf]  1874  62.47%  ##############################################

cat results_8081.bin | vegeta report -reporter=plot > results_8081.html f:id:tsuyoshi_nakamura:20161225170707p:plain

wrkでの検証結果

wrk -t1 -c100 -d30s http://localhost:8081/

Running 30s test @ http://localhost:8081/
  1 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   205.13ms  207.27ms   1.96s    95.27%
    Req/Sec   524.92    143.15     0.90k    72.44%
  8261 requests in 30.02s, 2.50MB read
  Socket errors: connect 0, read 0, write 0, timeout 14
Requests/sec:    275.16
Transfer/sec:     85.18KB

Go(net.tcp) + Nginx(fastcgi_pass)

fastcgiTCP通信

vegetaでの検証結果

echo "GET http://localhost:8071" | vegeta attack -rate=100 -duration=30s | tee results_8071.bin | vegeta report

Requests      [total, rate]            3000, 100.03
Duration      [total, attack, wait]    34.148993246s, 29.989999849s, 4.158993397s
Latencies     [mean, 50, 95, 99, max]  786.786891ms, 6.547174ms, 6.418549209s, 7.619790263s, 7.978296865s
Bytes In      [total, mean]            486000, 162.00
Bytes Out     [total, mean]            0, 0.00
Success       [ratio]                  100.00%
Status Codes  [code:count]             200:3000
Error Set:

cat results_8071.bin | vegeta report -reporter='hist[0,2ms,4ms,6ms]'

Bucket         #     %       Histogram
[0,     2ms]   0     0.00%
[2ms,   4ms]   31    1.03%
[4ms,   6ms]   1116  37.20%  ###########################
[6ms,   +Inf]  1853  61.77%  ##############################################

cat results_8071.bin | vegeta report -reporter=plot > results_8071.html f:id:tsuyoshi_nakamura:20161225170704p:plain

wrkでの検証結果

wrk -t1 -c100 -d30s http://localhost:8071/

Running 30s test @ http://localhost:8071/
  1 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   312.64ms  258.32ms   1.97s    83.26%
    Req/Sec   330.31    225.29     0.92k    68.49%
  9786 requests in 30.08s, 3.11MB read
  Socket errors: connect 40, read 0, write 0, timeout 21
  Non-2xx or 3xx responses: 1607
Requests/sec:    325.37
Transfer/sec:    105.89KB

Go(net.unix) + Nginx(fastcgi_pass)

fastcgiUNIXドメインソケット通信

vegetaでの検証結果

echo "GET http://localhost:8061" | vegeta attack -rate=100 -duration=30s | tee results_8061.bin | vegeta report

Requests      [total, rate]            3000, 100.03
Duration      [total, attack, wait]    29.99792736s, 29.989999868s, 7.927492ms
Latencies     [mean, 50, 95, 99, max]  7.824538ms, 5.861618ms, 16.640207ms, 29.212751ms, 231.643738ms
Bytes In      [total, mean]            486000, 162.00
Bytes Out     [total, mean]            0, 0.00
Success       [ratio]                  100.00%
Status Codes  [code:count]             200:3000
Error Set:

cat results_8061.bin | vegeta report -reporter='hist[0,2ms,4ms,6ms]'

Bucket         #     %       Histogram
[0,     2ms]   0     0.00%
[2ms,   4ms]   121   4.03%   ###
[4ms,   6ms]   1484  49.47%  #####################################
[6ms,   +Inf]  1395  46.50%  ##################################

cat results_8061.bin | vegeta report -reporter=plot > results_8061.html f:id:tsuyoshi_nakamura:20161225170701p:plain

wrkでの検証結果

wrk -t1 -c100 -d30s http://localhost:8061/

Running 30s test @ http://localhost:8061/
  1 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   221.23ms  300.71ms   1.99s    89.90%
    Req/Sec   552.81    243.08     1.09k    72.54%
  16298 requests in 30.01s, 5.20MB read
  Socket errors: connect 1, read 0, write 0, timeout 52
Requests/sec:    543.11
Transfer/sec:    177.28KB

vegetaでの検証結果のまとめ

  • レイテンシーのグラフではぱっと見ちょっと良くわからなかった。ちゃんと追うと少し差異があるのがわかるんだけど...
  • Go(http.ListenAndServe) + Nginx(proxy)Go(net.tcp) + Nginx(fastcgi_pass)の比較はだいたい同じかんじ
  • Go(net.unix) + Nginx(fastcgi_pass)との比較ではちょっと差異がでた

少し差異がでた部分のLatency比較

4ms, 6ms 6ms, +Inf
Go(net.tcp) + Nginx(fastcgi_pass) 37.20% 61.77%
Go(net.unix) + Nginx(fastcgi_pass) 49.47% 46.50%

Go(net.unix) + Nginx(fastcgi_pass)ではレイテンシーが少々いいのかな?

wrkでの検証結果のまとめ

Req/Sec
Go(http.ListenAndServe) + Nginx(proxy) Requests/sec: 275.16
Go(net.tcp) + Nginx(fastcgi_pass) Requests/sec: 325.37
Go(net.unix) + Nginx(fastcgi_pass) Requests/sec: 543.11

秒間でさばけるリクエストはGo(net.unix) + Nginx(fastcgi_pass)は一番良かった?

まとめ的な

  • Golangをメイン言語にしてフルスタックなFWを使うパターンよりはバッチやAPIに使う場合が結構あると思う
    • そのときに https://github.com/gin-gonic/ginのうなFWは使わずに内包されてるnet/httpで作ってしまってパフォーマンスを優先するパターンが結構あるんだなーと色々と調べて感じた?
  • 今回の検証結果的にはnet/httpGo(net.unix) + Nginx(fastcgi_pass)でやると一番良いんじゃないかという結果になった
  • 今回は対象外にしたけど grpc / grpc.ioあたりも触ってみたいなーと余談??
  • AWS gatewayを利用するけどヘッダー認証系やスロットリングAPIキー認証、レスポンスキャッシュなどが低コストでさくっと実装できたりできるので組み合わせるとなお良いかなーっと思ったけど内部のAPIサーバには使えなかったり...うーんおしい?
    • 使い勝手良く改善されることを願う
  • golangでgraceful-restartも運用を考えると必要だなぁと感じたけどAPIの上位にロードバランサがいればいらないかもって感じてる。どうなんだろう?