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

# 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](https://orkhan.gitbook.io/typeorm/docs/eager-and-lazy-relations)を指定することが出来ます。
これはFindメソッドでEntityを取得する際に、一緒にリレーション先のデータをLEFT JOINしてすべて取得するというオプションです。

```typescript:例 - 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](https://github.com/typeorm/typeorm/issues/2381)を経て、0.3系へのアップデートで追加された **"relationLoadStrategy"** というオプションで解決されました！

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

（ちなみに、[relationLoadStrategyの説明が不十分だ！というIssue](https://github.com/typeorm/typeorm/issues/9356)もあります）

## 解決策｜relationLoadStrategy: "query"

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

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

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

```typescript: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使用率のスパイクが治った](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333194/d043fde7-9cf4-2671-a8d4-ad38c81abb25.png)

# おわりに

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

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

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

<font color="#d3d3d3">~~早くPrismaに移行したい~~</font>


# 参考

- [Releases 0.3.0](https://github.com/typeorm/typeorm/releases/tag/0.3.0)
- [Hydration performance issue on complex dataset #2381](https://github.com/typeorm/typeorm/issues/2381)
- [Performance of query with many relations #3857](https://github.com/typeorm/typeorm/issues/3857)