業務でLambdaを書いてた時にmysqlの操作を非同期で処理する方法にかなり苦労した。
まぁLambdaがどうこうというよりはmysql操作の非同期処理の仕方でハマったんだけども。。
結論パッケージとしてmysqlではなくmysql2/promiseを使うことで解決したのでまとめておく。
前提
やりたかったことはLambdaからMySQLにアクセスして必要な情報を取得し、それをレスポンスとして返すこと。
まずmysqlライブラリが必要なのでローカルでnpm install mysql
でインストールしてそれをzip化してLambdaにアップロードした。
いきなり話逸れるんだけど、Lambdaでライブラリ入れる時一々ローカルでインストールしてアップロードしなきゃいけないのめんどい。
Lambda上でインストールできるようになってくれないかな。。
さて、Lambda上の関数がこちら。
const mysql = require("mysql");
exports.handler = async (event, context, callback) => {
const connection = await mysql.createConnection({
//hostとかuserとかpasswordの情報を環境変数から読み込む
});
const email = event.userName;
const myquery = `SELECT * FROM admin_users WHERE email = "${email}";`;
//MySQLの操作
connection.query(myquery, function(err, results, fields) => {
if(err) {
//エラーハンドリング
} else {
//色んな処理
context.succeed(event);
return;
}
});
}:
12行目が実際にMySQLの操作をする部分。
connection.queryの第一引数に処理するクエリを渡して、その結果をコールバック関数で受け取りその後の処理を行う。
このLambda関数はハイライトしてある16, 17行目で色々処理してレスポンスするんだけど、なぜか期待通りのレスポンスがされなかった。
この原因が全然分からなかったんだけど、結論、非同期処理がうまく実装できてなかったからだった。
うまく処理が行われなかった原因
先ほどのコードの該当部分がこちら。
//MySQLの操作
connection.query(myquery, function(err, results, fields) => {
if(err) {
//エラーハンドリング
} else {
//色んな処理
context.succeed(event);
return;
}
});
操作の流れは以下の通り。
①connection.queryでクエリ操作
②結果を受け取ってif文で色んな処理
③レスポンスを返す
なんだけど、①のconnection.queryを行うとMySQLに接続しているので時間がかかってしまう。
本当はその結果を受け取って②、③と処理が続いて欲しいんだけど、Lambda関数は①が実行されてから②を待つことなく次の処理に行ってしまう。
//MySQLの操作
①connection.query(myquery, ②function(err, results, fields) => {
if(err) {
//エラーハンドリング
} else {
//色んな処理
③context.succeed(event);
return;
}
});
//MySQLの操作
①connection.query(myquery, function(err, results, fields) => {
if(err) {
//エラーハンドリング
} else {
//色んな処理
context.succeed(event);
return;
}
});
}; ② //Lambda関数の終わり
つまり、本来はif文の条件分岐のどちらかでLambda関数が終わるはずなんだけど、Lambda関数自体が①の結果を待たずして処理が次に移行するため、せっかくのMySQLへの操作が意味ないものになってしまっている。
この原因を突き止めるのにかなり時間がかかった。。。
期待していない挙動が出てきたときにエラー内容とか見るけど、その原因が非同期処理になっていないからってのはまだまだ気づけてないなと反省。
後から見るとそりゃそうだって感じなんだが、、今度からは真っ先に疑うようにする。
解決方法
解決方法としては①の処理をawaitしてからその後の処理を行うように実装すればよい。
なんだけど、connection.query(myquery, function(err, results, fields) => {
の部分を第一引数をawaitしてその後にコールバック関数を呼び出すっていう処理の仕方が分からなかった。
頑張ればできるのかもしれないが、今回はmysql2/promiseを使って行った。
まずローカルでmysql2をnpm installして再びzip化したファイルをLambdaにアップロード(これがめんどい(2回目))。
const mysql = require("mysql2/promise");
exports.handler = async (event, context, callback) => {
const connection = await mysql.createConnection({
//hostとかuserとかpasswordの情報を環境変数から読み込む
});
const email = event.userName;
const myquery = `SELECT * FROM admin_users WHERE email = "${email}";`;
try {
const [rows] = await connection.execute(myquery);
//色んな処理
context.succeed(event);
return;
} catch(err) {
context.done(null, err);
return;
}
};
まずmysql2/promiseをimportする。
先ほどやりたかった処理は以下の通りで、今回これができるようになった。
①クエリ操作
②クエリ処理の結果を受け取って色んな処理
③レスポンスを返す
クエリ処理はconnection.execute(myquery)
でできるらしい。
queryとexecuteの使い分けはこちらを参照。
https://blog.senseshare.jp/query-prepare.html
そしてこのコードによって結果(rows)とテーブルのカラムの情報(fields)が配列として返ってくる。
今回は結果だけが欲しいので[rows]として受け取る。
この処理をawaitを使って書くことができる。
つまり、クエリ処理をしてその結果を受け取るまでをawaitで待つことができるので、その後に色んな処理をできるようになった。
そのため、クエリ処理の結果を待ってる間にLambdaの処理が先に行ってしまうということがなくなった。
ちなみにif文ではなくtry, catchで書いている。
まとめ
LambdaからMySQLへの接続方法を調べるとmysqlばっかり出てきてたが、非同期処理を行うならmysql2/promise
を使った方がよさそう。
ちなみにmysql2
だけではasync/ awaitは使えないらしい。
最近うまくいかない原因が非同期処理のことが多い。
エラーでわかりづらいからだと思うんだけど、少し気をつけて意識してみよう。
参考
[node.js] Express,mysql2/promiseでasync/await使って簡潔に書く
https://qiita.com/tatsumi44/items/4b0fe912a49025591945
コメント