Ce document décrit les bonnes pratiques pour optimiser les performances des requêtes Spanner Graph, qui incluent les optimisations suivantes :
- Évitez une analyse complète de la table d'entrée pour les nœuds et les arêtes.
- Réduisez la quantité de données que la requête doit lire à partir du stockage.
- Réduisez la taille des données intermédiaires.
Commencer par les nœuds de faible cardinalité
Écrivez la traversée de chemin de sorte qu'elle commence par les nœuds de cardinalité inférieure. Cette approche permet de limiter la taille de l'ensemble de résultats intermédiaires et d'accélérer l'exécution des requêtes.
Par exemple, les requêtes suivantes ont la même sémantique :
Parcours de bord avant :
GRAPH FinGraph MATCH (p:Person {name:"Alex"})-[:Owns]->(a:Account {is_blocked: true}) RETURN p.id AS person_id, a.id AS account_id;Traversée des arêtes en sens inverse :
GRAPH FinGraph MATCH (a:Account {is_blocked:true})<-[:Owns]-(p:Person {name: "Alex"}) RETURN p.id AS person_id, a.id AS account_id;
En supposant qu'il y ait moins de personnes portant le nom Alex que de comptes bloqués, nous vous recommandons d'écrire cette requête dans la traversée de bord avant.
Il est particulièrement important de commencer par les nœuds de cardinalité inférieure pour le parcours de chemin de longueur variable. L'exemple suivant montre la méthode recommandée pour trouver les comptes qui se trouvent à trois transferts d'un compte donné.
GRAPH FinGraph
MATCH (:Account {id: 7})-[:Transfers]->{1,3}(a:Account)
RETURN a.id;
Spécifier tous les libellés par défaut
Spanner Graph déduit les nœuds et les libellés d'arêtes éligibles si les libellés sont omis. Nous vous recommandons de spécifier des libellés pour tous les nœuds et toutes les arêtes dans la mesure du possible, car cette inférence n'est pas toujours possible et peut entraîner l'analyse d'un nombre de libellés supérieur à celui nécessaire.
Instruction MATCH unique
L'exemple suivant recherche les comptes associés par au maximum trois transferts à partir du compte donné :
GRAPH FinGraph
MATCH (src:Account {id: 7})-[:Transfers]->{1,3}(dst:Account)
RETURN dst.id;
Dans les instructions MATCH
Spécifiez des libellés sur les nœuds et les arêtes lorsqu'ils font référence au même élément, mais qu'ils se trouvent dans des instructions MATCH.
L'exemple suivant illustre cette approche recommandée :
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;
Utiliser IS_FIRST pour optimiser les requêtes
Vous pouvez utiliser la fonction IS_FIRST pour améliorer les performances des requêtes en échantillonnant les arêtes et en limitant les traversées dans les graphiques. Cette fonction permet de gérer les nœuds à cardinalité élevée et d'optimiser les requêtes multihops.
Si la taille de l'échantillon que vous avez spécifiée est trop petite, il est possible que la requête ne renvoie aucune donnée. Pour cette raison, vous devrez peut-être essayer différentes tailles d'échantillon afin de trouver l'équilibre optimal entre les données renvoyées et l'amélioration des performances des requêtes.
Ces exemples IS_FIRST utilisent FinGraph, un graphique financier avec Account nœuds et Transfers arêtes pour les transferts d'argent. Pour créer le FinGraph et l'utiliser pour exécuter les exemples de requêtes, consultez Configurer et interroger Spanner Graph.
Limiter les arêtes traversées pour améliorer les performances des requêtes
Lorsque vous interrogez des graphiques, certains nœuds peuvent avoir un nombre d'arêtes entrantes ou sortantes beaucoup plus élevé que d'autres nœuds. Ces nœuds à cardinalité élevée sont parfois appelés super-nœuds ou nœuds hubs. Les super-nœuds peuvent entraîner des problèmes de performances, car les traversées peuvent impliquer le traitement d'énormes quantités de données, ce qui entraîne un déséquilibre des données et de longs temps d'exécution.
Pour optimiser une requête d'un graphique avec des super-nœuds, utilisez la fonction IS_FIRST dans une clause FILTER afin de limiter le nombre d'arêtes que la requête traverse à partir d'un nœud. Étant donné que les comptes FinGraph peuvent comporter un nombre de transactions beaucoup plus élevé que d'autres, vous pouvez utiliser IS_FIRST pour éviter une requête inefficace. Cette technique est particulièrement utile lorsque vous n'avez pas besoin d'une énumération complète de toutes les connexions à partir d'un super nœud.
La requête suivante recherche les comptes (a2) qui reçoivent des transferts directement ou indirectement à partir de comptes bloqués (a1). Elle utilise IS_FIRST pour éviter les ralentissements lorsqu'un compte comporte de nombreux transferts en limitant le nombre d'arêtes Transfers à prendre en compte pour chaque 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;
Cet exemple utilise les éléments suivants :
@max_transfers_per_account: paramètre de requête qui spécifie le nombre maximal d'arêtesTransfersà prendre en compte pour chaque compte (a1).PARTITION BY SOURCE_NODE_ID(selected_e): garantit que la limiteIS_FIRSTs'applique indépendamment à chaque compte (a1).ORDER BY selected_e.create_time DESC: spécifie que les transferts les plus récents sont renvoyés.
Échantillonner les nœuds intermédiaires pour optimiser les requêtes multihops
Vous pouvez également améliorer l'efficacité des requêtes en utilisant IS_FIRST pour échantillonner les nœuds intermédiaires dans les requêtes multihops. Cette technique améliore l'efficacité en limitant le nombre de chemins que la requête prend en compte pour chaque nœud intermédiaire. Pour ce faire, divisez une requête multihop en plusieurs instructions MATCH séparées par NEXT, et appliquez IS_FIRST au point médian où vous devez échantillonner :
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;
Pour comprendre comment IS_FIRST optimise cette requête :
La clause
FILTER IS_FIRST(1) OVER (PARTITION BY a2)est appliquée dans la première instructionMATCH.Pour chaque nœud de compte intermédiaire (
a2),IS_FIRSTne tient compte que de la première arêteTransfersentrante (e1), ce qui réduit le nombre de chemins à explorer dans la deuxième instructionMATCH.L'efficacité globale de la requête à deux sauts est améliorée, car le deuxième
MATCHne traite pas les données inutiles, en particulier lorsquea2comporte de nombreux transferts entrants.
Utiliser l'exécution factorisée pour optimiser les requêtes
Optimisez les performances des requêtes Spanner Graph en factorisant une requête qui traverse un modèle de graphique et crée des résultats intermédiaires en double.
Une requête de graphique peut s'exécuter en mode factorisé, ce qui déduplique les préfixes de chemin à cardinalité élevée avant de parcourir le reste du chemin. Cette optimisation améliore les performances des requêtes en réduisant les comparaisons de clés répétées ou les requêtes de table de hachage lors de l'exécution des requêtes de graphiques. Pour factoriser une requête, ajoutez l'indication @{factorized_mode} à la traversée de modèle individuelle ou au niveau de la requête.
Définissez factorized_mode sur l'une des valeurs suivantes :
factorize_left: optimise les traversées de graphes où de nombreux chemins convergent vers quelques nœuds. Spanner Graph déduplique les chemins par ID de nœud de destination, puis récupère les propriétés des nœuds pour réduire le travail redondant.factorize_both: optimise les requêtes non linéaires avec plusieurs sous-chemins qui partagent des nœuds. Spanner Graph évite de générer des résultats intermédiaires pour chaque chemin d'accès de manière indépendante. Au lieu de cela, il calcule chaque sous-chemin une seule fois, puis les combine, en retardant le produit croisé des résultats jusqu'à la fin. Utilisez-le pour les motifs tels que les triangles ou les branches.
Pour en savoir plus sur les indications de parcours, consultez Indications de graphiques.
Les exemples suivants montrent comment utiliser des indications de mode factorisé pour améliorer les performances des requêtes de graphiques. Pour exécuter l'exemple de code, créez le schéma FinGraph dans Configurer et interroger Spanner Graph.
Réduire les résultats intermédiaires dans une requête linéaire
Les comptes frauduleux reçoivent souvent un grand nombre de transactions. Les requêtes conçues pour identifier ces comptes peuvent générer des résultats intermédiaires volumineux, dont beaucoup sont des doublons, car ils convergent vers un petit ensemble de comptes de destination. La récupération des propriétés de nœuds frauduleux à partir de ces résultats intermédiaires redondants nécessite des calculs inutiles, ce qui peut ralentir considérablement les performances des requêtes.
L'indication @{factorized_mode} résout ce problème en optimisant l'exécution des requêtes.
L'exemple suivant montre comment utiliser cet indice pour récupérer les détails du compte et de la transaction. Il retrace les transferts provenant d'un compte bloqué connu vers des comptes potentiellement frauduleux situés à un à trois sauts de distance. La version non optimisée de cette requête est disponible dans le deuxième onglet.
Requête factorisée
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;
Requête non optimisée
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;
Lorsque le nombre de nœuds de destination distincts a2 est beaucoup plus petit que le nombre de chemins d'accès menant à a2, cette technique évite les tâches redondantes. C'est courant dans les réseaux de fraude, où les intermédiaires transfèrent de l'argent à quelques destinataires. L'indice factorisé effectue les actions suivantes :
Trouve tous les chemins qui incluent un à trois nœuds à partir d'un nœud bloqué, ainsi que la somme des transferts le long de ces chemins.
Détermine le nombre de nœuds de destination distincts
a2.Récupère le
create_timepour chaque nœud de destination distincta2une seule fois.Combine le
create_timede chaque nœuda2avec la liste des chemins menant àa2pour former le résultat de la requête.
Différer la génération de résultats intermédiaires dans les requêtes non linéaires complexes
Les requêtes graphiques ont souvent une structure complexe, composée de plusieurs modèles de sous-chemins linéaires. L'exemple suivant montre une requête qui analyse les transactions provenant d'un compte dont la valeur id est égale à 1. Cette requête classe tous les comptes de destination en fonction de leur create_time dans trois buckets distincts :
- Au cours des 30 derniers jours
- Entre 30 et 90 jours
- Plus de 90 jours
La requête renvoie tous les triplets et le montant total des transactions.
L'indication @{factorized_mode=factorize_both} optimise l'exécution des requêtes en évitant la matérialisation précoce des résultats intermédiaires. Il utilise le fait que tous les sous-chemins de la clause MATCH sont conditionnellement indépendants, étant donné la valeur du nœud de départ a. factorize_both évalue les deux premiers sous-chemins de manière indépendante et ne répète pas l'évaluation du dernier sous-chemin.
Requête factorisée
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;
Requête non optimisée
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;
Pour un compte de départ donné avec id égal à 1, chaque arête Transfers sortante mène à plusieurs nœuds de destination indépendants. Prenons l'exemple suivant :
Mcorrespond au nombre de comptes (a1) dont les transferts se situent dans la plage(-inf, 90 DAYS).Ncorrespond au nombre de comptes (a2) dont les transferts se situent dans la plage[90 DAYS, 30 DAYS).Kcorrespond au nombre de comptes (a3) dont les transferts se situent dans la plage[30 DAYS, +inf).
La requête non optimisée génère des résultats intermédiaires de manière précoce, en calculant le deuxième sous-chemin M fois et le troisième sous-chemin M * N fois. En revanche, une version factorisée optimise l'exécution des requêtes en procédant comme suit :
Obtenir les nœuds de destination
M(a1) pour le premier sous-chemin et stocker temporairement un ensemble de nœudsa1.Obtenir les nœuds de destination
N(a2) pour le deuxième sous-chemin une seule fois et stocker temporairement un ensemble de nœudsa2.Obtenir les nœuds de destination
K(a3) pour le dernier sous-chemin une seule fois.Effectuer un produit croisé entre les résultats intermédiaires temporaires pour créer le produit des enregistrements
MxNxK.
Étapes suivantes
- Découvrez comment interroger des graphes de propriétés dans Spanner Graph.
- Migrez vers Spanner Graph.