Microsoft Fabricでのグラフの GQL クエリ パフォーマンスの最適化

この記事では、Microsoft Fabricでグラフを操作するときに予測可能かつ効率的に実行される GQL (Graph クエリ言語) クエリの記述に関するガイダンスを提供します。 推奨事項は、現在のプラットフォームの動作と文書化された制約に基づいています。

グラフサイズ、結果サイズ、およびクエリタイムアウトのハード制限については、「 現在の制限事項」を参照してください。 この記事のいくつかの推奨事項は、グラフ スキーマの設計方法にも関連しています。 詳細については、「 グラフ スキーマの設計」を参照してください。

フィルターをその意味論に基づいて配置する

どのノードや辺がマッチに参加できるかを定義した述語をグラフパターン内に配置します。 完了したマッチを後からフィルタリングするには文レベルの MATCH ... WHERE 条件を使用するか、述語が前のステートメントによって生成された行に適用される場合は別の FILTER ステートメントを使用します。

例えば、マッチしたノードの条件に対してパターンレベルの WHERE 節を用いましょう。

MATCH (p:Person WHERE p.birthday < 19940101)-[:workAt]->(c:Company WHERE c.id > 1000)
RETURN p.firstName, p.lastName, c.name

別の FILTER は通常の強制マッチの後に同じ条件を表現し、デフォルトの ALL 経路探索を用います。

MATCH (p:Person)-[:workAt]->(c:Company)
FILTER p.birthday < 19940101 AND c.id > 1000
RETURN p.firstName, p.lastName, c.name

クエリオプティマイザは、クエリの意味を保持する際にスキャン中に同等の述語を適用できるため、インライン構文が本質的に高速であるわけではありません。 条件が適用される時を表す形を選びます。

述語の配置は ANY SHORTESTによって結果を変えることがあります。 インライン述語は最短経路選択の対象となる経路を制約します。 パス選択後に文レベルの WHERE またはその後の FILTER が適用されるため、より長いパスを選ぶことなく、選択された最短パスを除去できます。 現在の MATCH ... WHERE 制限と信頼できる配置パターンについては、「 パス選択の前後の配置述語」を参照してください。

配置もまた、 OPTIONAL MATCHで重要で、インライン WHERE はオプションマッチを制約しますが、その後の FILTER でヌルエクステンダーの行を削除できます。

ヒント

パターン レベルの WHERE は、SQL JOIN ... ON 条件に似ています。 これは、後から結果の行をフィルタリングするのではなく、どの一致が条件を満たすかを記述します。

必要なプロパティのみを返します

シナリオで必要なノードとエッジのプロパティのみを返します。 プロパティのサブセットのみが必要な場合は、完全なノードを返したり、 RETURN * を使用したりしないでください。

不要なプロパティを選択すると、データの読み取り、シリアル化コスト、応答サイズが増加します。 グラフモデリング中は、ノードタイプのプロパティとして必要なソースカラムだけを選択してください。

推奨: 狭い投影。

MATCH (p:Person)-[:workAt]->(c:Company)
RETURN p.firstName, p.lastName, c.name

避ける: 完全なノードを返します。

MATCH (p:Person)-[:workAt]->(c:Company)
RETURN *

注

クエリまたは分析に必要な場合にのみ、グラフ モデリング中にノードの種類のプロパティを追加します。 ノードあたりのプロパティ数が少ないほど、storageとクエリのオーバーヘッドの両方が削減されます。

結果セットのサイズを制限する

カーディナリティが高いノードまたはリレーションシップに対してクエリを実行する場合は、 LIMIT またはその他の境界条件を適用します。 無制限のグラフの一致により、プラットフォームの制限に近づく非常に大きな結果セットが生成される可能性があります。

推奨: 境界付き結果。

MATCH (p:Person)-[:knows]->(friend:Person)
RETURN p.firstName, friend.firstName
LIMIT 1000

避ける: 制約のない高カーディナリティのマッチ。

MATCH (p:Person)-[:knows]->(friend:Person)
RETURN p.firstName, friend.firstName

Important

グラフは、内部のバイナリ表現が64MBを超えるクエリ応答を切り捨てます。 短縮された応答には、パブリックコード 01000 および標準的なGQLSTATUS 01M11を含む追加ステータスが含まれます。 フィルター、狭い投影、 LIMIT を使って結果のサイズを小さくしましょう。 詳細については、現在の制限事項に関する記事を参照してください。

トラバーサルを浅くし、目標に合わせて行う

深く入れ子になったグラフ パターンや複雑なグラフ パターンは避けてください。 特定の質問に直接答える、単純なターゲット トラバーサルを使用します。 可変長パターンの各余分なホップは、特に密に接続されたグラフで、エンジンが評価するパスの数を指数関数的に増加させることができます。

推奨: 厳密な制限。

-- Use the narrowest hop range that answers your question
MATCH (p:Person)-[:knows]->{1,3}(friend:Person)
RETURN p.firstName, friend.firstName
LIMIT 1000

避けること: 明確な必要性のない広い移動範囲。

-- A wider range on a dense graph is more expensive
MATCH (p:Person)-[:knows]->{1,8}(friend:Person)
RETURN *

Important

ビジュアルクエリビルダーは可変長パスを8ホップに制限していますが、このユーザーインターフェースの制限はコードエディタのGQLには適用されません。 シナリオが許す最もタイトな境界線を使いましょう。広い範囲はより多くの経路にマッチさせることができます。

経路がエッジを繰り返してはいけない場合はTRAILを使います

有効な経路が辺を繰り返してはならない場合 TRAIL パスモードを使用します。 サイクルを持つグラフでは、この制限によりデフォルトの WALK モードと比べてマッチング経路の数も減らすことができます。

-- TRAIL prevents revisiting the same :knows edge
MATCH TRAIL (src:Person)-[:knows]->{1,4}(dst:Person)
WHERE src.firstName = 'Alice' AND dst.firstName = 'Bob'
RETURN count(*) AS numPaths

TRAILがなければ、巡回グラフに対する同じクエリは、同じ辺を繰り返し通る経路を返すことがあります。 必要なパスセマンティクスに合ったモードを使い、一般的なパフォーマンス最適化として扱うのではなく TRAIL 。

無限の ALL WALK パターンはサポートされていません。なぜならサイクルは無限に多くの経路を生み出すことができるからです。 無限の TRAIL、 SIMPLE、 ACYCLIC パターンは終わるが、それでも多くの経路を列挙できる。 クエリが無制限のトラバーサルを必要としない限り、有限上限を用いてください。

効率的な結合に共有変数を使用する

クエリで複数のリレーションシップのデータが必要な場合は、共有変数を使用して同じエンティティのパターンを結合します。 共有変数がない場合、パターンはデカルト積 (両方のパターンからの一致のすべての組み合わせ) を生成でき、結果セットが大幅に大きくなります。

推奨: 共有変数 p パターンを結合します。

-- Single shared variable ensures an efficient join
MATCH (p:Person)-[:workAt]->(c:Company),
      (p)-[:isLocatedIn]->(city:City)
RETURN p.firstName, c.name AS company, city.name AS city
LIMIT 1000

避ける: 共有変数のない独立したパターン。

-- Without a shared variable, this produces a cartesian product
MATCH (p1:Person)-[:workAt]->(c:Company),
      (p2:Person)-[:isLocatedIn]->(city:City)
RETURN p1.firstName, c.name, p2.firstName, city.name

デカルト積は、1 つのパターンのすべての結果と、もう一方のパターンのすべての結果をペアにしています。 Person-workAt->Companyが 1,000 行に一致し、Person-isLocatedIn->Cityが 500 行に一致する場合、クエリは 1,000 × 500 = 500,000 行を返します。 共有変数を追加すると結合が制約されるため、一致するペアのみが返されます。

ノードを識別する際の主要なプロパティをフィルターしてください

ノードキー制約を定義し、ノードを一意に識別し、データの整合性を強制します。 特定のノードが必要な場合は、そのキープロパティをパターン述語に含めて、無関係なノードとマッチングしないようにしましょう。

たとえば、グラフの種類で、id ノードのキーとしてPersonが定義されている場合は、次のようになります。

CONSTRAINT person_pk
  FOR (n:Person) REQUIRE n.id IS KEY

次に、その人が必要なときは id で絞り込みます:

MATCH (p:Person WHERE p.id = 12345)-[:workAt]->(c:Company)
RETURN p.firstName, c.name

フィルターなしでは、クエリは workAt エッジをたどる前に、すべての Person ノードに一致します:

MATCH (p:Person)-[:workAt]->(c:Company)
RETURN p.firstName, c.name

ヒント

重要な制約は同一性と一意性を確立することです。 それだけでは特定の物理的な検索やクエリプランを保証するわけではありません。

適切なデータ型を選択する

各プロパティの値と意図された操作を表すデータ型を選択します。 例えば、数値を計算・比較する数値には、文字列として書式化された数字を保存するのではなく、数値型を使うと良いでしょう。

サポートされているデータ型については、「 現在の制限事項 - データ型 と サポートされるプロパティ型」を参照してください。

可能であれば、同じエッジを個別に走査する個別のクエリを発行するのではなく、1 つのグラフ パターンで関連エンティティを取得します。 トラバーサルを組み合わせると、冗長なパターン マッチングが回避され、N+1 クエリの問題が回避されます。最初の 1 つのクエリによって、結果行ごとに個別のクエリがトリガーされます。

推奨: 単一の結合パターン。

MATCH (c:Customer)-[:purchases]->(o:`Order`)-[:`contains`]->(product:`Product`)
RETURN c.fullName, o, product.productName
LIMIT 1000

避ける: 同じ Customer → Order エッジを走査する 2 つの個別のクエリ。

-- Query 1: fetch 100 orders
MATCH (c:Customer)-[:purchases]->(o:`Order`)
RETURN c.fullName, o
LIMIT 100

-- Query 2: repeat for each returned order, substituting its key value
MATCH (o:`Order` WHERE o.SalesOrderDetailID_K = 12345)-[:`contains`]->(product:`Product`)
RETURN o, product.productName

現実的なデータ ボリュームに対してクエリをテストする

小さなデータセットに対して適切に実行されるクエリは、直線的にスケーリングされない可能性があります。 予想される運用ワークロードを表すデータ ボリュームを使用してクエリをテストします。

  • フィルターと制限を含む保守的なクエリ図形を使用します。
  • 大規模なグラフに対する探索的な "すべてを返す" クエリは避けてください。
  • 20 分間のタイムアウト制限に対するクエリ期間を監視します。