📌

スロークエリを改善したらECSの負荷が爆下がりした話(TypeORM)

2024/04/03に公開1

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が見つかりました。

https://github.com/typeorm/typeorm/issues/3857

どうやらTypeORMのRepositoryメソッドに関して、relationsで多くのテーブルをjoinしていると、MySQLで直接実行する場合と比べてパフォーマンスに影響が出るようです。影響が出る原因としてはsqlで取得したデータをTypeORMが変換する処理において、relationsが多いほど複雑になり変換処理が重くなる事が考えられます。

今回修正した箇所では高頻度で実行されていたので、顕著に影響が出たように感じます。
対策としては今回のように分割したりする方法、relationLoadStrategyのオプションでqueryを指定してrelationsの中身のクエリを分ける方法を取るのが良いのかなと思います。
後者の方法はrelations毎に個別のクエリが飛ぶようになるので、DBへの負荷状況に合わせて使い分けるのが良さそうです。

さいごに

今回joinが問題でスロークエリが出ていて、少ないデータ取得でもアプリケーションの負荷にまで影響するケースに遭遇し疑問におもったので色々調べて記事を書いてみました。

スロークエリが出るようなデータはrelationsが複雑になっている可能性があり、TypeORMの変換処理に影響を及ぼす恐れがあるので、ECSでスパイクが発生していたらスロークエリを見直してみるとよいかもしれません。

レバテック開発部

Discussion

neco3coffeeneco3coffee

実行計画見るの大事なんだなぁ〜

ORM側の処理で重くなることがあるのか!!

勉強になりました!
共有ありがとうございます!