Nginx with Goのパフォーマンスを検証した
はじめに
そろそろAPIをちゃんと切り出して運用したほうが良いよなーとか、ネイティブアプリやる時に必ず必須だしなぁーとか思ってた。 でせっかくならGolangでやった場合どんな感じになるかなと思いながらかる~く調べていた。
Golang自体の書き方云々はまぁ良いとして、実際のAPIを公開して運用するときにさてどんな構成が一番良いんだろうとかちょっと疑問に思った。
んで何通りかあって、どれが一番ベストパフォーマンスが出るんだろうなーと疑問に思ったのでそのあたりのbenchmarkとったのでまとめる
検証環境
- 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
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)
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
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)
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
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
で作ってしまってパフォーマンスを優先するパターンが結構あるんだなーと色々と調べて感じた😁
- そのときに https://github.com/gin-gonic/ginのうなFWは使わずに内包されてる
- 今回の検証結果的には
net/http
、Go(net.unix) + Nginx(fastcgi_pass)
でやると一番良いんじゃないかという結果になった - 今回は対象外にしたけど grpc / grpc.ioあたりも触ってみたいなーと余談💪🏽
- AWS gatewayを利用するけどヘッダー認証系やスロットリング、APIキー認証、レスポンスキャッシュなどが低コストでさくっと実装できたりできるので組み合わせるとなお良いかなーっと思ったけど内部のAPIサーバには使えなかったり...うーんおしい😅
- 使い勝手良く改善されることを願う
- golangでgraceful-restartも運用を考えると必要だなぁと感じたけどAPIの上位にロードバランサがいればいらないかもって感じてる。どうなんだろう🤔