Hasura GraphQLエンジンは、パーミッションセーフな方法でGraphQLを使用してPostgresにクエリを実行するためのハイパーテキスト転送プロトコルアプリケーションプログラミングインターフェイスを提供します。
Postgresで外部キー制約を使用して、単一のリクエストで階層データをクエリできます。たとえば、このクエリを実行して、「アルバム」とそのすべての「トラック」を取得できます(「トラック」テーブルに「アルバム」テーブルへの外部キーがある場合)。
ご想像のとおり、クエリはテーブルを任意の深さまでトラバースできます。このクエリインターフェイスと権限を組み合わせることで、フロントエンドアプリケーションはバックエンドコードを記述せずにPostgresにクエリを実行できます。
アプリケーションプログラミングインターフェイスは、リソースを節約しながら(CPUとメモリの使用量が少ない)、高速(応答時間)で高スループット(1秒あたりの要求数)を処理するように設計されています。この目標を達成するためのアーキテクチャ上の決定について話し合いました。
データマイクロサービスのクエリは、次の段階を経ます。
- セッション解決:要求はゲートウェイに到着し、ゲートウェイは認証キー(存在する場合)を解決し、ユーザーIDとロールヘッダーを追加してから、要求をデータサービスにプロキシします。
- クエリ分析:データサービスはリクエストを受信し、タイトルを解析してユーザーIDとロールを取得し、サブジェクトをGraphQLASTとして解析します。
- クエリの検証:クエリが意味的に正しいかどうかを確認してから、ロールに定義されている権限を適用します。
- クエリの実行:検証されたクエリはSQLステートメントに変換され、Postgresで実行されます。
- 応答の生成: Postgresの結果が処理されてクライアントに送信されます(ゲートウェイは必要に応じてgzip圧縮を追加します)。
要件はおおまかに次のとおりです。
- ハイパーテキスト転送プロトコルスタックは、オーバーヘッドをほとんど追加せず、高スループットを得るために多数の同時要求を処理できる必要があります。
- クイッククエリ変換(GraphQLからSQL)
- コンパイルされたSQLクエリはPostgresで効率的であるはずです。
- Postgrisの結果は効果的に返送する必要があります。
以下は、GraphQLクエリに必要なデータを取得するためのさまざまな方法です。
GraphQLクエリの実行には、通常、各フィールドのパーサーの実行が含まれます。クエリの例では、2018年にリリースされたアルバムを取得する関数を呼び出し、次にこれらのアルバムごとに、トラックを取得する関数を呼び出します。これは、古典的なN +1クエリの問題です。クエリの数は、クエリの深さとともに指数関数的に増加します。
Postgresで実行されるクエリは次のとおりです。
これは、必要なすべてのデータを取得するためのN +1クエリの合計になります。
データローダーのようなプロジェクトは、バッチクエリを通じてN +1クエリの問題を解決することを目的としています。リクエストの数は、結果セットのサイズではなく、GraphQLクエリのノードの数に依存します。この場合、サンプルクエリでは、必要なデータを取得するためにPostgresに対して2つのクエリが必要です。
Postgresで実行されるクエリは次のとおりです。
これですべてのアルバムができました。目的のアルバムのすべてのトラックを取得するには:
これは合計2つのクエリです。各アルバムのトラック情報を取得するためのクエリの発行は避けますが、where句を使用して、1つのクエリで目的のアルバムのすべてのトラックを取得します。
データローダーは、さまざまなデータソース間で機能するように設計されており、単一のデータソースの機能を利用することはできません。この例では、唯一のデータソースはPostgresです。すべてのリレーショナルデータベースと同様に、Postgresは単一のクエリka接続で複数のテーブルからデータを収集する方法を提供します。GraphQLクエリに必要なテーブルを決定し、接続を使用して単一のクエリを生成し、すべてのデータを取得できます。したがって、GraphQLクエリに必要なデータは、単一のクエリから取得できます。クライアントに送信する前に、これらのデータを適切に変換する必要があります。
クエリは次のとおりです。
これにより、次のデータが提供されます。
アルバムID(_ d) |
アルバムタイトル(_ t) |
トラックID |
トラックタイトル(_ t) |
1 |
アルブミン1 |
1 |
track1 |
1 | アルブミン1 | 2 | track2 |
2 | アルブミンm2 | 空気 | 空気 |
このデータは、次の構造のJSON応答に変換する必要があります。
リクエストの処理にほとんどの時間が変換関数に費やされていることがわかりました(SQL結果をJSON応答に変換します)。変換関数を最適化するいくつかの方法を試した後、変換をPostgresにプッシュしてこの関数を削除することにしました。Postgres 9.4(最初のデータマイクロサービスがリリースされた頃にリリースされました)はJSON集計関数を追加しました。これは、Postgresへの変換を進めるのに役立ちました。生成されるSQLは次のようになります。
该查询的结果将有一列和一行,并且该值被发送到客户端,无需任何进一步的转换。 从我们的基准测试来看,这种方法大约比哈斯克尔的转换函数快3-6倍。
根据查询的嵌套级别和使用的条件,生成的SQL语句可能非常大而且复杂。 通常,任何前端应用程序都有一组用不同参数重复的查询。 例如,上述查询可以针对2017年而不是2018年执行。 准备好的语句最适合这些用例。当你有复杂的SQL语句时,这些语句会随着一些参数的改变而重复。
因此,第一次执行这个GraphQL查询时:
我们准备了SQL语句,而不是直接执行它,所以生成的SQL将是(注意$1):
接下来执行这个准备好的语句:
当GraphQL查询更改为2017年时,我们只需直接执行准备好的语句:
根据GraphQL查询的复杂程度,这大致可以使我们提高10-20% .
Haskell非常适合各种原因:
- 具有出色性能的编译语言(本)
- 高效能的HTTP堆栈(warp,warp的体系结构)
- 我们之前使用该语言的经验
高效能的HTTP堆栈(翘曲,翘曲的体系结构(
这是Hasura的建筑与Prisma和Postgraphile的比较。
我们之前使用该语言的经验
- 8GB RAM,i7笔记本电脑
- Postgres在同一台机器上运行
- wrk被用作基准测试工具,并且针对不同类型的查询,我们尝试“每秒”最大化请求
- 查询Hasura GraphQL引擎的单个实例
- 连接池大小:50
- 数据集:chinook
查询1:tracks_media_some
- 每秒请求数:1375 req / s
- 5毫秒
- 30%
- 30MB(Hasura)+ 90MB(Postgres)
查询2:tracks_media_all
- 每秒请求数:410 req / s
- 延迟时间:59毫秒
- 查询1:轨道_媒体_部分100%
- 每秒请求数:1375次请求/秒每秒请求数:410次请求/秒
查询3:album_tracks_genre_some
- 每秒请求数:1029 req / s
- 延迟时间:24ms
- 30%
- 30%30MB(Hasura)+ 90MB(Postgres)
查询4:album_tracks_genre_all
- 每秒请求数:328 req / s
- CPU:100%
- 30MB(Hasura)+ 130MB(Postgres)