nginxとnode.jsのCPU affinity

# はじめに

> Nginxがシングルスレッドで動作するため、コア数に合わせて設定しておくのが標準的な運用である。

nginxに限らず、シングルスレッドのものは基本的にこのような記述を見る。

例えば、 nginx + nodejs + redis という組み合わせで、１つのサーバを構成することを考えた場合、
全部シングルスレッドなんだけれど、全部コア数に合わせるのが正解なのか、
それとも各プロセスの使用cpuが被らないように設定しつつ、合計がコア数になるように設定するのが正義なのか。

イメージとしては、コンテキストスイッチが増えて困るものを隔離しておけばいいイメージがあるが、
実際のところは、どうなんだ。

というわけで、計測しよう。

# 計測環境

物理サーバを用意するのが望ましいのだろうなんて思いながら、ec2のm4.xlargeあたりで試してみる。

構成は、
リクエスト → nginx(proxy_passを使用して振り分け) → node.js(pm2でdaemon化) → redis (get/set) → レスポンス
node.jsで重い処理をさせて、CPUを消費するパターンで試して見る。

m4.xlargeは、vCPUが4つ。

分け方としては、
・すべてコア数に合わせた場合
・node.jsと他を別のコアに分けた場合
の２パターンで検証してみました。


node.js はこんな感じ

```js
require('date-utils');

var express = require("express");
var app = express();

var server = app.listen(8080 + parseInt(process.env.NODE_APP_INSTANCE || 0), function (){
    console.log("listening:" + server.address().port);
});

var redisClient = require('redis').createClient();

var RAND_KEY_MAX = 100000 + 1;
var LOOP_MAX = 10000;


app.get("/api/cpu-affinity-check", function(req, res, next){
    var key = "hoge" + Math.floor( Math.random() * RAND_KEY_MAX ) ;

    console.log(key);
    redisClient.get(key, function(err, value){
	if (err) return console.log("get err:" + err);
	if (value == null) value = 0;

        for(var i = 0; i < LOOP_MAX; i++){
            value += i;
        }
        redisClient.set (key, value, function (){
           res.send("OK");
        });
    });
});
```


負荷計測は、
別インスタンスを立ち上げ、weighttpを使用した。
こちらもスペックは、m4.xlarge


# cpu affinity

## すべてコア数に合わせた場合

```
$ pgrep -f "redis-server"
16004
$ sudo taskset -p 16004
pid 16004's current affinity mask: f

$ pgrep -f "nginx: worker process"
16335
16336
16337
16339
(省略)
pid 16335's current affinity mask: f
pid 16336's current affinity mask: f
pid 16337's current affinity mask: f
pid 16339's current affinity mask: f

$ pgrep -f "node"
16531
16537
16553
16559
(省略)
pid 16531's current affinity mask: f
pid 16537's current affinity mask: f
pid 16553's current affinity mask: f
pid 16559's current affinity mask: f
```

## node.jsと他を分けた場合

```
$ pgrep -f "redis-server"
16004
$ sudo taskset -p 16004
pid 16004's current affinity mask: 1

$ pgrep -f "nginx: worker process"
16668
$ sudo taskset -p 16668
pid 16668's current affinity mask: 1

$ pgrep -f "node"
16691
16697
16703
（省略）
pid 16691's current affinity mask: 2
pid 16697's current affinity mask: 4
pid 16703's current affinity mask: 8
```

# 計測結果

結果は、こんな感じ。

・すべてコア数に合わせた場合

```bash
$ ./weighttp -n 100000 -c 1000 -t 4 -k http://(TARGET HOST)/api/cpu-affinity-check

finished in 62 sec, 791 millisec and 479 microsec, 1592 req/s, 345 kbyte/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 22197480 bytes total, 21997480 bytes http, 200000 bytes data
```

・node.jsと他を別のコアに分けた場合

```bash
$ ./weighttp -n 100000 -c 1000 -t 4 -k http://(TARGET HOST)/api/cpu-affinity-check

finished in 64 sec, 479 millisec and 506 microsec, 1550 req/s, 336 kbyte/s
requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored
status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 22197475 bytes total, 21997475 bytes http, 200000 bytes data
```

上記環境だと、『コア数に合わせて設定しておくのが標準的な運用』で問題なさそう。

もう少しチューニングをいろいろ試してみたい気もしたが、
そうすると明日になってしまいそうなので、今日のところはココまで。

負荷を考えた際の、チューニング候補にcpu affinity も検討しても面白いかもしれない。