nginx〜go〜mongoDBを使ったREST-WebAPIサーバのパフォーマンスチューニング 第3回 バックエンドはコネクションプーリングで

この記事ではAWS ec2上のCentOS7にインストールしたnginx 1.10.2を使っています。go1.7を追加しました。

連載一覧
[第1回 REST-WebAPIの特徴に合わせてチューニングしよう] (http://qiita.com/toast-uz/items/828bbb48d58eb65f1f9f)
[第2回 nginx基本チューニング(有効なのはtcp_nopushだけ）] (http://qiita.com/toast-uz/items/48754a95aa0c60db67f5)
[第3回 バックエンドはコネクションプーリングで] (http://qiita.com/toast-uz/items/b459100189efb995b9c5)
[第4回 DB接続もコネクションプーリングで] (http://qiita.com/toast-uz/items/9c1b97bee8f4a7c13bfc)

今回からは、testサーバ〜webサーバ〜apサーバの構成にして、apサーバにgo1.7をインストールしました。全てt2.microを利用しています。

goのインストールについては、[How To Install Go 1.7 on CentOS 7](https://www.digitalocean.com/community/tutorials/how-to-install-go-1-7-on-centos-7)を参考にしました。

# 1. 準備

nginxのバックエンド接続は、FastCGI方式、http_proxy方式があり、さらにhttp_proxy方式はkeepaliveありなしの区別があります。この3方式のapサーバを準備します。また、比較のためにwebサーバのデフォルトindex.htmlも参照できるようにするとともに、3方式のapサーバは、いずれも、index.htmlと同じコンテンツを返すようにします。

これらの3方式+1（index.html参照）が、任意に試せるような環境構築をします。

## 1.1 testサーバ

忘れずに遅延印加しておきます。

```:testサーバ
$ sudo tc qdisc add dev eth0 root netem delay 100ms
```

## 1.2 webサーバ（nginx.conf）

3方式+1を一度に表現します。

| アクセス先 | 動作 |
|:---------|:---------|
| / |index.html参照|
| /api1 |FastCGI接続|
| /api2 |http_proxy接続|
| /api2k |http_proxy接続（KeepAliveあり）|

nginx.confは[第2回](http://qiita.com/toast-uz/items/48754a95aa0c60db67f5)までの内容として、nginx.confにincludeされているdefault.confを修正することで、上記を実現します。

```nginx:default.conf
upstream api1 {
     server 172.31.24.119:9000;
}
upstream api2 {
     server 172.31.24.119:8080;
}
upstream api2k {
     server 172.31.24.119:8080;
     keepalive 100;
}

server {

# 中略（元々記載されている内容 ※index.htmlを含む）

    location /api1 {
        fastcgi_pass api1;
        include fastcgi_params;
    }
    location /api2 {
        proxy_pass http://api2/;
    }
    location /api2k {
        proxy_pass http://api2k/;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}
```

なお、このまま実行すると、SELinuxに起因するセキュリティにより、http_proxyが動作しません。以下の設定により、動作できるようになります。

```:webサーバ
# setsebool -P httpd_can_network_relay on
```

## 1.3 apサーバ（Go言語）

apサーバのファイルディスクリプタを増やしておきます。これをしないと、同時接続が1000を超えたあたりで、アプリがダウンします。

```:apサーバ
$ sudo vi /etc/security/limits.conf
/* 以下を追記 */
* soft nofile 64000
* hard nofile 64000
$ sudo reboot now
```

FastCGI用のアプリapi1.goと、HTTP用のアプリapi2.goを作成します。

```go:api1.go
package main

import (
    "fmt"
    "net"
    "net/http"
    "net/http/fcgi"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, `
/* nginxのデフォルトのindex.htmlを貼り付ける*/
`)
}

func main() {
    l, err := net.Listen("tcp", ":9000")
    if err != nil {
        return
    }
    http.HandleFunc("/", handler)
    fcgi.Serve(l, nil)
}
```

```go:api2.go
package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, `
/* nginxのデフォルトのindex.htmlを貼り付ける*/
`)
}

func main() {
    http.HandleFunc("/", handler)
    err := http.ListenAndServe(":8080", nil);
    if err != nil {
        return
    }
}
```

以下により、コンパイルして実行させておきます。

```:apサーバ
$ go build api1.go
$ go build api2.go
$ ./api1 &
$ ./api2 &
```

# 2. 測定結果

testサーバでabを実行させます。

```:testサーバ
$ ab -c 10000 -n 1000000 -r http://172.31.20.18/
$ ab -c 10000 -n 1000000 -r http://172.31.20.18/api1
$ ab -c 10000 -n 1000000 -r http://172.31.20.18/api2
$ ab -c 10000 -n 1000000 -r http://172.31.20.18/api2k
```

測定結果を以下に示します。

| アクセス先 | 動作 | Requests/sec | エラー数 |
|:---------|:---------|------:|-----:|
| / |index.html参照|2,530|1,362|
| /api1 |FastCGI接続|2,097|2,765|
| /api2 |http_proxy接続|1,813|9,843|
| /api2k |http_proxy接続（KeepAliveあり）|3,845|1,467|

なんと、この条件では、webサーバのindex.htmlを参照するよりも、http_proxy（KeepAliveあり）でapサーバと通信した方が、速くなりました。


さらに、結果を分かりやすくするために、testサーバ〜webサーバのボトルネックの影響を外して、webサーバローカルでabをkeepaliveありでも測定してみます。

```:webサーバ
$ ab -c 100 -n 1000000 -k http://172.31.20.18/
$ ab -c 100 -n 1000000 -k http://172.31.20.18/api1
$ ab -c 100 -n 1000000 -k http://172.31.20.18/api2
$ ab -c 100 -n 1000000 -k http://172.31.20.18/api2k
```

測定結果を以下に示します。

| アクセス先 | 動作 | Requests/sec | エラー数 |
|:---------|:---------|------:|-----:|
| / |index.html参照|38,754|0|
| /api1 |FastCGI接続|2,251|0|
| /api2 |http_proxy接続|2,150|0|
| /api2k |http_proxy接続（KeepAliveあり）|10,378|0|

さすがに、今度は、index.html参照が最速でしたが、これは比較対象用なので、apサーバとの通信に限定すると、http_proxy（KeepAliveあり）が最速となります。

以上の結果により、http_proxyでkeepaliveした状態が、明らかに性能が出ることがわかりました。keepaliveによりコネクションプーリング状態になり、コネクション接続切断のオーバーヘッドが無くなるためと考えられます。



なお、keepalive設定の数値は、コネクションプーリングの最小数ですが、最大値ではありません。最大値は別途、max_connsにより制限をする必要があります。詳細は、[ngx_http_upstream_module モジュール](http://mogile.web.fc2.com/nginx/http/ngx_http_upstream_module.html)を参照してください。