Apacheにかわるwebサーバ: uWSGIパフォーマンスチューニング


# 背景

PythonとDjangoでソマートフォンゲームのアプリケーションサーバを開発しており、そのwebサーバとしてuWSGIを使っています。
uWSGIには多くのパラメータがありすぎてどれを選んでいいかわかりにくいです。

実際に負荷試験をして、各種パラメータを変え、安定的にパフォーマンスが出せるようになったので、
そのときにどのように計測して、どのパラメータを変えたのかを共有したいと思います。

# 環境

![server.png](https://qiita-image-store.s3.amazonaws.com/0/30079/15a0d636-e358-2cc7-d8a1-49ebd659b050.png)

* python: 2.7.10
* uWSGI: 2.0.11.1
* locust: 負荷試験のツールです。ユーザシナリオを作ってアプリケーションサーバの各種API負荷試験をしました。
* EC2: c4.2xlargeの8コア


# 性能測定ツール

    「推測するな、計測せよ」
    
という言葉がありますが、何をおいても計測できなければ改善したかどうかわかりません。
主に以下のツールが今回役立ちました。

* NewRelic: おなじみですね。有償版なので細かいところまで見れます
* [uwsgitop](https://pypi.python.org/pypi/uWSGItop): PyPIで導入できる、uWSGIのパフォーマンス統計ツール
* vmstat

## uwsgitopについて

uWSGI側の設定で以下のようにして、パフォーマンス統計データをsocketに出力できます。

    stats = /var/run/uWSGI/projectname.stats.sock
    memory-report = true

これをpipなどでインストールしたuWSGItopを以下のように実行すると統計値がunixコマンドtopのようにリアルタイムで見れます。

    $ uwsgitop /var/run/uWSGI/projectname.stats.sock

以下が実行例です。この状態ではプロセス単位ですが、キーボード上でaを押すと、スレッド単位で見ることもできます。

![uwsgitop.png](https://qiita-image-store.s3.amazonaws.com/0/30079/d5b71229-6c5b-f911-bb8a-e1afc4811d11.png)

# 今回設定したパフォーマンスチューニングに有効なuWSGI設定

[uWSGI Options](http://uWSGI-docs.readthedocs.org/en/latest/Options.html)に各種設定ファイル用の値がかいてあるのですが、設定出来る量が多すぎてどれを設定していいか最初はわかりずらいです。
さらに、オプションの説明も簡易的で、uWSGIの作者自身もソースみてねといっているので使ったことがない場合には難儀します。

ここでは実際に設定してみて効果があったパラメを上げたいと思います。

## processes, threads

uWSGIはリクエストを受け付けるプロセスとスレッドの数をそれぞれ指定できます。
開発しているuWSGIには以下のように設定しました。

    processes = 16
    threads = 1

様々なプロセス数とスレッド数の組み合わせで試験したのですが、threadsを増やすとコンテキストスイッチが多くなりさばけるRPSが半分以下になっていました。

![vmstat_1.png](https://qiita-image-store.s3.amazonaws.com/0/30079/3cff4cf3-a9b8-605a-44c7-5ab105842310.png)

-----------------

![vmstat_2.png](https://qiita-image-store.s3.amazonaws.com/0/30079/b536a33b-5456-f1b7-427e-17af96be9df4.png)

ちなみにthreadsが1の場合に、processesを増やしてもコア数である8以上では大して違いはありませんでした。

## thunder-lock 

Linux系のサーバを使っているのならまずこのオプションを以下のようにtrueに設定すべきです。

    thunder-lock = true
    
詳しくは[ここ](http://uWSGI-docs.readthedocs.org/en/latest/articles/SerializingAccept.html)にかいてあるのですが、falseになっていると複数のuWSGIのプロセスで捌くリクエストに偏りが出てしまいます。

![uwsgitop_1.png](https://qiita-image-store.s3.amazonaws.com/0/30079/fbee26fa-0555-6de4-7a97-8ea1d636f4e3.png)

-----------------

![uwsgitop_2.png](https://qiita-image-store.s3.amazonaws.com/0/30079/a73cb3d8-3c9d-3a48-cdf6-681562bc21c8.png)

## max-requests, max-requests-delta

max-requestsは、プロセスが再読み込みするまでにどのくらいのリクエストを受けるかという設定値です。
以下のように設定してあります。

    max-requests = 6000

負荷試験当初はこの値が大きな値になっており、再読み込みされず、メモリの使用状況を見ると以下のようになってました。

![memory_1.png](https://qiita-image-store.s3.amazonaws.com/0/30079/48912b85-403d-fa30-9239-060b2a44952c.png)

uWSGIプロセスがかかえているメモリの増え方などを見て、6000が適切なのでそのように設定しました。
どうしてメモリが増大していくか別途調べる必要はあるとは思いますが、6000回に1回の再読み込みなのでそう影響がないと判断し設定しています。
再読み込みするとメモリは開放され、初期に起動したときと同じ状態になります。

プロセスごとのを取り忘れていましたが正しく設定されると以下のようにメモリが一定期間で落ち着きます。

![memory_2.png](https://qiita-image-store.s3.amazonaws.com/0/30079/0d4cce21-db0c-9b96-ccd7-9cea28feea20.png)

ただ、ここで注意なのですが、プロセスが再起動すると数秒間、新しいリクエストが受けれなくなります。
max-requestsという同一の値をすべてのプロセスに適用すると、ほぼ一斉に再起動するので、サーバ全体でリクエストが数秒うけられなくなります。

これを回避するのがmax-requests-deltaという値で以下のように設定しています。

    max-requests-delta = 300

プロセスごとにこの差分で再起動してくれます。例えばある1プロセスが6000で再起動する場合は、次のプロセスは6300のリクエストを受けると再起動します。
RPSが16くらいでプロセスが16の設定を想定していれば、1プロセスが1秒ごとに1リクエストうけるので、300秒ごとくらいに再起動します。
このようにすると一度に再起動するプロセスが限られるのでサーバ全体としてリクエストがうけれなくなることは回避できます。

## touch-reload, lazy-apps

touch-reloadにファイルのパスを指定し、その設定がtouchされるたびにuWSGIをリロードすることができます。
ただそうすると、サーバ全体でリクエストを受けれなくなってしまうので、lazy-appsを設定し、順次リロードできるようにできます。

自分たちの環境ではlazy-appsを指定すると、すぐ更新したい場合などは不向きなので、数秒の待ちは許容し設定してません。


# まとめ

今回、実際にチューニングしたuWSGIのパラメータを見てきました。
その他にも以下のようなパラメが設定してありますが、これからもより深く調べ性能改善していきたいと思います。

* listen
* harakiri
* harakiri-verbose
* limit-as
* log-date