Node.jsでサーバサイドWebアプリを開発中、なぜかメモリリークがあるライブラリに遭遇してしまったので、ワークアラウンドとして、定期的にプロセスを再起動させて、メモリリークの問題を緩和したいと思いました。
- コード: https://github.com/knjname/2020-04-05_restartClusterChild
- 参考: https://nodejs.org/api/cluster.html
サーバサイド
http://0.0.0.0:10080 をリスンするプロセスが4つ立ち上がり、5秒未満で子プロセスを停止させ、その後に再起動します。(実用上は、もっと長い時間でプロセスを殺すべきです。)
src/index.js
const cluster = require("cluster");
const http = require("http");
const sleep = time => new Promise(done => setTimeout(done, time));
const clusterCount = 4;
const portNumber = 10080;
if (cluster.isMaster) {
const spawnProcess = () => {
// プロセスを終了させるまでの時間: 0 〜 5000 msec
const ttl = ~~(5000 * Math.random());
const child = cluster.fork();
let timeout;
child.on("listening", () => {
// 指定時間で終了(Graceful kill)させる
console.log(`誕生! 死まで ${ttl} msec.`);
timeout = setTimeout(() => {
console.log(`死: ${child.id}`);
child.kill();
}, ttl);
});
child.on("disconnect", () => {
// 別の理由で死んだ場合はkillをキャンセル
if (timeout) {
clearTimeout(timeout);
}
});
child.on("exit", () => {
// 子プロセスが終了したら代わりのものを1つ起動する
spawnProcess();
});
};
// 子プロセスを複数起動する
for (let i = 0; i < clusterCount; i++) {
spawnProcess();
}
}
if (cluster.isWorker) {
// Express や Koa など好きに使いましょう
http
.createServer(async (req, res) => {
// リクエスト終了までやや時間がかかる設定
await sleep(1000);
res.writeHead(200);
res.end("Request done\n");
})
.listen(portNumber);
}
起動すると、下記のようなログを吐きながら、プロセスの終了と生成を延々と繰り返します。
誕生! 死まで 2712 msec.
誕生! 死まで 3984 msec.
誕生! 死まで 4297 msec.
誕生! 死まで 1547 msec.
死: 4
誕生! 死まで 4276 msec.
死: 2
:
:
テスト
ab
コマンドできちんとリクエストが中断されずにいるか、テストしてみます、
$ ab -c 20 -n 500 http://localhost:10080/
This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests
Server Software:
Server Hostname: localhost
Server Port: 10080
Document Path: /
Document Length: 13 bytes
Concurrency Level: 20
Time taken for tests: 26.226 seconds
Complete requests: 500
Failed requests: 0
Total transferred: 44000 bytes
HTML transferred: 6500 bytes
Requests per second: 19.07 [#/sec] (mean)
Time per request: 1049.029 [ms] (mean)
Time per request: 52.451 [ms] (mean, across all concurrent requests)
Transfer rate: 1.64 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.0 0 7
Processing: 1000 1008 5.4 1007 1025
Waiting: 1000 1007 4.7 1006 1023
Total: 1000 1008 5.5 1007 1025
Percentage of the requests served within a certain time (ms)
50% 1007
66% 1010
75% 1012
80% 1013
90% 1016
95% 1019
98% 1020
99% 1022
100% 1025 (longest request)
特に何も問題なくリクエスト処理は完了しているようです。
Complete requests: 500
Failed requests: 0
まとめ
- cluster でマルチプロセス化できるし、定期的にプロセスを再起動して、健全性を保つことができるはず
- こういうゴミ掃除はマルチスレッドモデルだとできなさそう