スロークエリを改善したらECSの負荷が爆下がりした話(TypeORM)
TL;DR
- TypeORMで発生していたスロークエリを改善
- スロークエリを改善したらECSの負荷も減少
はじめに
スロークエリを改善したら、ECSコンテナ側の負荷も下がってなんでだろ?と思ったので記事にしようと思います。
環境
- TypeORM v0.3.20
- Node.js v18.x
- バックエンドインフラ ECS on Fargate => Amazon Aurora MySQL
負荷改善の前と後
まずはどのくらい改善したのかを示します。
この時ECSコンテナ8台動いてました。(4vCPU 8GBMem)
改善前
改善後
改善前と改善後は一日前の同じ時間帯のものです。
ちゃんと動いてるのか不安になるくらい下がってました笑
どのような対応をしたのか
スロークエリの出ていたクエリでMySQLの実行計画を確認しました。
TypeALL,index, Using Filesort等はなかったのですが、left join(以下join)しているテーブルが20近くありました。(中には使われていないテーブルも含まれてました笑)
1対多でjoinテーブルの先でさらにjoinしていたので、NLJ(ネステッド・ループ結合)の処理が重くなり、スロークエリが出ていたと想定されます。
今回は以下の対応をしました。
- joinテーブルの先でさらにjoinする部分を別の処理に分割
- 不要なjoinを削除
- selectで必要なカラムのみ選択
コードのイメージは以下のような形です。
const a = await
this.Repository.findOne({
where: {
id: request.id,
},
relations: {
hoge1: true,
hoge2: {
fuga1: true,
fuga2: true,
fuga3: true,
},
...
},
});
const a = await
this.Repository.findOne({
where: {
id: request.id,
},
relations: {
hoge1: true,
...
},
});
const b = await
this.Repository2.findOne({
select: {
id: true,
aId: true,
where: {
aId: request.id,
},
relations: {
fuga1: true,
fuga2: true,
fuga3: true,
},
});
対応としては、特別なことはしていないかなという感じですね。
なぜECSコンテナの負荷が下がったのか
今回はMySQLの実行計画を見て対応をしました。スロークエリが出ているのはAmazon Aurora MySQLの方で、何故ECSコンテナの負荷が下がるのかと修正したときは疑問でした。取得件数も1件で大量のデータをアプリケーション側で処理しているわけでもなかったので。
いろいろ調べてみたところ以下のissueが見つかりました。
どうやらTypeORMのRepositoryメソッドに関して、relationsで多くのテーブルをjoinしていると、MySQLで直接実行する場合と比べてパフォーマンスに影響が出るようです。影響が出る原因としてはsqlで取得したデータをTypeORMが変換する処理において、relationsが多いほど複雑になり変換処理が重くなる事が考えられます。
今回修正した箇所では高頻度で実行されていたので、顕著に影響が出たように感じます。
対策としては今回のように分割したりする方法、relationLoadStrategyのオプションでqueryを指定してrelationsの中身のクエリを分ける方法を取るのが良いのかなと思います。
後者の方法はrelations毎に個別のクエリが飛ぶようになるので、DBへの負荷状況に合わせて使い分けるのが良さそうです。
さいごに
今回joinが問題でスロークエリが出ていて、少ないデータ取得でもアプリケーションの負荷にまで影響するケースに遭遇し疑問におもったので色々調べて記事を書いてみました。
スロークエリが出るようなデータはrelationsが複雑になっている可能性があり、TypeORMの変換処理に影響を及ぼす恐れがあるので、ECSでスパイクが発生していたらスロークエリを見直してみるとよいかもしれません。
Discussion
実行計画見るの大事なんだなぁ〜
ORM側の処理で重くなることがあるのか!!
勉強になりました!
共有ありがとうございます!