Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Node.js - Mysql「Error: Cannot enqueue Query after invoking quit.」の対処(Connection pool)

$
0
0

はじめに

本記事はデータベースにおけるコネクションプール(connection pool)について触れていくものです。

実際にハマったシチュエーションをもとに説明していきたいと思います。

  • 事象の詳細
  • 原因
  • 状況を再現してみる
  • 適切な対処方法
  • コネクションプールの実装

事象の詳細

Node.jsにて、Mysqlからデータを取得しようとしたとき、以下のエラーが発生した。

Error: Cannot enqueue Query after invoking quit.

どうやら2回目のGET:/api/todoを呼び出した時に必ず発生するようです。
その時のソースコードは以下です。

constexpress=require("express");constmysql=require("mysql");constapp=express();// データベースへのコネクションを生成constconnection=mysql.createConnection({// DB接続に関するオプション設定});connection.connect();app.get("/api/todo",(req,res)=>{constquery="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()を呼び出せばまた接続できるのでは?という発想でした。

状況を再現してみる

実装した処理

同一のコネクションに対して、複数回接続を行う処理を作成してみました。

constmysql=require("mysql");constconnection=mysql.createConnection({// DB接続に関するオプション設定});for(letindex=0;index<2;index++){connection.connect();constquery=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です。

constmysql=require("mysql");constpool=mysql.createPool({// DB接続に関するオプション設定});pool.query("SELECT * FROM todo;",(error,results)=>{if(error)throwerror;console.log(results[0]);});

プールが持つquery関数はpool.getConnection()connection.query()connection.release()を省略してくれます。

複数回のクエリ実行を行いたい場合などは以下です。

pool.getConnection((err,connection)=>{if(err)throwerr;connection.query("SELECT something FROM sometable",(error,results)=>{connection.release();if(error)throwerror;console.log(results[0])});});

おわり

データベースに関する基礎的な知識が不足していたせいで、こんなところでハマってしまいました。
基礎的な部分を固めることができたと思うので、次の課題に取り組んでいきたいと思います。

あと、mysqlライブラリのドキュメントに書いてあるとおりに実装していくと、コールバック地獄に陥りそうですね。
async/awaitなどを使ってもっとスマートに実装していきたいです。


Viewing all articles
Browse latest Browse all 8691

Trending Articles