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などで終了したり、正常終了時でもコネクションを解放しない雑な実装だったりすると、サーバ側のコネクションは消えずに残ってしまいます。タイムアウトを無限大にしておくと、このようなゴミが永遠に解放されず少しずつ溜まっていき、いずれサーバのコネクション数の上限に達して障害の原因となります。

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

mongoDBクラスタにrestoreする時の高速化テクニック

developers.cyberagent.co.jp

この記事のように新しいmongoDBクラスタにrestoreでデータを移行する場合のテクニックですが、新クラスタへの移行前にバックアップデータ等でrestoreを行って暖気しておくというものがあります。

mongoDBの大規模なクラスタに巨大なデータをrestoreすると非常に時間がかかります。800GBのdumpデータをrestoreすると丸一日以上かかってしまいます。restoreでinsertされたデータは一旦プライマリシャードに保存され、データが増えると自動的に他のシャードにデータが分散されていくのですが、このチャンク移動の負荷がかなり大きいのです。

そこで事前にバックアップの本番データをdump、新クラスタにrestoreしてチャンク移動の完了を待ち、ドキュメントの保存先のシャードが決定された状態にします。この状態でデータをremove(dropするとシャーディングの情報も消えてしまうのでremove)してrestoreすると、最初から適切なシャードに分散してinsertされ、チャンク移動の負荷がなくなるので、restoreの所要時間は1/3 〜 1/4程度に短縮されます。