はじめに
本記事はデータベースにおけるコネクションプール(Connection pool)
について触れていくものです。
実際にハマったシチュエーションをもとに説明していきたいと思います。
- 事象の詳細
- 原因
- 状況を再現してみる
- 適切な対処方法
- コネクションプールの実装
事象の詳細
Node.js
にて、MySQL
からデータを取得しようとしたとき、以下のエラーが発生した。
Error: Cannot enqueue Query after invoking quit.
どうやら2回目のGET:/api/todo
を呼び出した時に必ず発生するようです。
その時のソースコードは以下です。
const express = require("express");
const mysql = require("mysql");
const app = express();
// データベースへのコネクションを生成
const connection = mysql.createConnection({
// DB接続に関するオプション設定
});
connection.connect();
app.get("/api/todo", (req, res) => {
const query = "SELECT * FROM todo;";
connection.query(query, (err, rows) => {
if (err) console.log("err: ", err);
res.json(rows);
});
connection.end();
});
// サーバー起動
app.listen(8081, () => console.log("Example app listening on port 8081!"));
原因
コネクションは再利用できない。
変数connection
に対して、end()
を呼び出し、丁寧に接続を切っています。
そこで再度connection()
を呼び出せばまた接続できるのでは?という発想でした。
状況を再現してみる
実装した処理
同一のコネクションに対して、複数回接続を行う処理を作成してみました。
const mysql = require("mysql");
const connection = mysql.createConnection({
// DB接続に関するオプション設定
});
for (let index = 0; index < 2; index++) {
connection.connect();
const query = connection.query("SELECT * FROM todo;");
query.on("result", (row, index) => {
console.log("--- result ---");
console.log(row);
console.log(index);
});
connection.end();
}
処理結果
node
コマンドを使って、この処理を実行すると、以下のようなエラーになります。
Error: Cannot enqueue Handshake after invoking quit.
一度使ったんだから、ちゃんと破棄してくれってことですね。
適切な対処方法
何度もアクセス要求ができる接続窓口を作ってあげる。
実際のWebアプリケーションでは、同一のデータベースに対して何度も接続する処理が行われます。
規模にもよりますが、多人数で利用することを考えると、その数は膨大なものになります。
よって、以下の観点からコネクション確立処理は極力減らしたほうが良いです。
- 本処理自体が
オーバーヘッド(overhead)
である - コネクション確立には時間がかかるため、ユーザーを都度待たせてしまう
- コネクションの数だけDB側でメモリを確保する必要があるため、高負荷状態になりやすい
それらを実現するのがコネクションプール(Connection pool)
です。
- コネクションの状態を保持し、そのコネクションを使いまわすことができる
- コネクション数に上限を設けることができる
コネクションプールの実装
MySQL - Pooling connections を参考に、コネクションプールを作成し、複数回のデータベース接続処理を行ってみます。
使用する関数はcreatePool()
です。
実行するクエリが一つの場合、以下のような書き方でOKです。
const mysql = require("mysql");
const pool = mysql.createPool({
// DB接続に関するオプション設定
});
pool.query("SELECT * FROM todo;", (error, results) => {
if (error) throw error;
console.log(results[0]);
});
プールが持つquery
関数はpool.getConnection()
、connection.query()
、connection.release()
を省略してくれます。
複数回のクエリ実行を行いたい場合などは以下です。
pool.getConnection((err, connection) => {
if (err) throw err;
connection.query("SELECT something FROM sometable", (error, results) => {
connection.release();
if (error) throw error;
console.log(results[0])
});
});
おわり
データベースに関する基礎的な知識が不足していたせいで、こんなところでハマってしまいました。
基礎的な部分を固めることができたと思うので、次の課題に取り組んでいきたいと思います。
あと、ライブラリのドキュメントに書いてあるとおりに実装していくと、コールバック地獄に陥りそうですね。
async/awaitなどを使ってもっとスマートに実装していきたいです。