Spanner Graph クエリを調整する際のベスト プラクティス

このドキュメントでは、Spanner Graph クエリのパフォーマンスを調整する際のベスト プラクティスについて説明します。これには、次の最適化が含まれます。

  • ノードとエッジの入力テーブルの完全スキャンを回避する。
  • クエリでストレージから読み取る必要があるデータの量を減らす。
  • 中間データのサイズを減らす。

低いカーディナリティのノードから開始する

低いカーディナリティのノードから開始するようにパス トラバーサルを記述します。このアプローチでは、中間結果セットを小さく保ち、クエリの実行を高速化します。

たとえば、次のクエリは同じ意味になります。

  • フォワード エッジ トラバーサル:

    GRAPH FinGraph
    MATCH (p:Person {name:"Alex"})-[:Owns]->(a:Account {is_blocked: true})
    RETURN p.id AS person_id, a.id AS account_id;
    
  • リバース エッジ トラバーサル:

    GRAPH FinGraph
    MATCH (a:Account {is_blocked:true})<-[:Owns]-(p:Person {name: "Alex"})
    RETURN p.id AS person_id, a.id AS account_id;
    

Alex という名前のユーザーがブロックされたアカウントよりも少ない場合、このクエリはフォワード エッジ トラバーサルで記述することをおすすめします。

可変長のパス トラバーサルでは、低いカーディナリティのノードから開始することが特に重要です。次の例は、特定のアカウントから 3 回以内の移動でアカウントを見つける場合の推奨方法を示しています。

GRAPH FinGraph
MATCH (:Account {id: 7})-[:Transfers]->{1,3}(a:Account)
RETURN a.id;

デフォルトですべてのラベルを指定する

ラベルが省略されている場合、Spanner Graph は適格なノードとエッジラベルを推測します。可能であれば、すべてのノードとエッジに対してラベルを指定することをおすすめします。この推定が常に可能とは限らず、スキャンに必要以上のラベルが発生する可能性があるためです。

単一の MATCH ステートメント

次の例では、特定のアカウントから最大 3 回の移動でリンクされているアカウントを検索します。

GRAPH FinGraph
MATCH (src:Account {id: 7})-[:Transfers]->{1,3}(dst:Account)
RETURN dst.id;

複数の MATCH ステートメント

同じ要素を参照していて複数の MATCH ステートメントにまたがっているノードとエッジにラベルを指定します。

次の例は、この推奨のアプローチを示しています。

GRAPH FinGraph
MATCH (acct:Account {id: 7})-[:Transfers]->{1,3}(other_acct:Account)
RETURN acct, COUNT(DISTINCT other_acct) AS related_accts
GROUP BY acct

NEXT

MATCH (acct:Account)<-[:Owns]-(p:Person)
RETURN p.id AS person, acct.id AS acct, related_accts;

IS_FIRST を使用してクエリを最適化する

IS_FIRST 関数を使用すると、グラフ内のエッジをサンプリングしてトラバーサルを制限することで、クエリのパフォーマンスを向上させることができます。この関数は、カーディナリティの高いノードを処理し、マルチホップ クエリを最適化する際に役立ちます。

指定したサンプルサイズが小さすぎると、クエリからデータが返されないことがあります。そのため、返されるデータとクエリ パフォーマンスの向上との最適なバランスを見つけるには、さまざまなサンプルサイズが必要になる場合があります。

これらの IS_FIRST の例では、Account ノードと送金の Transfers エッジを含む金融グラフである FinGraph を使用しています。FinGraph を作成してサンプルクエリの実行に使用するには、Spanner Graph を設定してクエリを実行するをご覧ください。

クエリのパフォーマンス向上のためにトラバースされるエッジを制限する

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

スーパーノードを含むグラフのクエリを最適化するには、FILTER 句内で IS_FIRST 関数を使用して、クエリがノードから走査するエッジの数を制限します。FinGraph のアカウントは他のアカウントよりもトランザクション数が大幅に多い可能性があるため、IS_FIRST を使用して非効率的なクエリを防ぐことができます。この手法は、スーパーノードからのすべての接続の完全な列挙が必要ない場合に特に便利です。

次のクエリは、ブロックされたアカウント(a1)から直接または間接的に送金を受け取ったアカウント(a2)を検索します。このクエリでは、IS_FIRST を使用して、アカウントに多くの送金がある場合にパフォーマンスが低下しないように、各 Account で考慮する Transfers エッジの数を制限します。

GRAPH FinGraph
MATCH
(a1:Account {is_blocked: true})
-[e:Transfers WHERE e IN
  {
    MATCH -[selected_e:Transfers]->
    FILTER IS_FIRST(@max_transfers_per_account) OVER (
      PARTITION BY SOURCE_NODE_ID(selected_e)
      ORDER BY selected_e.create_time DESC)
    RETURN selected_e
  }
]->{1,5}
(a2:Account)
RETURN a1.id AS src_id, a2.id AS dst_id;

この例では、次のものを使用します。

  • @max_transfers_per_account: 各アカウント(a1)で考慮する Transfers エッジの最大数を指定するクエリ パラメータ。

  • PARTITION BY SOURCE_NODE_ID(selected_e): IS_FIRST の上限が各アカウント(a1)に個別に適用されるようにします。

  • ORDER BY selected_e.create_time DESC: 最新の転送が返されることを指定します。

マルチホップ クエリを最適化するために中間ノードをサンプリングする

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

GRAPH FinGraph
MATCH (a1:Account {is_blocked: true})-[e1:Transfers]->(a2:Account)
FILTER IS_FIRST(1) OVER (PARTITION BY a2)
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;

IS_FIRST がこのクエリを最適化する方法を理解するには:

  • FILTER IS_FIRST(1) OVER (PARTITION BY a2) は、最初の MATCH ステートメントに適用されます。

  • 中間アカウント ノード(a2)ごとに、IS_FIRST は最初の受信 Transfers エッジ(e1)のみを考慮し、2 番目の MATCH ステートメントで探索するパスの数を減らします。

  • 特に a2 に多くの受信転送がある場合、2 番目の MATCH が不要なデータを処理しないため、2 ホップクエリ全体の効率が向上します。

次のステップ