グラフクエリのベスト プラクティス

このドキュメントでは、BigQuery Graph クエリを最適化するためのベスト プラクティスについて説明します。

カーディナリティの低いノードからパス トラバーサルを開始する

中間結果セットを小さく保ち、クエリの実行を高速化するには、パス トラバーサルの方向に関係なく、低いカーディナリティのノードからパス トラバーサルが開始されるようにグラフクエリを記述します。次の MATCH ステートメントでは、プロパティ フィルタを使用して、一致するすべてのノードを計算してからフィルタするのではなく、可能な開始ノードの数を減らしています。

MATCH (p:Person {id: 10})-[own:Owns]->(a:Account)
MATCH (a:Account WHERE balance > 10)<-[own:Owns]-(p:Person)

これは、特に次のような定量化されたパスクエリで重要です。

MATCH (p:Person {id: 10})-[own:Owns]->{1,3}(a:Account)

接続チェックに ANY または ANY SHORTEST 構文を使用する

定量化されたパスクエリは、ソースノードと宛先ノードの間の重複するパスを返すことがあります。接続を確認することが目的で、可能なすべてのパスが必要ない場合は、ANY または ANY SHORTEST を使用して、冗長な計算を減らし、パス検索の効率を向上させます。たとえば、次の MATCH ステートメントは、ANY SHORTEST を使用して、ノードの各ペア間のパスを 1 つだけ保持します。

MATCH ANY SHORTEST (a1:Account)-[t:Transfers]->{1,3}(a2:Account)

方向パス トラバーサルを使用する

BigQuery グラフ スキーマは方向性があります。つまり、各エッジにはソースノードと宛先ノードがあります。グラフクエリ構文では任意の方向のパス トラバーサル(-[edge]- など)が許可されていますが、パフォーマンスを向上させるには、方向付きパス トラバーサル(-[edge]-><-[edge]- など)を使用することをおすすめします。任意の方向のパス トラバーサルは、パフォーマンスの低下を引き起こす可能性があります。

次の MATCH ステートメントは、任意の方向のパス トラバーサルを使用します。

-- Avoid.
MATCH (a1:Account {id: 7})-[t:Transfers]-(a2:Account)

代わりに、2 つの方向性トラバーサルを UNION ALL で組み合わせます。

MATCH (a1:Account {id: 7})-[t:Transfers]->(a2:Account)
...
UNION ALL
...
MATCH (a1:Account  {id: 7})<-[t:Transfers]-(a2:Account)

ラベルを明示的に指定する

クエリでノードラベルまたはエッジラベルが省略されている場合、BigQuery Graph は条件を満たすノードラベルとエッジラベルをすべて列挙します。この列挙により、必要以上に多くのラベルがスキャンされる可能性があります。これを回避するには、可能な限り、クエリ内のすべてのノードとエッジにラベルを指定します。

たとえば、次のクエリは Account ラベルと Transfers ラベルを指定します。

GRAPH graph_db.FinGraph
MATCH (a1:Account)-[t:Transfers]->(a2:Account)
RETURN COUNT(*) AS num_transfers;

ラベルを省略すると、ノード間の不要な関係がスキャンされる可能性があるため、ラベルは省略しないようにしてください。次のクエリでは、a1 はアカウントまたは人物を表し、t は移行またはアカウントの所有権を表します。

GRAPH graph_db.FinGraph
MATCH (a1)-[t]->(a2)
RETURN COUNT(*) AS num_transfers;

単一の MATCH ステートメントを優先する

BigQuery Graph を使用すると、1 つのグラフクエリに複数の MATCH ステートメントを含めることができます。これらのステートメントは、同じノードまたはエッジを表す複数宣言された変数によって接続されます。ただし、複数の MATCH ステートメントを使用すると、ステートメント間のカーディナリティのメリットが低下する可能性があります。パフォーマンスを向上させるため、可能であれば 1 つの MATCH ステートメントを使用します。

たとえば、次のクエリは同等ですが、最初のクエリは 1 つの MATCH ステートメントを使用しているため、パフォーマンスが向上します。

-- Preferred syntax.
GRAPH graph_db.FinGraph
MATCH
  (p:Person {id: 1})-[o:Owns]->
  (a:Account)-[t:Transfers]->(a2:Account)
RETURN o.account_id, t.amount;
-- Avoid this syntax.
GRAPH graph_db.FinGraph
MATCH (p:Person {id: 1})-[o:Owns]->(a:Account)
MATCH (a:Account)-[t:Transfers]->(a2:Account)
RETURN o.account_id, t.amount;

カーディナリティの高いノードからトラバースされるエッジを制限する

グラフをクエリすると、他のノードと比較して、入力エッジまたは出力エッジの数が大幅に多いノードが存在する場合があります。このようなカーディナリティの高いノードは、スーパーノードまたはハブノードと呼ばれることもあります。スーパーノードは、大量のデータの処理を伴うトラバーサルが発生し、データスキューや実行時間の長期化につながるため、パフォーマンスの問題を引き起こす可能性があります。

スーパーノードを含むグラフのクエリを最適化するには、MATCHFILTER 句または WHERE 句内で ROW_NUMBER() 関数を使用して、クエリがノードから走査するエッジの数を制限します。この手法は、スーパーノードとの間のすべての接続の完全な列挙が必要ない場合に特に便利です。

たとえば、FinGraph の一部のアカウントでトランザクション数が非常に多い場合は、ROW_NUMBER() を使用して、各 Account で考慮する Transfers エッジの数を制限し、非効率的なクエリを回避できます。

GRAPH graph_db.FinGraph
MATCH (a1:Account)-[e1:Transfers WHERE e1 IN {
  GRAPH graph_db.FinGraph
  -- Sample 5 edges per source node
  MATCH -[selected_e:Transfers]->
    FILTER ROW_NUMBER() OVER (
      PARTITION BY SOURCE_NODE_ID(selected_e)) < 5
    RETURN selected_e
}]->{1,3}(a2:Account)
RETURN COUNT(*) AS cnt;

マルチホップ クエリで中間ノードまたはエッジをサンプリングする

また、ROW_NUMBER() を使用してマルチホップ クエリの中間ノードをサンプリングすることで、クエリの効率を向上させることもできます。この手法では、各中間ノードでクエリが考慮するパスの数を制限することで、効率を向上させます。これを行うには、マルチホップ クエリを NEXT で区切られた複数の MATCH ステートメントに分割し、サンプリングが必要な中間点で ROW_NUMBER() を適用します。

GRAPH graph_db.FinGraph
MATCH (a1:Account)-[e1:Transfers]->(a2:Account)
-- Sample 5 destination nodes per a1 source node
FILTER ROW_NUMBER() OVER (PARTITION BY ELEMENT_ID(a1)) < 5
RETURN a1, a2
NEXT
MATCH (a2)-[e2:Transfers]->(a3:Account)
RETURN a1.id AS src_id, a2.id AS mid_id, a3.id AS dst_id;

次のステップ