Carpe Diem

備忘録

Node.jsでGraceful Shutdown

概要

christina04.hatenablog.com

のNode.js版です。

環境

  • Node.js v18.18.0
  • TypeScript v5.2.2
  • Express v4.18.2

課題

次のようなアプリケーションコードがあった際に

import type { Express, Request, Response } from "express";
import express from "express";

const app: Express = express();
const port = process.env.port || 8000;

app.get("/", (req: Request, res: Response) => {
  setTimeout(() => {
    res.end("Hello world");
  }, 10000);
});

const server = require("http").createServer(app);

server.listen(port, () => {
  console.log("Express server listening on port " + server.address().port);
});

サーバ側はデプロイなどで停止することがあります。

Ctrl-CSIGINTを投げると、途中で処理が中断され、

$ curl localhost:8000
curl: (52) Empty reply from server

クライアント側はエラーになります。

対応方法

Graceful Shutdown

次のようなフローでGraceful shutdownを導入します。

  1. 終了シグナルを受け取る
  2. 新規リクエストの受け付けを停止する
  3. 処理中のリクエストを最後まで処理する
  4. (Option)その他のリソース(DB接続など)を閉じる

1. 終了シグナルを受け取る

まずはSIGINTシグナルを受け取るように修正します。

process.on('SIGINT')でできます。

import type { Express, Request, Response } from "express";
import express from "express";

const app: Express = express();
const port = process.env.port || 8000;

app.get("/", (req: Request, res: Response) => {
  setTimeout(() => {
    res.end("Hello world");
  }, 10000);
});

const server = require("http").createServer(app);

server.listen(port, () => {
  console.log("Express server listening on port " + server.address().port);
});

process.on("SIGINT", () => {
  console.info("SIGINT signal received.");
});

2, 3. 新規リクエストの受付を停め、処理中のリクエストを最後まで実行

終了シグナルを受け取ったら新規リクエストの受付を停め、処理中のリクエストを最後まで実行するようにします。

server.close()でできます。

process.on("SIGINT", () => {
  console.info("SIGINT signal received.");

  server.close((err: any) => {
    console.log("Http server closed.");
    if (err) {
      console.error(err);
      process.exit(1);
    }
  });
});

この時点で先程のcurl: (52) Empty reply from serverも出なくなります。

サーバ

$ npx ts-node src/index.ts
Express server listening on port 8000
^CSIGINT signal received.
Http server closed.

クライアント

$ curl localhost:8000
Hello world

4. その他のリソースを閉じる

必要であればDBなどの接続もきちんと後片付けをして、TCPハーフオープンなコネクションが生まれたりしないようにします。

process.on("SIGINT", () => {
  console.info("SIGINT signal received.");

  server.close((err: any) => {
    console.log("Http server closed.");
    if (err) {
      console.error(err);
      process.exit(1);
    }

    mongoose.connection.close(false).then(() => {
      console.log("MongoDB connection closed.");
      process.exit(0);
    });
  });
});

その他

今回のサンプルコードはこちら

github.com

まとめ

Node.jsでのGraceful Shutdownの導入方法を紹介しました。

参考