nginx〜go〜mongoDBを使ったREST-WebAPIサーバのパフォーマンスチューニング 第1回 REST-WebAPIの特徴に合わせてチューニングしよう

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

連載一覧
[第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)

# 1. 背景

## 1.1. REST-WebAPIサーバの特徴

nginxのチューニング情報は数多いのですが、REST-WebAPIサーバに特化した情報はあまり無く、整理してみることにしました。REST-WebAPIサーバの特徴は以下だと思います。

+ クライアントはネットワーク遅延があるリモート環境に配置されている。
+ 非常に多数のクライアントが１問１応答のREST形式でリクエストしてくる。
+ Webコンテンツは基本的に動的生成で、コンテンツサイズは小さい（高々数キロバイト）である。

## 1.2. 一般的なチューニング情報の課題

一方、これまでの多くのチューニング情報は、REST-WebAPIサーバの特徴に反して、以下の課題があります。

+ 遅延が考慮されていない。さらに、WebAPIサーバと同一サーバ上で性能測定されているものが多い。
+ KeepAliveを前提としている。特に、KeepAlive必須のwrkを使用しているものが多い。
+ 静的コンテンツを前提としており、キャッシュによる性能改善を行なっている。

## 1.3. 本記事でのチューニング方針

そこで、本記事では、以下の方針でチューニングを行います。

+ tcにより遅延100ms印加した環境での高速化を目指す。
+ KeepAliveを前提としない。Apache abを使用し、同時接続数は1万以上とする。また、スケールアップしてCPUコアが増えることも考慮したチューニングを行う。
+ キャッシュは前提としないが、REST-WebAPIでも活用できそうなチューニングは行う。

また、これまでの多くのチューニング情報はそのまま鵜呑みにはできない、ということで、しっかりした根拠や実測での効果があるチューニングのみ行うことにします。

今回は、基本的な環境準備を行い、チューニング前の基礎性能を測定します。最終的にはREST WebAPIサーバとして動的コンテンツを前提にしますが、当初はnginxのフロント部分のチューニングのために、nginxのデフォルトホームページ（612 bytes）のGETで測定します。

# 2. 準備

AWS ec2 t2.microインスタンス２台（testサーバ、webサーバ）にCentOS7をインストールします。AWSのセキュリティグループで、test〜webの相互の通信を許可するようにしてください。

## 2.1. nginxの準備

### 2.1.1. 初期インストール

webサーバにnginxをインストールします。

```:webサーバ
# vi /etc/yum.repos.d/nginx.repo   ※以下の内容を作成
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/rhel/$releasever/$basearch/
gpgcheck=0
enabled=1
# yum install nginx -y
（略）
Installed:
  nginx.x86_64 1:1.10.2-1.el7.ngx                                               
# systemctl start nginx
# systemctl enable nginx で自動起動設定
```

nginx.confに、同時接続数が大きいサイト向けの基本設定を行います。

```nginx:/etc/nginx/nginx.conf
user  nginx;
worker_processes  auto;      #変更
worker_cpu_affinity auto;    #追記

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

worker_rlimit_nofile 90000;   #追加

events {
    worker_connections 30000;   #変更
}
```

`# systemctl reload nginx`  により、新しい設定が有効になります。

### 2.1.2. worker_processes、worker_cpu_affinityのチューニング

worker_processesは以前からauto設定が可能でしたが、worker_cpu_affinityも、nginx 1.9からauto設定が出来るようになりました。固定値を設定すると、スケールアップでCPUコアが増えた場合に設定を修正しないといけません。今回は、スケールアップを考慮した設定を行う方針のため、autoとします。

### 2.1.2. worker_rlimit_nofileのチューニング

worker_rlimit_nofileは、workerが同時にオープンできるファイル（LinuxなのでI/O全てが対象）の数です。worker_rlimit_nofileは、同時接続数が大きいサイトでは、なるべく大きな値を設定します。ただし、worker_rlimit_nofile × CPUコア数が、Linux OSのファイルディスクリプタ最大値以下である必要があります。ec2の主要なインスタンスで、`$ cat /proc/sys/fs/file-max`により、OS上のファイルディスクリプタ最大値を確認すると、メモリ量に比例しているようです。

|インスタンスタイプ|CPUコア数|メモリ|fiel-max|
|:-----|-----:|-----:|-----:|
|t2.micro|1|1GB|97135|
|t2.small|1|2GB|182202|
|t2.medium|2|4GB|380304|
|t2.large|2|8GB|789690|

これにより、t2.microで、`worker_rlimit_nofile 90000;`にしておくことで、スケールアップでworker_processesが増えた場合にも、worker_rlimit_nofileの設定を変更せずに、同時オープンするファイルの総数がOS最大数に収まることがわかります。

なお、file-maxを増やすことはできますが、スケールアップを考慮した設定を行う方針のため、file-maxの個別修正はしないようにします。

注意ですが、CentOS7では、nginx.confの設定だけでは、worker_rlimit_nofileが有効になりません。実際に、以下のようなエラーが出ます。

```:/var/log/nginx/error.log
2017/01/07 14:44:39 [alert] 7848#7848: setrlimit(RLIMIT_NOFILE, 90000) failed (1: Operation not permitted)
```

これは、CentOS7のSELinuxの影響のようです。[CentOS 7 raise nofile limit for Nginx](https://ospi.netcode.fi/blog/centos-7-raise-nofile-limit-for-nginx.html "CentOS 7 raise nofile limit for Nginx") に原因と対処方法が詳しく書かれています。ここでは、nginx.confをそのまま活かしたいので、`# setsebool -P httpd_setrlimit 1`による対処を行います。

### 2.1.3. worker_connectionsのチューニング

worker_connectionsは、worker毎の最大同時接続数です。バラツキがあるため、必ずしも、worker_connections × woker数 = システム最大同時接続数 にならないようです。一般的には、worker_connectionsの2〜3倍の値を、worker_rlimit_nofileとして設定すると良いようです。この理由をきちんと記述している出典は見かけませんが、おそらく、HTTP/1.1のパイプライン処理や、HTTP/2のように、同一コネクション内で並列に複数のコンテンツを送受信することが考慮されているものと思います。

今回のREST WebAPIでの１問１応答を前提とすると、あまり倍率は上げなくても良いものと思われますが、同時接続数1万を目安とする方針ですので、ギリギリの値を攻めずに保守的に3倍としても十分です。よって、worker_connectionsは3万とします。

## 2.2. Apache abの準備

Apache abは、比較用のためtestサーバだけでなくwebサーバにもを使えるようにします。

```:testサーバ、webサーバ
# yum install httpd -y
```

このままでは、1024を超える同時接続を行う場合に、ファイルディスクリプタが不足します。webサーバ側はベンチマークへの影響を出さないために設定を変えませんが、testサーバ側はファイルディスクリプタを増やしておきます。なお、Apache abでは最大同時接続は2万となっています。

```:testサーバ
# vi /etc/security/limits.conf
* soft nofile 64000 #追記
* hard nofile 64000 #追記
# reboot now
```

## 2.3. tcの準備

擬似的にネットワーク遅延等を発生させるtcについては、初期状態でインストールされていました。

遅延を入れるには、`$ sudo tc qdisc add dev eth0 root netem delay 100ms`遅延を除くには、`$ sudo tc qdisc del dev eth0 root`というコマンドを、testサーバ側で実行することにします。

# 3. 測定結果

以上の準備の元で、測定をしてみました。本来はリモートで遅延のある環境、かつKeepAlive無しで測定するのですが、初回ですので、いろいろな条件で比較測定してみます。

abは、`ab -c 同時コネクション数 -t 10 http://アドレス/`というコマンドで実行します。同時コネクション数は、100〜abの最大値である2万の間でいくつか試します。webサーバ側のOSファイルディスクリプタは増やしていませんので、ローカル実行では同時コネクション数は1000までとします。アドレスは、リモートの場合はwebサーバのプライベートIPアドレス、ローカルの場合は127.0.0.1を指定します。

abの実行結果の、Requests per second \[#/sec] (mean) の値を一覧表にします。

|ab実行場所|KeepAlive|遅延印加|同時100|同時1千|同時1万|同時2万|
|:-----|:-----:|:-----:|-----:|-----:|-----:|-----:|
|ローカル(web)|あり|-|36,492|34,925|-|-|
|ローカル(web)|なし|-|14,132|13,725|-|-|
|リモート(test)|なし|-|1,941|1,900|1,016|745|
|リモート(test)|なし|100ms|467|1,269|1,515|2,284|

今回の前提条件の元でのベンチマーク結果が、一般的なチューニング情報で使われているローカル環境でKeepAliveを前提としたwrkを実行した結果と比較して、大差があることが分かると思います。背景で述べた「一般的なチューニング情報の課題」が立証できたことになります。

遅延印加している状態では、同時接続数が増えると性能が向上します。これは、多重度が高まるため、ネットワーク遅延の与える影響が相対的に小さくなるためだと思います。また、遅延印加していない状態では、同時接続数が増えると性能が悪化します。クライアントかサーバのどちらかが過負荷になったものと思います。

本記事では、今後、リモートでの遅延印加100ms,同時1万接続でのベンチマーク値である、1,515の値を改善していくことを目指します。