2024年2月21日

Prisma ORM が最適なJoin戦略を選択可能に (プレビュー)

SQLデータベースで複数のテーブルから関連データを取得するのは、コストがかかる場合があります。Prisma ORMでは、データベースレベルアプリケーションレベルの結合を選択できるようになったため、リレーションクエリに最適なアプローチを選択できます。

目次

Prisma ORMの新機能:最適なJoin戦略を選択可能に 🎉

データベースレベルの結合のサポートは、Prisma ORMで最も要望の多かった機能の1つであり、別のクエリ戦略として利用可能になったことを共有できることを嬉しく思います!

include(またはselect)を使用した任意のリレーションクエリには、トップレベルにrelationLoadStrategyという新しいオプションが追加されました。このオプションは、次の2つの可能な値のいずれかを受け入れます。

  • join (デフォルト): データベースレベルの結合戦略を使用して、データベース内のデータをマージします。
  • query: アプリケーションレベルの結合戦略を使用して、個々のテーブルに複数のクエリを送信し、アプリケーション層でデータをマージします。

新しいrelationLoadStrategyを有効にするには、まずプレビュー機能フラグをPrisma Clientのgeneratorブロックに追加する必要があります。

注意:relationLoadStrategyは、PostgreSQLおよびMySQLデータベースでのみ利用可能です。

完了したら、この変更を有効にするためにprisma generateを再実行し、クエリでリレーションロード戦略を選択する必要があります。

新しいjoin戦略を使用する例を次に示します。

"join"がデフォルトであるため、上記のコードスニペットではrelationLoadStrategyオプションを技術的に省略することもできることに注意してください。ここでは、説明のためにのみ示しています。

join vs query — どちらをいつ使用すべきか?

これらの2つのクエリ戦略により、いつどちらを使用すべきか疑問に思うでしょう。

Prisma ORMがPostgreSQLで使用するLATERAL、集約JOINと、MySQLの相関サブクエリにより、ほとんどの場合、join戦略の方が効率的である可能性が高いです(詳細については、後のセクションで説明します)。データベースエンジンは非常に強力で、クエリプランの最適化に優れています。この新しいリレーションロード戦略は、それに敬意を表しています。

ただし、テーブルごとに1つのクエリを実行し、アプリケーションレベルでデータをマージするために、query戦略を引き続き使用したい場合もあります。データセットとスキーマで構成されているインデックスによっては、複数のクエリを送信する方がパフォーマンスが向上する可能性があります。クエリのプロファイリングとベンチマークは、このような状況を特定するために重要になります。

もう1つの考慮事項は、複雑な結合クエリによって発生するデータベース負荷です。何らかの理由でデータベースサーバーのリソースが不足している場合は、フィルターとページネーションを含む複雑な結合クエリに必要な大量の計算を、スケールが容易なアプリケーションサーバーに移動することを検討できます。

まとめ

  • 新しいjoin戦略は、ほとんどのシナリオでより効率的になります。
  • queryは、データセットとクエリの特性によっては、よりパフォーマンスが高くなるエッジケースが存在する可能性があります。これらのシナリオを特定するために、データベースクエリをプロファイルすることをお勧めします。
  • データベースサーバーのリソースを節約し、スケールが容易なアプリケーションサーバーでデータのマージと変換の重労働を行いたい場合は、queryを使用してください。

SQLデータベースにおけるリレーションの理解

Prisma ORMのJOIN戦略について学んだので、リレーションクエリがSQLデータベースで一般的にどのように機能するかを確認しましょう。

リレーションのフラットデータ構造とネストされたデータ構造

SQLデータベースは、フラット(つまり、正規化された)形式でデータを格納します。エンティティ間のリレーションは、テーブル間の参照を指定する外部キーを介して表現されます。

一方、アプリケーション開発者は通常、ネストされたデータ、つまり、他のオブジェクトを任意に深くネストできるオブジェクトを扱うことに慣れています。

これは、データがディスク上およびメモリ上に物理的にレイアウトされる方法だけでなく、データのメンタルモデルと推論においても大きな違いがあります。

リレーショナルデータはアプリケーション開発者向けに「マージ」する必要がある

関連データはデータベースに物理的に別々に格納されているため、アプリケーション開発者が慣れ親しんでいるネストされた構造にするには、どこかでマージする必要があります。このマージは「結合」とも呼ばれます。

この結合が発生する可能性のある場所は2つあります。

  • データベースレベル: 単一のSQLクエリがデータベースに送信されます。クエリはJOINキーワードまたは相関サブクエリを使用して、データベースに複数のテーブル間の結合を実行させ、ネストされた構造を返します。
  • アプリケーションレベル: 複数のクエリがデータベースに送信されます。各クエリは単一のテーブルにのみアクセスし、クエリ結果はアプリケーション層のメモリ内でマージされます。これは、v5.9.0より前のPrisma Clientがサポートしていた唯一のクエリ戦略でした。

どちらのアプローチがより望ましいかは、使用するデータベース、データセットのサイズと特性、およびクエリの複雑さによって異なります。どちらの戦略を使用することが推奨されるかを学ぶために読み進めてください。

内部で何が起こっているのか?

Prisma ORMは、PostgreSQLのLATERAL結合とDBレベルのJSON集約(例:json_aggを使用)、およびMySQLの相関サブクエリを使用して、新しいjoinリレーションロード戦略を実装しています。

次のセクションでは、PostgreSQLでのLATERAL結合とDBレベルのJSON集約アプローチが、プレーンな従来のJOINよりも効率的な理由を調査します。

JSON集約によるクエリ結果の冗長性の防止

データベースレベルのJOINを使用する場合、SQLクエリを構築するためのいくつかのオプションがあります。上記のPrismaスキーマのSQLテーブル定義を考えてみましょう。

すべてのユーザーを投稿とともに取得するには、単純なLEFT JOINクエリを使用できます。

これは、サンプルデータを使用した結果の例です。

この場合、user_name列の冗長性に注意してください。この冗長性は、結合されるテーブルが多いほど悪化するだけです。たとえば、別のCommentテーブルがあり、各コメントにPostテーブルのレコードを指すpostId外部キーがあると仮定します。

それを表すSQLクエリを次に示します。

ここで、最初の投稿に複数のコメントがあると仮定します。

この場合の結果セットのサイズは、結合されるテーブルの数とともに指数関数的に増加します。このデータはデータベースからアプリケーションサーバーにネットワーク経由で送信されるため、非常にコストがかかる可能性があります。

Prismaによって実装されたjoin戦略は、データベースレベルでJSON集約を使用してこの問題を解決します。

PostgreSQLの例を次に示します。json_aggjson_build_objectを使用して冗長性の問題を解決し、ユーザーごとの投稿をJSON形式で返します。

今回の結果セットには冗長なデータは含まれていません。さらに、データ構造はPrisma Clientによって返される形状をすでに備えているため、クエリエンジンで結果を変換する余分な作業が不要になります。

ページネーションとフィルターを使用したより効率的なクエリのためのLateral JOIN

リレーションクエリ(他のほとんどのクエリと同様)は、テーブルからすべてのデータをフェッチすることはほとんどなく、フィルターやページネーションなどの追加の結果セット制約が伴います。特にページネーションは、従来のJOINでは非常に複雑になる可能性があるため、別の例を見てみましょう。

10人のユーザーとユーザーあたり5つの投稿をフェッチするこのPrisma Clientクエリを考えてみましょう。

これを生のSQLで記述する場合、サブクエリ内でLIMIT句を使用することを検討するかもしれません。例:

ただし、内部のSELECTは実際にはユーザーあたり5つの投稿を返しません。代わりに、合計2つの投稿を返すため、もちろん望ましい結果ではありません。

従来のJOINを使用すると、row_number()関数を使用して、結果セット内のレコードにインクリメント整数を割り当てることで解決できます。これにより、ページネーションの計算を手動で実行できます。

ただし、このアプローチは非常に複雑になるのが早く、ページネーションされたリレーションクエリを構築するには理想的ではありません。

これらの種類のSQLクエリの保守、スケーリング、およびデバッグは困難であり、開発時間が何時間もかかる可能性があります。

ありがたいことに、新しいデータベースバージョンでは、新しい種類のクエリであるlateral JOINでこれが解決されています。

上記のクエリは、LATERALキーワードを使用することで簡略化できます。

これにより、クエリが読みやすくなるだけでなく、データベースエンジンはクエリの意図についてより多くを理解できるため、クエリを最適化する能力も向上する可能性があります。

結論

Prismaを使用したリレーションクエリからのデータを結合するためのさまざまなオプションを確認しましょう。

過去には、Prismaはアプリケーションレベルの結合戦略のみをサポートしており、データベースに複数のクエリを送信し、クエリエンジンの内部で予期されるJavaScriptオブジェクト構造にマージおよび変換するすべての作業を行っていました。

プレーンな従来のJOINを使用すると、データのマージはデータベースに委任されます。ただし、上記で説明したように、データ冗長性(リレーションクエリのテーブル数とともに結果セットが指数関数的に増加する)と、フィルターとページネーションを含むクエリの複雑さという問題があります。

これらの問題を回避するために、Prisma ORMは最新のlateral JOINをデータベースレベルでのJSON集約と組み合わせて実装しています。これにより、クエリを解決し、データを予期されるJavaScriptオブジェクト構造にするために必要なすべての重労働がデータベースレベルで行われます。

試してみてフィードバックを共有してください

リレーションクエリの新しいロード戦略をぜひお試しください。ご意見をお聞かせいただき、フィードバックをお寄せください

次回の投稿をお見逃しなく!

Prismaニュースレターに登録する