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してすべて取得するというオプションです。
// 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オプションで指定してしまいましょう!
await tx.getEntityManager().getRepository(ExampleEntity).findOne({
where: { id: 1 },
loadEagerRelations: false,
relationLoadStrategy: "query",
relations: ["manyToOne", "oneToMany"]
})
リリースノートには、loadEagerRelationsをConnectionOptions
で指定する方法やQueryBuilder使用時に指定する方法も記載があります。
relationLoadStrategy: "query"
を設定することで見事にCPU使用率のスパイクが落ち着いてくれました
おわりに
この記事では、TypeORMのrelationLoadStrategyオプションを紹介しました。
執筆日時点では公式ドキュメントに載っていないので、これについて知らない方も多いかもしれないですね…
懸念点として、クエリ数は増えるのでDBへの負荷はこれから気にしていこうと思います
お役に立てたら幸いです、ご参考までに!
早くPrismaに移行したい