LoginSignup
4
0

TypeORMのFindでCPU使用率がスパイクするのはオプションで解決できるぞ!

Last updated at Posted at 2023-09-05

TL;DR

たくさんのリレーションを持つEntityを取得する際はEager Relationsを使用せず、Findメソッドのオプションに

  • loadEagerRelations: false
  • relationLoadStrategy: "query"
  • relations: [リレーションのあるフィールド達]

を指定してデータ取得しよう!

はじめに

私のチームで運用しているマイクロサービスでは、TypeORMを使ってデータの読み書きをしています。
そんな中、TypeORMによるデータ取得が原因でCPU使用率が100%までスパイクし、処理時間も長くなってしまうという問題がありました。
また、別チームではメモリを逼迫したという報告もあります。

ここでは、その解決策を紹介します。

使用環境

  • TypeORM v0.3.17
  • Node.js v18.x

本編

Eager Relations

TypeORMにはリレーションを持つEntityの取得を楽に行うために、リレーションのあるフィールドに対してEager Relationsを指定することが出来ます。
これはFindメソッドでEntityを取得する際に、一緒にリレーション先のデータをLEFT JOINしてすべて取得するというオプションです。

例 - Eager Relationsを指定したEntity
// Entity定義
@Entity()
export class ExampleEntity {
  @PrimaryGeneratedColumn({ comment: "ID" })
  id: number
  
  @Column({ type: "varchar", length: 255 })
  name: string

  @ManyToOne((type) => ManyToOne, { eager: true })
  manyToOne: ManyToOne

  @OneToMany((type) => OneToMany, { eager: true })
  oneToMany: OneToMany[]
}

// id:1のデータを取得(リレーション先のデータもすべて取得できる)
await tx.getEntityManager().getRepository(ExampleEntity).findOne({
  where: { id: 1 }
})

一見便利にみえるこのオプションですが、その性能に問題があります;;

問題点|データ取得後のオブジェクト整形が重い

リレーションの多いEntityをEager Relationsで取得すると、たくさんのJOINを含む長いクエリが生成されます。
TypeORMは、そのようなクエリで得たデータの扱いが下手っぴみたいです。かわいいですね。

これについて3年間ほどOpen状態だったIssueを経て、0.3系へのアップデートで追加された "relationLoadStrategy" というオプションで解決されました!

しかしこのオプション、v0.3のリリースノートに記載があるだけで、公式ドキュメントには説明がないのです。
そのせいで知らない人も多いかも知れませんね。
実際に他チームには、このオプションを使用せずに自力でありったけのEntityをかき集め、丁寧 丁寧 丁寧にオブジェクトに詰めるという力技を使っているところもありました。

(ちなみに、relationLoadStrategyの説明が不十分だ!というIssueもあります)

解決策|relationLoadStrategy: "query"

FindメソッドでrelationLoadStrategyオプションを指定すると、リレーション先のデータを取得するためのクエリ生成方法を指定することが出来ます。

デフォルトでは relationLoadStrategy: "join" として指定されており、リレーションをLEFT JOINしたクエリでデータを取得するため、上述の問題が発生します。
一方 relationLoadStrategy: "query" を指定すると、リレーションを別のクエリで取得してくれます。

「Entityの定義自体に手をつけるのは億劫だ…でもこの取得処理が原因なのはわかっている…」というときは、このようにFindオプションで指定してしまいましょう!

Findのオプションで指定
await tx.getEntityManager().getRepository(ExampleEntity).findOne({
  where: { id: 1 },
  loadEagerRelations: false,
  relationLoadStrategy: "query",
  relations: ["manyToOne", "oneToMany"]
})

リリースノートには、loadEagerRelationsをConnectionOptionsで指定する方法やQueryBuilder使用時に指定する方法も記載があります。

relationLoadStrategy: "query" を設定することで見事にCPU使用率のスパイクが落ち着いてくれました:tada:
CPU使用率のスパイクが治った

おわりに

この記事では、TypeORMのrelationLoadStrategyオプションを紹介しました。
執筆日時点では公式ドキュメントに載っていないので、これについて知らない方も多いかもしれないですね…

懸念点として、クエリ数は増えるのでDBへの負荷はこれから気にしていこうと思います:writing_hand:

お役に立てたら幸いです、ご参考までに!

早くPrismaに移行したい

参考

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0