TypeORM 0.3.10→0.3.20で大幅改善!relationLoadStrategy: query の問題が解決
最初に
この記事では、TypeORMのバージョンを0.3.10から0.3.20にアップデートした際に発生した主な変化について解説します。
2025年2月現在、TypeORMの最新バージョンは0.3.20であり、2024年1月以降アップデートは行われていないようです。
バージョンを上げたことで起きた大きな変化
0.3.20のリリースノートで注目すべきポイントとして、以下の内容が挙げられます。
hangup when load relations with relationLoadStrategy: query
リレーションをrelationLoadStrategy: queryでロードすると、ハングアップ(フリーズ)する可能性がある。
ハングアップの原因
relationLoadStrategy: queryでは、リレーションごとに個別のSQLクエリが発行される仕組みです。このため、リレーションの数が増えるとクエリ数が膨大になり、データベース接続プールが枯渇することで性能問題やハングアップを引き起こします。
0.3.20ではこの問題が解消され、性能が大幅に向上しています。
relationLoadStrategyとは?
TypeORMのバージョン0.3.0から導入された設定で、リレーションのロード方法を指定できます。主に以下の2つがあります。
1. relationLoadStrategy: query
- 仕組み:各リレーションについて個別にSQLクエリを発行。
- 特徴:シンプルなリレーション構造では効率的。リレーションが複雑になるとSQLの発行数が増え、性能問題を引き起こしやすい。
2. relationLoadStrategy: join
- 仕組み:JOINを使用してリレーションを1つのSQLクエリで取得。
- 特徴:クエリ発行数を抑えることが可能。リレーションが深い場合やデータ量が多い場合、JOIN結果が大きくなりパフォーマンスに影響することもある。
検証してみた
実際にrelationLoadStrategyを設定して発行されるSQLを検証してみました。
relationLoadStrategy: queryの場合
UserとSchoolが1対多(OneToMany)のリレーションで、以下のようなクエリを実行します。
userRepository.find({
where: { email: email },
relations: {
school: true,
},
relationLoadStrategy: "query"
});
発行されるSQLは以下の通りです。
-- Userを検索
SELECT
"User"."id",
"User"."first_name",
"User"."last_name",
"User"."school_id"
FROM
"users" "User"
WHERE
"User"."email" = $1;
-- Schoolを取得
SELECT
"School"."id" AS "School_id",
"School"."name" AS "School_name",
"School"."created_at" AS "School_created_at",
"School"."updated_at" AS "School_updated_at"
FROM
"schools" "School"
INNER JOIN "users" "User" ON "User"."school_id" = "School"."id"
AND "User"."deleted_at" IS NULL
WHERE
"User"."id" IN ($1) -- PARAMETERS: ["20c12a52-a64b-018f-8809-55314b80ff0f"]
- クエリ数: 2つのSQLが発行されています。リレーションごとに個別のクエリが発行されるため、リレーションが多いほどクエリ数が増えます。そのため、relationsで指定したテーブルの数だけクエリが発行されるということです。
relationLoadStrategy: joinの場合
同じ条件でrelationLoadStrategy: joinを設定すると、以下のようなクエリが発行されます。
userRepository.find({
where: { email: email },
relations: {
school: true,
},
relationLoadStrategy: "join"
});
SELECT
"User"."id",
"User"."first_name",
"User"."last_name",
"School"."id" AS "School_id",
"School"."name" AS "School_name"
FROM
"users" "User"
LEFT JOIN "schools" "School"
ON "School"."id" = "User"."school_id"
WHERE
"User"."email" = $1;
- クエリ数: JOINによってリレーションを結合し、SQLクエリを1本にまとめます。
結果
設定 | クエリ数 | 特徴 |
---|---|---|
relationLoadStrategy:query | リレーションごとに1本 | クエリ数が多くなるため、複雑なリレーションでは非効率 |
relationLoadStrategy:join | 1本 | クエリ数は1本になるが、結果セットが大きくなると負荷が増加する可能性。 |
ハングアップの原因を検証
実際に、リレーションを多く持つクエリを実行して、接続プールの動作を確認しました。
async testConnectionPool(email: string): Promise<any> {
const sql = async () =>
await this.postgresDataSource.getRepository(User).find({
where: {
id: "XXXXXXXXXXX"
},
relations: {
school: {
departments: {
classes: {
students: true
},
},
affiliatedOrganizations: {
organizationDetails: true
},
},
projects: {
tasks: {
taskAssignees: true,
taskCategories: {
categoryDetails: true
},
},
},
resourceGroup: {
resources: {
resourceAttributes: true,
resourceTags: {
tagDetails: true
},
},
accessPermissions: {
permissionDetails: true
},
},
relationLoadStrategy: "query"
},
});
const log = async () => {
await new Promise((resolve) => {
let count = 0;
const interval = setInterval(() => {
const pool = (this.postgresDataSource.driver as any).master as Pool;
console.log("Total Clients:", pool.totalCount);
console.log("Idle Clients:", pool.idleCount);
console.log("Waiting Requests:", pool.waitingCount);
count++;
if (count >= 10) {
clearInterval(interval);
resolve("Completed");
}
}, 100);
});
};
await Promise.all([log(), sql()]);
}
- TypeORMのバージョン0.3.10の場合
Total Clients: 19
Idle Clients: 19
Waiting Requests: 0
クライアント数が多く、接続プールが逼迫する可能性。
- TypeORMのバージョン0.3.20の場合
Total Clients: 3
Idle Clients: 3
Waiting Requests: 0
バージョンアップにより接続プールの負荷が軽減され、効率的に動作。
結論
- TypeORM 0.3.10の課題
relationLoadStrategy: queryでリレーションを多用すると、SQL発行数が増加し、接続プールが枯渇する恐れがあります。 - TypeORM 0.3.20の改善
relationLoadStrategy: queryの性能が向上し、接続プールの負荷が軽減されました。
補足
複雑なリレーションを使用している場合は、TypeORM 0.3.20以降へのアップデートを推奨します。ただし、正規化は進んでおり、リレーションがそこまで深くないシステムの場合、そもそも影響が小さいケースもあります。
- 影響を受けやすいケース:リレーションのネストが深い・取得するテーブル数が多い
- 影響が小さいケース:リレーションが単純・取得データが少ない
また、リレーションの数が多い場合は、relationLoadStrategy: joinの利用を検討することでクエリ数を抑え、パフォーマンスをさらに向上させることが可能です。
最後に
最後まで、目を通していただきありがとうございました!アスエネでは、このように自分の強みを活かしながら、自分の目標目掛けてチャレンジできる魅力的な環境があります。全方位で採用強化中なので、ご興味がある方は、ぜひこちらの採用サイトからご連絡ください!
(SNSからカジュアルにご連絡いただくのも大歓迎です!)
Discussion