GraphQLロゴGraphQL

@defer および @stream ディレクティブによるレイテンシの改善

12/8/2020執筆者Rob Richard、Liliana Matos

Rob RichardとLiliana Matosは、1stDibs.comのフロントエンドエンジニアです。彼らは、@defer および @stream ディレクティブの推進者としてGraphQLワーキンググループと協力してきました。

Lee ByronがGraphQL Europe 2016で初めて語って以来、@defer および @stream ディレクティブは待望の機能でした。 2020年の大半で、私たちはGraphQLワーキンググループと協力してこの機能を標準化してきました。現在、これはステージ2の提案ですが、さらに前進するために、GraphQLコミュニティにこれらのディレクティブを使用してみてフィードバックを提供することを期待しています。 私たちは、GraphQL.jsexpress-graphql の実験バージョンをリリースしました。 これらは、graphql@experimental-stream-defer および express-graphql@experimental-stream-defer としてnpmに公開されています。 この機能に興味のある方は、これらのリリースを試してみて、フィードバック用のissueに結果をお知らせください。 この提案が提供する内容の詳細については、以下をお読みください。

GraphQLのリクエスト/レスポンスモデルの欠点の1つは、GraphQLレスポンスがリクエスト全体の処理が完了するまでクライアントに返されないことです。 ただし、すべてのリクエストデータが同じ重要度を持つとは限らず、ユースケースによっては、アプリケーションがリクエストデータのサブセットに対して動作できる場合があります。 GraphQLサーバーが最も重要なデータを準備ができ次第送信できれば、アプリケーションのインタラクティブまでの時間を短縮できます。 新しい@defer および @stream ディレクティブを使用すると、GraphQLサーバーは単一のGraphQLレスポンスから複数のペイロードを返すことで、まさにそれを行うことができます。

@defer ディレクティブは、フラグメントスプレッドとインラインフラグメントに適用できます。 これは、開発者がクエリの特定部分を即時返却に不可欠ではないとマークするための宣言的な方法です。

@defer ディレクティブの例を次に示します。

リクエスト#

query {
person(id: "cGVvcGxlOjE=") {
name
...HomeworldFragment @defer(label: "homeworldDefer")
}
}
fragment HomeworldFragment on Person {
homeworld {
name
}
}

レスポンス#

ペイロード 1

{
"data": {
"person": {
"name": "Luke Skywalker"
}
},
"hasNext": true
}

ペイロード 2

{
"label": "homeworldDefer",
"path": ["person"],
"data": {
"homeworld": {
"name": "Tatooine"
}
},
"hasNext": false
}

GraphQL実行エンジンが@deferディレクティブに遭遇すると、実行をフォークして非同期的にそれらのフィールドの解決を開始します。 遅延ペイロードの準備中も、クライアントは最初のペイロードを受信して​​処理できます。 これは、遅延データが大きく、ロードに費用がかかり、インタラクティブ性のクリティカルパスにない場合に最も役立ちます。

@deferと同様に、@streamディレクティブも、結果全体が準備される前にクライアントがデータを受信できるようにします。 @stream はリストフィールドで使用できます。 @stream ディレクティブの例を次に示します。

リクエスト#

query {
person(id: "cGVvcGxlOjE=") {
name
films @stream(initialCount: 2, label: "filmsStream") {
title
}
}

レスポンス#

ペイロード 1

{
"data": {
"person": {
"name": "Luke Skywalker",
"films": [
{ "title": "A New Hope" },
{ "title": "The Empire Strikes Back" }
]
}
},
"hasNext": true
}

ペイロード 2

{
"label": "filmsStream",
"path": ["person", "films", 2],
"data": {
"title": "Return of the Jedi"
},
"hasNext": true
}

ペイロード 3

{
"label": "filmsStream",
"path": ["person", "films", 3],
"data": {
"title": "Revenge of the Sith"
},
"hasNext": false
}

GraphQL実行エンジンが@streamディレクティブに遭遇すると、initialCount引数で指定された数のリスト項目を解決します。 残りは非同期的に解決されます。 これは、最初の表示領域にわずかな要素しかレンダリングできないインターフェースに特に役立ちます。 クライアントは、サーバーがデータの残りの部分を解決している間、これらの要素をできるだけ早くレンダリングできます。

GraphQLの仕様ではトランスポートプロトコルを指定していませんが、@defer / @stream を使用したクエリの最も一般的なトランスポートは、チャンク転送エンコーディングを使用したHTTPであると予想されます。 これにより、GraphQLサーバーは標準のHTTP接続を開いたまま、各ペイロードを準備でき次第クライアントにストリーミングできます。 オーバーヘッドが少なく、ブラウザで何十年もサポートされており、ほとんどのインフラストラクチャで簡単に動作します。

これらのディレクティブの詳細については、以下をご覧ください。

– **Rob Richard**、**Liliana Matos**、フロントエンドエンジニアリング、1stDibs.com