Best practice per l'ottimizzazione delle query di Spanner Graph

Questo documento descrive le best practice per l'ottimizzazione delle prestazioni delle query Spanner Graph, che includono le seguenti ottimizzazioni:

  • Evita una scansione completa della tabella di input per nodi e archi.
  • Riduci la quantità di dati che la query deve leggere dallo spazio di archiviazione.
  • Riduci le dimensioni dei dati intermedi.

Inizia dai nodi con cardinalità inferiore

Scrivi l'attraversamento del percorso in modo che inizi con i nodi a cardinalità inferiore. Questo approccio mantiene ridotto il set di risultati intermedi e velocizza l'esecuzione delle query.

Ad esempio, le seguenti query hanno la stessa semantica:

  • Attraversamento del bordo in avanti:

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

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

Supponendo che il numero di persone con il nome Alex sia inferiore al numero di account bloccati, ti consigliamo di scrivere questa query nella traversata degli archi in avanti.

Iniziare dai nodi con cardinalità inferiore è particolarmente importante per l'attraversamento di percorsi di lunghezza variabile. Il seguente esempio mostra il modo consigliato per trovare gli account che si trovano a tre trasferimenti da un determinato account.

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

Specificare tutte le etichette per impostazione predefinita

Spanner Graph deduce i nodi e le etichette degli archi qualificati se le etichette vengono omesse. Ti consigliamo di specificare le etichette per tutti i nodi e gli archi ove possibile, perché questa inferenza potrebbe non essere sempre possibile e potrebbe causare la scansione di più etichette del necessario.

Singola istruzione MATCH

L'esempio seguente trova gli account collegati da un massimo di 3 trasferimenti dall'account specificato:

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

Nelle istruzioni MATCH

Specifica le etichette su nodi e archi quando si riferiscono allo stesso elemento, ma si trovano in istruzioni MATCH.

Il seguente esempio mostra questo approccio consigliato:

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;

Utilizzare IS_FIRST per ottimizzare le query

Puoi utilizzare la funzione IS_FIRST per migliorare il rendimento delle query campionando i bordi e limitando gli attraversamenti nei grafici. Questa funzione consente di gestire nodi con cardinalità elevata e ottimizzare query multihop.

Se la dimensione del campione specificata è troppo piccola, la query potrebbe non restituire dati. Per questo motivo, potresti dover provare diverse dimensioni del campione per trovare l'equilibrio ottimale tra i dati restituiti e il miglioramento delle prestazioni delle query.

Questi esempi di IS_FIRST utilizzano FinGraph, un grafico finanziario con Account nodi e Transfers archi per i trasferimenti di denaro. Per creare FinGraph e utilizzarlo per eseguire le query di esempio, consulta Configura ed esegui query su Spanner Graph.

Limita gli archi attraversati per migliorare le prestazioni delle query

Quando esegui query sui grafici, alcuni nodi possono avere un numero significativamente maggiore di archi in entrata o in uscita rispetto ad altri nodi. Questi nodi con cardinalità elevata vengono a volte chiamati super nodi o nodi hub. I super nodi possono causare problemi di prestazioni perché i relativi attraversamenti potrebbero comportare l'elaborazione di enormi quantità di dati, il che porta a una distorsione dei dati e a tempi di esecuzione lunghi.

Per ottimizzare una query di un grafico con supernodi, utilizza la funzione IS_FIRST all'interno di una clausola FILTER per limitare il numero di archi attraversati dalla query da un nodo. Poiché gli account in FinGraph potrebbero avere un numero di transazioni significativamente superiore rispetto ad altri, puoi utilizzare IS_FIRST per evitare una query inefficiente. Questa tecnica è particolarmente utile quando non è necessaria un'enumerazione completa di tutte le connessioni da un super nodo.

La seguente query trova gli account (a2) che ricevono trasferimenti direttamente o indirettamente da account bloccati (a1). La query utilizza IS_FIRST per evitare prestazioni lente quando un account ha molti trasferimenti limitando il numero di archi Transfers da considerare per ogni Account.

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;

Questo esempio utilizza quanto segue:

  • @max_transfers_per_account: un parametro di query che specifica il numero massimo di archi Transfers da considerare per ogni account (a1).

  • PARTITION BY SOURCE_NODE_ID(selected_e): garantisce che il limite di IS_FIRST venga applicato in modo indipendente per ogni account (a1).

  • ORDER BY selected_e.create_time DESC: specifica che vengono restituiti i trasferimenti più recenti.

Esegui il campionamento dei nodi intermedi per ottimizzare le query multihop

Puoi anche migliorare l'efficienza delle query utilizzando IS_FIRST per campionare i nodi intermedi nelle query multihop. Questa tecnica migliora l'efficienza limitando il numero di percorsi che la query considera per ogni nodo intermedio. Per farlo, dividi una query multihop in più istruzioni MATCH separate da NEXT e applica IS_FIRST a metà percorso, dove devi campionare:

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;

Per capire come IS_FIRST ottimizza questa query:

  • La clausola FILTER IS_FIRST(1) OVER (PARTITION BY a2) viene applicata nella prima istruzione MATCH.

  • Per ogni nodo dell'account intermedio (a2), IS_FIRST considera solo il primo arco in entrata Transfers (e1), riducendo il numero di percorsi da esplorare nella seconda istruzione MATCH.

  • L'efficienza complessiva della query a due hop viene migliorata perché il secondo MATCH non elabora dati non necessari, soprattutto quando a2 ha molti trasferimenti in entrata.

Utilizzare l'esecuzione fattorizzata per ottimizzare le query

Ottimizza le prestazioni delle query Spanner Graph fattorizzando una query che attraversa un pattern del grafico e crea risultati intermedi duplicati.

Una query del grafico può essere eseguita in modalità fattorizzata, che deduplica i prefissi dei percorsi con cardinalità elevata prima di attraversare il resto del percorso. Questa ottimizzazione migliora le prestazioni delle query riducendo i confronti ripetuti delle chiavi o i probe della tabella hash durante l'esecuzione delle query del grafico. Per fattorizzare una query, aggiungi il suggerimento @{factorized_mode} all'attraversamento del pattern individuale o a livello di query.

Imposta factorized_mode su uno dei seguenti valori:

  • factorize_left: ottimizza le traversie del grafico in cui molti percorsi convergono su pochi nodi. Spanner Graph deduplica i percorsi in base all'ID nodo di destinazione e poi recupera le proprietà del nodo per ridurre il lavoro ridondante.

  • factorize_both: ottimizza le query non lineari con più percorsi secondari che condividono i nodi. Spanner Graph evita di generare risultati intermedi per ogni percorso in modo indipendente. Calcola invece ogni percorso secondario una sola volta e poi li combina, ritardando il prodotto incrociato dei risultati fino alla fine. Utilizzalo per motivi come triangoli o rami.

Per saperne di più sui suggerimenti di attraversamento, consulta Suggerimenti per il grafico.

Gli esempi seguenti mostrano come utilizzare i suggerimenti per la modalità fattorizzata per migliorare il rendimento delle query grafiche. Per eseguire il codice campione, crea lo schema FinGraph in Configura ed esegui query su Spanner Graph.

Ridurre i risultati intermedi in una query lineare

Gli account fraudolenti spesso ricevono un volume elevato di transazioni. Le query progettate per identificare questi account possono generare risultati intermedi di grandi dimensioni, molti dei quali sono duplicati perché convergono su un piccolo insieme di account di destinazione. Il recupero delle proprietà dei nodi fraudolenti da questi risultati intermedi ridondanti richiede calcoli non necessari, che possono rallentare notevolmente le prestazioni delle query.

Il suggerimento @{factorized_mode} risolve questo problema ottimizzando l'esecuzione delle query. L'esempio seguente mostra come utilizzare questo suggerimento per recuperare i dettagli dell'account e della transazione. Traccia i trasferimenti provenienti da un account bloccato noto a account potenzialmente fraudolenti che si trovano a una distanza di 1-3 passaggi. La versione non ottimizzata di questa query è disponibile nella seconda scheda.

Query fattorizzata

GRAPH FinGraph
MATCH (a1:Account {is_blocked: true})-[e1:Transfers]->{1,3}@{factorized_mode=factorize_left}(a2:Account)
LET total_amount = SUM(e1.amount)
RETURN a2.id, a2.create_time, total_amount;

Query non ottimizzata

GRAPH FinGraph
MATCH (a1:Account {is_blocked: true})-[e1:Transfers]->{1,3}(a2:Account)
LET total_amount = SUM(e1.amount)
RETURN a2.id, a2.create_time, total_amount;

Quando il numero di nodi di destinazione distinti a2 è molto inferiore al numero di percorsi che portano a a2, questa tecnica impedisce il lavoro ridondante. Questo è comune nelle reti di frode, dove gli intermediari trasferiscono denaro a pochi destinatari. Il suggerimento fattorizzato esegue le seguenti azioni:

  • Trova tutti i percorsi che includono da uno a tre bordi a partire da un nodo bloccato e la somma dei trasferimenti lungo questi percorsi.

  • Determina il numero di nodi di destinazione distinti a2.

  • Recupera il create_time per ogni nodo di destinazione distinto a2 una sola volta.

  • Combina il create_time per ogni nodo a2 con l'elenco dei percorsi che portano a a2 per formare il risultato della query.

Posticipare la generazione dei risultati intermedi in query non lineari complesse

Le query del grafico hanno spesso una struttura complessa, costituita da diversi pattern di sottopercorso lineari. L'esempio seguente mostra una query che analizza le transazioni provenienti da un account con id uguale a 1. Questa query classifica tutti gli account di destinazione in base al loro create_time in tre bucket distinti:

  • Negli ultimi 30 giorni
  • Da 30 a 90 giorni fa
  • Più vecchie di 90 giorni

La query restituisce tutte le triple e l'importo totale delle transazioni.

Il suggerimento @{factorized_mode=factorize_both} ottimizza l'esecuzione delle query evitando la materializzazione anticipata dei risultati intermedi. Sfrutta il fatto che tutti i sottocammini nella clausola MATCH sono indipendenti in modo condizionale, dato il valore del nodo iniziale a. factorize_both valuta i primi due sottopercorsi in modo indipendente e non ripete la valutazione dell'ultimo sottopercorso.

Query fattorizzata

GRAPH FinGraph
MATCH (a:Account {id: 1})-[e1:Transfers]->(a1:Account),
  @{factorized_mode=factorize_both}(a:Account)-[e2:Transfers]->(a2:Account),
  (a:Account)-[e3:Transfers]->(a3:Account)
WHERE e1.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
  AND e2.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
  AND e2.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
  AND e3.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
LET total_amount = e1.amount + e2.amount + e3.amount
RETURN a1.id as a1_id, a2.id as a2_id, a3.id as a3_id, total_amount;

Query non ottimizzata

GRAPH FinGraph
MATCH (a:Account {id: 1})-[e1:Transfers]->(a1:Account),
  (a:Account)-[e2:Transfers]->(a2:Account),
  (a:Account)-[e3:Transfers]->(a3:Account)
WHERE e1.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
  AND e2.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
  AND e2.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
  AND e3.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
LET total_amount = e1.amount + e2.amount + e3.amount
RETURN a1.id as a1_id, a2.id as a2_id, a3.id as a3_id, total_amount;

Per un determinato account iniziale con id uguale a 1, ogni arco Transfers in uscita porta a più nodi di destinazione indipendenti. Considera l'esempio seguente:

  • M è il numero di account (a1) che hanno trasferimenti nell'intervallo (-inf, 90 DAYS)

  • N è il numero di account (a2) che hanno trasferimenti nell'intervallo [90 DAYS, 30 DAYS)

  • K è il numero di account (a3) che hanno trasferimenti nell'intervallo [30 DAYS, +inf)

La query non ottimizzata genera risultati intermedi in anticipo, calcolando il secondo percorso secondario M volte e il terzo percorso secondario M * N volte. Al contrario, una versione fattorizzata ottimizza l'esecuzione delle query nel seguente modo:

  • Ottenere M nodi di destinazione (a1) per il primo percorso secondario e memorizzare temporaneamente un insieme di nodi a1.

  • Ottenere i nodi di destinazione N (a2) per il secondo percorso secondario una sola volta e memorizzare temporaneamente un insieme di nodi a2.

  • Ottenere i nodi di destinazione K (a3) per l'ultimo percorso secondario una sola volta.

  • Eseguire un prodotto incrociato tra i risultati intermedi temporanei per creare il prodotto di M x N x K record.

Passaggi successivi