DBのタイムアウトはサーバ側をクライアント側より長くする

多くのデータベースにはコネクションのタイムアウトという設定が存在します。指定した期間使われていないコネクションを解放するというもので、クライアント、サーバ双方で設定可能になっています。

タイムアウトはサーバ側の寿命がクライアント側よりも長くなるように設定すべきです。逆にしてしまうとサーバ側で切断されたコネクションに対してクライアントが読み書きを行おうとしてエラーになってしまうことがあります。

MySQLとnode.jsで再現すると以下のようになります。

$ node index.js
[ RowDataPacket { Variable_name: 'wait_timeout', Value: '28800' } ]
wait 11 seconds
[ RowDataPacket { '1': 1 } ]
end
$
$ mysql -u root -e "set global wait_timeout=10;"
$
$ node index.js
[ RowDataPacket { Variable_name: 'wait_timeout', Value: '10' } ]
wait 11 seconds
{ Error: Cannot enqueue Query after fatal error.
    at Protocol._validateEnqueue (/Users/nullpon/mysql-node/node_modules/mysql/lib/protocol/Protocol.js:201:16)
    at Protocol._enqueue (/Users/nullpon/mysql-node/node_modules/mysql/lib/protocol/Protocol.js:139:13)
    at PoolConnection.query (/Users/nullpon/mysql-node/node_modules/mysql/lib/Connection.js:208:25)
    at PoolConnection.query (internal/util.js:230:26)
    at __dirname (/Users/nullpon/mysql-node/index.js:21:59)
    at <anonymous> code: 'PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR', fatal: false }

index.jsのコードは以下になります

'use strict';
const {promisify} = require('util')
const mysql = require('mysql');

(async () => {
    var pool  = mysql.createPool({
        host     : 'localhost',
        user     : 'root',
        password : '',
        database : 'test',
        acquireTimeout: 60000 // timeout 60秒
    });
    const conn = await promisify(pool.getConnection).bind(pool)();

    const result1 = await promisify(conn.query).bind(conn)('show global variables like \'wait_timeout\'');
    console.log(result1);

    console.log('wait 11 seconds');
    await new Promise((r) => setTimeout(r, 11000))

    const result2 = await promisify(conn.query).bind(conn)('SELECT 1');
    console.log(result2);

    conn.release();

    await promisify(pool.end).bind(pool)();

})().then(() => {
    console.log('end');
}).catch((e) => {
    console.log(e);
});

Redisのようにデフォルトでサーバ側のタイムアウトが無限大となっているDBもあります。特にAWSのElastiCacheでありがちなのですが、アプリがOOMなどで終了したり、正常終了時でもコネクションを解放しない雑な実装だったりすると、サーバ側のコネクションは消えずに残ってしまいます。タイムアウトを無限大にしておくと、このようなゴミが永遠に解放されず少しずつ溜まっていき、いずれサーバのコネクション数の上限に達して障害の原因となります。

こういう事態を防ぐためにもサーバ側でもタイムアウトを設定した方が無難なのですが、クライアントよりも短い値を設定しないように注意してください。