AWS Lambda + Node.js で MySQLへのDBコネクションを切断する際の注意点

morry

Lambda内でMySQLへのDBコネクションをうまく切れずに、他のLambdaインスタンスの実行時にエラーが発生してしまうという事象にであったので、その原因と対処方法について記載しておこうと思います。同じ事象に会った方の助けになれば幸いです。

発生したエラー内容

1つのLambdaを5分間隔で繰り返しスケジュール実行している中で、不定期に次のエラーが発生していました。
内容としてはDBコネクションのタイムアウトエラーです。

ERROR	Uncaught Exception 	
{
    "errorType": "Error",
    "errorMessage": "Quit inactivity timeout",
    "code": "PROTOCOL_SEQUENCE_TIMEOUT",
    "fatal": true,
    "timeout": 30000,
    "stack": [
        "Error: Quit inactivity timeout",
        "    at Quit.<anonymous> (/var/task/node_modules/mysql/lib/protocol/Protocol.js:160:17)",
        "    at Quit.emit (node:events:517:28)",
        "    at Quit.emit (node:domain:489:12)",
        "    at Quit._onTimeout (/var/task/node_modules/mysql/lib/protocol/sequences/Sequence.js:124:8)",
        "    at Timer._onTimeout (/var/task/node_modules/mysql/lib/protocol/Timer.js:32:23)",
        "    at listOnTimeout (node:internal/timers:569:17)",
        "    at process.processTimers (node:internal/timers:512:7)"
    ]
}

発生状態

LambdaはNode.jsで記載されており、その中でデータベースへの接続と切断を行っています。

Node.js バージョン : Node.js 18

Lambdaコード概略

import mysql from 'mysql';

# MySQLへのコネクション作成
const dbConnectionToMySQL = mysql.createConnection({
        host: process.env.DBAPI_HOST,
        user: process.env.DBAPI_USER,
        password: process.env.DBAPI_PASSWORD,
        database: process.env.DBAPI_DATABASE,
      });

  try {
    # 各種処理
  } catch (e) {
    # エラー処理
    throw e;
  } finally {
    # MySQLへのコネクションを終了
    await dbConnectionToMySQL.end();
  }

エラー発生原因

エラーが発生していたのは、下記の箇所で適切にDBコネクションプールを破棄できていなかったことが原因でした。

finally {
    # MySQLへのコネクションを終了
    await dbConnectionToMySQL.end();
  }

end() ではDBへのコネクションプールは残っている状態であり、次のLambdaインスタンスへ流用されてしまう。そのためコネクションのタイムアウトが発生してしまってました。

end() の注意点は、コネクションのタイマーがクリアされないことであり、次に使用するときにタイムアウトの時間が過ぎているとエラーを引き起こします。
(この現象はLambdaにおいて、実行環境を再利用するウォームスタートの場合に発生します。実行環境がクリアされるコールドスタートの場合には発生しません。)

また、エラーが発生したLambdaでは、まず起動時にこのエラーが発生しますが、その後にハンドラーコードの実行は開始されるようで、途中でLambdaが止まってしまうというわけではないです。

解決方法

end()destroy() に変更し、強制的に接続を破棄することで解消できます。

finally {
    # MySQLへのコネクションを破棄
    await dbConnectionToMySQL.destroy();
  }

destroy()で接続プールを破棄することで、次のLambdaインスタンスで再利用されることがなくなり、タイムアウトエラーなどが生じなくなります。

最後に

こちらの記事が同じような現象に悩んでる方の一助になれば幸いです。

参考記事

同じ問題が発生しているissue
https://github.com/mysqljs/mysql/issues/2091

AUTHOR
morry
morry
記事URLをコピーしました