Este documento descreve as práticas recomendadas para ajustar a performance de consultas do Spanner Graph, incluindo as seguintes otimizações:
- Evite uma verificação completa da tabela de entrada para nós e arestas.
- Reduza a quantidade de dados que a consulta precisa ler do armazenamento.
- Reduza o tamanho dos dados intermediários.
Começar com nós de baixa cardinalidade
Escreva a travessia de caminho para que ela comece com os nós de baixa cardinalidade. Essa abordagem mantém o conjunto de resultados intermediários pequeno e acelera a execução da consulta.
Por exemplo, as consultas a seguir têm a mesma semântica:
Transversalidade de borda para frente:
GRAPH FinGraph MATCH (p:Person {name:"Alex"})-[:Owns]->(a:Account {is_blocked: true}) RETURN p.id AS person_id, a.id AS account_id;Travessia de arestas reversas:
GRAPH FinGraph MATCH (a:Account {is_blocked:true})<-[:Owns]-(p:Person {name: "Alex"}) RETURN p.id AS person_id, a.id AS account_id;
Supondo que haja menos pessoas com o nome Alex do que contas bloqueadas, recomendamos que você escreva essa consulta na travessia de borda direta.
Começar com nós de baixa cardinalidade é especialmente importante para a travessia de caminhos de comprimento variável. O exemplo a seguir mostra a maneira recomendada de encontrar contas que estão a três transferências de uma determinada conta.
GRAPH FinGraph
MATCH (:Account {id: 7})-[:Transfers]->{1,3}(a:Account)
RETURN a.id;
Especificar todos os rótulos por padrão
O Spanner Graph deduz os nós qualificados e os rótulos de arestas se eles forem omitidos. Recomendamos que você especifique rótulos para todos os nós e arestas sempre que possível, porque essa inferência nem sempre é possível e pode fazer com que mais rótulos do que o necessário sejam verificados.
Uma única instrução MATCH
O exemplo a seguir encontra contas vinculadas por no máximo três transferências da conta especificada:
GRAPH FinGraph
MATCH (src:Account {id: 7})-[:Transfers]->{1,3}(dst:Account)
RETURN dst.id;
Em instruções MATCH
Especifique rótulos em nós e arestas quando eles se referirem ao mesmo elemento, mas estiverem em instruções MATCH diferentes.
O exemplo a seguir mostra essa abordagem recomendada:
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;
Usar o IS_FIRST para otimizar consultas
Você pode usar a função
IS_FIRST
para melhorar o desempenho da consulta ao fazer amostragem de arestas e limitar travessias
em gráficos. Essa função ajuda a processar nós de alta cardinalidade e otimizar consultas de vários saltos.
Se o tamanho da amostra especificado for muito pequeno, a consulta poderá não retornar dados. Por isso, talvez seja necessário testar diferentes tamanhos de amostra para encontrar o equilíbrio ideal entre dados retornados e melhoria na performance da consulta.
Estes exemplos de IS_FIRST usam FinGraph, um gráfico financeiro com nós Account e arestas Transfers para transferências de dinheiro. Para criar o FinGraph e usá-lo para executar as consultas de exemplo, consulte Configurar e consultar o Spanner Graph.
Limitar as arestas percorridas para melhorar o desempenho da consulta
Ao consultar gráficos, alguns nós podem ter um número significativamente maior de arestas de entrada ou saída em comparação com outros nós. Esses nós de alta cardinalidade às vezes são chamados de supernós ou nós de hub. Os supernós podem causar problemas de desempenho porque as travessias por eles podem envolver o processamento de grandes quantidades de dados, o que leva a distorções de dados e longos tempos de execução.
Para otimizar uma consulta de um gráfico com supernós, use a função IS_FIRST em uma cláusula FILTER para limitar o número de arestas que a consulta atravessa de um nó. Como as contas no FinGraph podem ter um número significativamente maior de transações do que outras, use o IS_FIRST para evitar uma consulta ineficiente. Essa técnica é especialmente útil quando você não precisa de uma enumeração completa de todas as conexões de um supernó.
A consulta a seguir encontra contas (a2) que recebem transferências direta ou indiretamente de contas bloqueadas (a1). Ela usa IS_FIRST para evitar lentidão quando uma conta tem muitas transferências, limitando o número de arestas Transfers a serem consideradas para cada 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;
Este exemplo usa o seguinte:
@max_transfers_per_account: um parâmetro de consulta que especifica o número máximo de arestasTransfersa serem consideradas para cada conta (a1).PARTITION BY SOURCE_NODE_ID(selected_e): garante que o limite deIS_FIRSTseja aplicado de forma independente a cada conta (a1).ORDER BY selected_e.create_time DESC: especifica que as transferências mais recentes serão retornadas.
Amostrar nós intermediários para otimizar consultas de vários saltos
Também é possível melhorar a eficiência da consulta usando o IS_FIRST para amostrar nós intermediários em consultas de várias etapas. Essa técnica melhora a eficiência ao limitar o número de caminhos que a consulta considera para cada nó intermediário. Para fazer isso, divida uma consulta de vários saltos em várias instruções MATCH separadas por NEXT e aplique IS_FIRST no ponto médio em que você precisa fazer a amostragem:
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;
Para entender como o IS_FIRST otimiza essa consulta:
A cláusula
FILTER IS_FIRST(1) OVER (PARTITION BY a2)é aplicada na primeira instruçãoMATCH.Para cada nó de conta intermediário (
a2),IS_FIRSTconsidera apenas a primeira aresta de entradaTransfers(e1), reduzindo o número de caminhos a serem explorados na segunda instruçãoMATCH.A eficiência geral da consulta de dois hops é melhorada porque o segundo
MATCHnão processa dados desnecessários, especialmente quandoa2tem muitas transferências de entrada.
Usar a execução fatorizada para otimizar consultas
Otimize a performance da consulta do Spanner Graph fatorando uma consulta que percorre um padrão de gráfico e cria resultados intermediários duplicados.
Uma consulta de grafo pode ser executada no modo fatorado, que remove a duplicação de prefixos de caminho de alta cardinalidade antes de percorrer o restante do caminho. Essa otimização melhora o desempenho da consulta reduzindo comparações de chaves repetidas ou sondagens de tabela hash durante a execução da consulta de gráfico. Para fatorar uma consulta, adicione a
dica @{factorized_mode}
ao percurso do padrão individual ou no nível da consulta.
Defina factorized_mode como uma das seguintes opções:
factorize_left: otimiza travessias de gráficos em que muitos caminhos convergem em alguns nós. O Spanner Graph remove caminhos duplicados por ID do nó de destino e busca propriedades do nó para reduzir o trabalho redundante.factorize_both: otimiza consultas não lineares com vários subcaminhos que compartilham nós. O Spanner Graph evita gerar resultados intermediários para cada caminho de forma independente. Em vez disso, ele calcula cada subcaminho uma vez e os combina, adiando o produto cartesiano dos resultados até o final. Use isso para padrões como triângulos ou ramificações.
Para mais informações sobre dicas de travessia, consulte Dicas de gráfico.
Os exemplos a seguir mostram como usar dicas de modo fatorado para melhorar o desempenho das consultas de gráficos. Para executar o exemplo de código, crie o esquema FinGraph em
Configurar e consultar o Spanner Graph.
Reduzir resultados intermediários em uma consulta linear
As contas fraudulentas costumam receber um grande volume de transações. As consultas criadas para identificar essas contas podem gerar grandes resultados intermediários, muitos deles duplicados porque convergem em um pequeno conjunto de contas de destino. Recuperar propriedades de nós fraudulentos desses resultados intermediários redundantes exige computação desnecessária, o que pode diminuir significativamente o desempenho da consulta.
A dica @{factorized_mode} resolve esse problema otimizando a execução da consulta.
O exemplo a seguir demonstra como usar essa dica para recuperar detalhes da conta e da transação. Ele rastreia transferências originadas de uma conta bloqueada conhecida para contas potencialmente fraudulentas que estão a um a três saltos de distância. A versão não otimizada dessa consulta está disponível na segunda guia.
Consulta fatorizada
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;
Consulta não otimizada
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 o número de nós de destino distintos a2 é muito menor do que o número de caminhos que levam a a2, essa técnica evita trabalho redundante. Isso é comum em redes de fraude, em que intermediários transferem dinheiro para alguns destinatários. A dica fatorada executa as seguintes ações:
Encontra todos os caminhos que incluem de uma a três arestas começando de um nó bloqueado e a soma das transferências ao longo desses caminhos.
Determina o número de nós de destino distintos
a2.Recupera o
create_timepara cada nó de destinoa2distinto apenas uma vez.Combina o
create_timede cada nóa2com a lista de caminhos que levam aa2para formar o resultado da consulta.
Adiar a geração de resultados intermediários em consultas não lineares complexas
As consultas de gráficos geralmente têm uma estrutura complexa, consistindo em vários padrões de subcaminho lineares. O exemplo a seguir mostra uma consulta que analisa transações
originadas de uma conta com id igual a 1. Essa consulta categoriza todas as contas de destino por create_time em três buckets distintos:
- Nos últimos 30 dias
- De 30 a 90 dias atrás
- Com mais de 90 dias
A consulta retorna todas as triplas e o valor total das transações.
A dica @{factorized_mode=factorize_both} otimiza a execução da consulta evitando a materialização antecipada de resultados intermediários. Ele usa o fato de que todos os subcaminhos na cláusula MATCH são condicionalmente independentes, considerando o valor do nó inicial a. factorize_both avalia os dois primeiros subcaminhos de forma independente e não repete a avaliação do último subcaminho.
Consulta fatorizada
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;
Consulta não otimizada
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;
Para uma determinada conta inicial com id igual a 1, cada aresta Transfers de saída leva a vários nós de destino independentes. Confira este
exemplo:
Mé o número de contas (a1) que têm transferências no intervalo(-inf, 90 DAYS)Né o número de contas (a2) que têm transferências no intervalo[90 DAYS, 30 DAYS)Ké o número de contas (a3) que têm transferências no intervalo[30 DAYS, +inf)
A consulta não otimizada gera resultados intermediários antecipadamente, calculando o segundo subcaminho M vezes e o terceiro subcaminho M * N vezes. Por outro lado, uma versão fatorada otimiza a execução da consulta fazendo o seguinte:
Obtenção de nós de destino
M(a1) para o primeiro subcaminho e armazenamento temporário de um conjunto de nósa1.Obter
Nnós de destino (a2) para o segundo subcaminho apenas uma vez e armazenar temporariamente um conjunto de nósa2.Obter nós de destino
K(a3) para o último subcaminho apenas uma vez.Realizar vários produtos entre resultados intermediários temporários para criar o produto de registros
MxNxK.