Este documento descreve como criar consultas eficientes usando as práticas recomendadas para projetar esquemas de grafos do Spanner. Você pode iterar o design do esquema. Por isso, recomendamos que você primeiro identifique os padrões de consulta essenciais para orientar o design do esquema.
Para informações gerais sobre as práticas recomendadas de design de esquema do Spanner, consulte Práticas recomendadas de design de esquema.
Otimizar a travessia de borda
A transversal de arestas é o processo de navegação por um gráfico seguindo as arestas, começando em um nó específico e se movendo ao longo de arestas conectadas para alcançar outros nós. A direção da borda é definida pelo esquema. A travessia de arestas é uma operação fundamental no Spanner Graph. Portanto, melhorar a eficiência da travessia de arestas é fundamental para o desempenho do aplicativo.
É possível atravessar uma aresta em duas direções:
Transposição de borda para frente: segue as bordas de saída do nó de origem.
Transversal de borda reversa: segue as bordas de entrada do nó de destino.
Dada uma pessoa, a consulta de exemplo a seguir realiza a travessia de arestas para frente de
arestas Owns
:
GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;
Dada uma conta, a consulta de exemplo a seguir realiza a travessia reversa de arestas de
Owns
:
GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;
Otimizar a travessia de borda para frente usando a intercalação
Para melhorar a performance da travessia de borda para frente, intercale a tabela de entrada de borda na tabela de entrada do nó de origem para colocar bordas com nós de origem. A intercalação é uma técnica de otimização de armazenamento no Spanner que coloca fisicamente as linhas de tabelas filhas com as linhas mãe correspondentes no armazenamento. Para mais informações sobre a intercalação, consulte Visão geral dos esquemas.
O exemplo a seguir demonstra essas práticas recomendadas:
CREATE TABLE Person (
id INT64 NOT NULL,
name STRING(MAX),
) PRIMARY KEY (id);
CREATE TABLE PersonOwnAccount (
id INT64 NOT NULL,
account_id INT64 NOT NULL,
create_time TIMESTAMP,
) PRIMARY KEY (id, account_id),
INTERLEAVE IN PARENT Person ON DELETE CASCADE;
Otimizar a travessia de borda reversa usando chave externa
Para percorrer as arestas reversas com eficiência, crie uma restrição de chave estrangeira entre a aresta e o nó de destino. Essa chave externa cria automaticamente um índice secundário na borda com chave pelas chaves do nó de destino. O índice secundário é usado automaticamente durante a execução da consulta.
O exemplo a seguir demonstra essas práticas recomendadas:
CREATE TABLE Person (
id INT64 NOT NULL,
name STRING(MAX),
) PRIMARY KEY (id);
CREATE TABLE Account (
id INT64 NOT NULL,
create_time TIMESTAMP,
) PRIMARY KEY (id);
CREATE TABLE PersonOwnAccount (
id INT64 NOT NULL,
account_id INT64 NOT NULL,
create_time TIMESTAMP,
CONSTRAINT FK_Account FOREIGN KEY (account_id) REFERENCES Account (id),
) PRIMARY KEY (id, account_id),
INTERLEAVE IN PARENT Person ON DELETE CASCADE;
Otimização da travessia de borda reversa usando o índice secundário
Se você não quiser criar uma chave externa na borda, por exemplo, devido à integridade rígida de dados que ela impõe, crie um índice secundário diretamente na tabela de entrada da borda, conforme mostrado no exemplo a seguir:
CREATE TABLE PersonOwnAccount (
id INT64 NOT NULL,
account_id INT64 NOT NULL,
create_time TIMESTAMP,
) PRIMARY KEY (id, account_id),
INTERLEAVE IN PARENT Person ON DELETE CASCADE;
CREATE INDEX Reverse_PersonOwnAccount
ON PersonOwnAccount (account_id);
Desativar bordas soltas
Uma borda solta conecta menos de dois nós. Uma aresta pendente pode ocorrer quando um nó é excluído sem remover as arestas associadas ou quando uma aresta é criada sem ser vinculada corretamente aos nós.
A proibição de bordas soltas oferece os seguintes benefícios:
- Aplica a integridade da estrutura do gráfico.
- Melhora a performance da consulta evitando o trabalho extra para filtrar arestas em que os endpoints não existem.
Não permitir bordas soltas usando restrições referenciais
Para não permitir bordas soltas, especifique restrições nos dois terminais:
- Intercalar a tabela de entrada de borda na tabela de entrada do nó de origem. Essa abordagem garante que o nó de origem de uma aresta sempre exista.
- Crie uma restrição chave externa nas bordas para garantir que o nó de destino de uma borda sempre exista.
O exemplo a seguir usa intercalação e uma chave externa para aplicar integridade referencial:
CREATE TABLE PersonOwnAccount (
id INT64 NOT NULL,
account_id INT64 NOT NULL,
create_time TIMESTAMP,
CONSTRAINT FK_Account FOREIGN KEY (account_id) REFERENCES Account (id) ON DELETE CASCADE,
) PRIMARY KEY (id, account_id),
INTERLEAVE IN PARENT Person ON DELETE CASCADE;
Use ON DELETE CASCADE para remover arestas automaticamente ao excluir um nó
Quando você usa o intercalamento ou uma chave externa para impedir arestas soltas, use a
cláusula ON DELETE
para controlar o comportamento quando quiser excluir um nó com
arestas ainda anexadas. Para mais informações, consulte
Excluir em cascata para tabelas intercaladas
e
Excluir em cascata com chaves externas.
É possível usar ON DELETE
das seguintes maneiras:
ON DELETE NO ACTION
(ou omitindo a cláusulaON DELETE
): a exclusão de um nó com arestas falhará.ON DELETE CASCADE
: a exclusão de um nó remove automaticamente as arestas associadas na mesma transação.
Excluir cascata para arestas que conectam diferentes tipos de nós
Exclua as arestas quando o nó de origem for excluído. Por exemplo,
INTERLEAVE IN PARENT Person ON DELETE CASCADE
exclui todas as arestasPersonOwnAccount
de saída do nóPerson
que está sendo excluído. Para mais informações, consulte Criar tabelas intercaladas.Excluir arestas quando o nó de destino for excluído. Por exemplo,
CONSTRAINT FK_Account FOREIGN KEY(account_id) REFERENCES Account(id) ON DELETE CASCADE
exclui todas as arestasPersonOwnAccount
recebidas no nóAccount
que está sendo excluído. Para mais informações, consulte Chaves externas.
Excluir cascata para arestas que conectam o mesmo tipo de nós
Quando os nós de origem e de destino de uma aresta têm o mesmo tipo e a
aresta é intercalada no nó de origem, é possível definir ON DELETE CASCADE
apenas para o nó de origem ou de destino, mas não para os dois.
Para remover automaticamente as bordas soltas em ambos os casos, crie uma chave externa na referência do nó de origem da borda em vez de intercalar a tabela de entrada de bordas na tabela de entrada do nó de origem.
Recomendamos o intercalamento para
otimizar a travessia de borda para frente.
Verifique o impacto nas suas cargas de trabalho antes de continuar. Confira o
exemplo abaixo, que usa AccountTransferAccount
como a tabela de entrada de borda:
--Define two Foreign Keys, each on one end Node of Transfer Edge, both with ON DELETE CASCADE action:
CREATE TABLE AccountTransferAccount (
id INT64 NOT NULL,
to_id INT64 NOT NULL,
amount FLOAT64,
create_time TIMESTAMP NOT NULL,
order_number STRING(MAX),
CONSTRAINT FK_FromAccount FOREIGN KEY (id) REFERENCES Account (id) ON DELETE CASCADE,
CONSTRAINT FK_ToAccount FOREIGN KEY (to_id) REFERENCES Account (id) ON DELETE CASCADE,
) PRIMARY KEY (id, to_id);
Filtrar por propriedades de nó ou aresta com índices secundários
Os índices secundários são essenciais para um processamento de consulta eficiente. Eles oferecem suporte a pesquisas rápidas de nós e arestas com base em valores de propriedade específicos, sem precisar percorrer toda a estrutura do gráfico. Isso é importante quando você trabalha com gráficos grandes, porque a travessia de todos os nós e arestas pode ser muito ineficiente.
Acelerar a filtragem de nós por propriedade
Para acelerar a filtragem por propriedades do nó, crie índices secundários nas
propriedades. Por exemplo, a consulta a seguir encontra contas para um determinado
apelido. Sem um índice secundário, todos os nós Account
são verificados para corresponder
aos critérios de filtragem.
GRAPH FinGraph
MATCH (acct:Account)
WHERE acct.nick_name = "abcd"
RETURN acct.id;
Para acelerar a consulta, crie um índice secundário na propriedade filtrada, conforme mostrado no exemplo abaixo:
CREATE TABLE Account (
id INT64 NOT NULL,
create_time TIMESTAMP,
is_blocked BOOL,
nick_name STRING(MAX),
) PRIMARY KEY (id);
CREATE INDEX AccountByNickName
ON Account (nick_name);
Dica:use índices filtrados por NULL para propriedades esparsas. Para mais informações, consulte Desativar a indexação de valores NULL.
Acelerar a travessia de borda para frente com filtragem em propriedades de borda
Ao percorrer uma aresta enquanto filtra as propriedades dela, é possível acelerar a consulta criando um índice secundário nas propriedades da aresta e intercalando o índice no nó de origem.
Por exemplo, a consulta a seguir encontra contas de uma determinada pessoa após um determinado período:
GRAPH FinGraph
MATCH (person:Person)-[owns:Owns]->(acct:Account)
WHERE person.id = 1
AND owns.create_time >= PARSE_TIMESTAMP("%c", "Thu Dec 25 07:30:00 2008")
RETURN acct.id;
Por padrão, essa consulta lê todas as arestas da pessoa especificada e filtra
as arestas que atendem à condição em create_time
.
O exemplo a seguir mostra como melhorar a eficiência da consulta criando
um índice secundário na referência do nó de origem de borda (id
) e na propriedade de borda
(create_time
). Intercale o índice na tabela de entrada do nó de origem para
colocar o índice com o nó de origem.
CREATE TABLE PersonOwnAccount (
id INT64 NOT NULL,
account_id INT64 NOT NULL,
create_time TIMESTAMP,
) PRIMARY KEY (id, account_id),
INTERLEAVE IN PARENT Person ON DELETE CASCADE;
CREATE INDEX PersonOwnAccountByCreateTime
ON PersonOwnAccount (id, create_time)
INTERLEAVE IN Person;
Com essa abordagem, a consulta pode encontrar com eficiência todas as arestas que atendem à
condição em create_time
.
Acelerar a travessia de borda reversa com filtragem em propriedades de borda
Ao percorrer uma borda reversa enquanto filtra as propriedades dela, é possível acelerar a consulta criando um índice secundário usando o nó de destino e as propriedades de borda para filtragem.
O exemplo de consulta a seguir realiza a travessia reversa de arestas com filtragem nas propriedades de aresta:
GRAPH FinGraph
MATCH (acct:Account)<-[owns:Owns]-(person:Person)
WHERE acct.id = 1
AND owns.create_time >= PARSE_TIMESTAMP("%c", "Thu Dec 25 07:30:00 2008")
RETURN person.id;
Para acelerar essa consulta usando um índice secundário, use uma das seguintes opções:
Crie um índice secundário na referência do nó de destino da borda (
account_id
) e na propriedade de borda (create_time
), conforme mostrado no exemplo abaixo:CREATE TABLE PersonOwnAccount ( id INT64 NOT NULL, account_id INT64 NOT NULL, create_time TIMESTAMP, ) PRIMARY KEY (id, account_id), INTERLEAVE IN PARENT Person ON DELETE CASCADE; CREATE INDEX PersonOwnAccountByCreateTime ON PersonOwnAccount (account_id, create_time);
Essa abordagem oferece um desempenho melhor porque as arestas reversas são ordenadas por
account_id
ecreate_time
, o que permite que o mecanismo de consulta encontre com eficiência as arestas paraaccount_id
que atendem à condição emcreate_time
. No entanto, se diferentes padrões de consulta forem filtrados em propriedades diferentes, cada propriedade poderá exigir um índice separado, o que pode aumentar a sobrecarga.Crie um índice secundário na referência do nó de destino da borda (
account_id
) e armazene a propriedade de borda (create_time
) em uma coluna de armazenamento, conforme mostrado no exemplo abaixo:CREATE TABLE PersonOwnAccount ( id INT64 NOT NULL, account_id INT64 NOT NULL, create_time TIMESTAMP, ) PRIMARY KEY (id, account_id), INTERLEAVE IN PARENT Person ON DELETE CASCADE; CREATE INDEX PersonOwnAccountByCreateTime ON PersonOwnAccount (account_id) STORING (create_time);
Essa abordagem pode armazenar várias propriedades. No entanto, a consulta precisa ler todas as arestas do nó de destino e filtrar as propriedades de aresta.
É possível combinar essas abordagens seguindo estas diretrizes:
- Use propriedades de borda em colunas de índice se elas forem usadas em consultas críticas para o desempenho.
- Para propriedades usadas em consultas menos sensíveis ao desempenho, adicione-as às colunas de armazenamento.
Modelar tipos de nó e aresta com rótulos e propriedades
Os tipos de nó e aresta são comumente modelados com rótulos. No entanto, também é possível usar
propriedades para tipos de modelo. Considere um exemplo em que há muitos tipos
diferentes de contas, como BankAccount
, InvestmentAccount
e
RetirementAccount
. Você pode armazenar as contas em tabelas de entrada separadas e
modelá-las como rótulos separados ou armazenar as contas em uma única tabela
de entrada e usar uma propriedade para diferenciar os tipos.
Comece o processo de modelagem com os tipos com rótulos. Considere usar propriedades nos seguintes cenários.
Melhorar o gerenciamento de esquemas
Se o gráfico tiver muitos tipos diferentes de nó e aresta, pode ser difícil gerenciar uma tabela de entrada separada para cada um. Para facilitar o gerenciamento de esquemas, modele o tipo como uma propriedade.
Tipos de modelo em uma propriedade para gerenciar tipos que mudam com frequência
Quando você modela tipos como rótulos, a adição ou remoção de tipos exige mudanças no esquema. Se você executar muitas atualizações de esquema em um curto período, o Spanner poderá limitar o processamento de atualizações de esquema em fila. Para mais informações, consulte Limitar a frequência das atualizações de esquema.
Se você precisar mudar o esquema com frequência, recomendamos modelar o tipo em uma propriedade para contornar as limitações na frequência das atualizações do esquema.
Acelerar consultas
A modelagem de tipos com propriedades pode acelerar as consultas quando o padrão de nó ou aresta
faz referência a vários rótulos. O exemplo de consulta a seguir encontra todas as instâncias de
SavingsAccount
e InvestmentAccount
pertencentes a um Person
, supondo que os tipos de conta
sejam modelados com rótulos:
GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:SavingsAccount|InvestmentAccount)
RETURN acct.id;
O padrão de nó acct
faz referência a dois rótulos. Se essa for uma consulta crítica para o desempenho, considere modelar Account
usando uma propriedade. Essa
abordagem pode melhorar a performance da consulta, conforme mostrado no exemplo de consulta
a seguir. Recomendamos que você faça a comparação das duas consultas.
GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:Account)
WHERE acct.type IN ("Savings", "Investment")
RETURN acct.id;
Armazenar o tipo na chave do elemento do nó para acelerar as consultas
Para acelerar as consultas com filtragem no tipo de nó quando um tipo de nó é modelado com uma propriedade e o tipo não muda ao longo da vida útil do nó, siga estas etapas:
- Inclua a propriedade como parte da chave do elemento do nó.
- Adicione o tipo de nó na tabela de entrada de borda.
- Inclua o tipo de nó nas chaves de referência de borda.
O exemplo a seguir aplica essa otimização ao nó Account
e à
aresta AccountTransferAccount
.
CREATE TABLE Account (
type STRING(MAX) NOT NULL,
id INT64 NOT NULL,
create_time TIMESTAMP,
) PRIMARY KEY (type, id);
CREATE TABLE AccountTransferAccount (
type STRING(MAX) NOT NULL,
id INT64 NOT NULL,
to_type STRING(MAX) NOT NULL,
to_id INT64 NOT NULL,
amount FLOAT64,
create_time TIMESTAMP NOT NULL,
order_number STRING(MAX),
) PRIMARY KEY (type, id, to_type, to_id),
INTERLEAVE IN PARENT Account ON DELETE CASCADE;
CREATE PROPERTY GRAPH FinGraph
NODE TABLES (
Account
)
EDGE TABLES (
AccountTransferAccount
SOURCE KEY (type, id) REFERENCES Account
DESTINATION KEY (to_type, to_id) REFERENCES Account
);
Configurar o TTL em nós e arestas
O time to live (TTL) do Spanner é um mecanismo que oferece suporte à expiração e remoção automática de dados após um período especificado. Isso é usado com frequência para dados com vida útil ou relevância limitada, como informações de sessão, caches temporários ou registros de eventos. Nesses casos, o TTL ajuda a manter o tamanho e o desempenho do banco de dados.
O exemplo a seguir usa o TTL para excluir contas 90 dias após o fechamento:
CREATE TABLE Account (
id INT64 NOT NULL,
create_time TIMESTAMP,
close_time TIMESTAMP,
) PRIMARY KEY (id),
ROW DELETION POLICY (OLDER_THAN(close_time, INTERVAL 90 DAY));
Se a tabela de nós tiver um TTL e uma tabela de borda intercalada, a
intercalagem precisa ser definida com
ON DELETE CASCADE
.
Da mesma forma, se a tabela de nós tiver um TTL e for referenciada por uma tabela de aresta
por meio de uma chave externa, a chave externa precisa ser definida com
ON DELETE CASCADE
.
No exemplo abaixo, AccountTransferAccount
é armazenado por até 10 anos
enquanto a conta permanece ativa. Quando uma conta é excluída, o histórico de transferência
também é excluído.
CREATE TABLE AccountTransferAccount (
id INT64 NOT NULL,
to_id INT64 NOT NULL,
amount FLOAT64,
create_time TIMESTAMP NOT NULL,
order_number STRING(MAX),
) PRIMARY KEY (id, to_id),
INTERLEAVE IN PARENT Account ON DELETE CASCADE,
ROW DELETION POLICY (OLDER_THAN(create_time, INTERVAL 3650 DAY));
Mesclar tabelas de entrada de bordas e nós
Você pode usar a mesma tabela de entrada para definir mais de um nó e aresta no seu esquema.
Nas tabelas de exemplo a seguir, os nós Account
têm uma chave composta
(owner_id, account_id)
. Há uma definição de aresta implícita, o nó Person
com a chave (id
) é proprietário do nó Account
com a chave composta
(owner_id, account_id)
quando id
é igual a owner_id
.
CREATE TABLE Person (
id INT64 NOT NULL,
) PRIMARY KEY (id);
-- Assume each account has exactly one owner.
CREATE TABLE Account (
owner_id INT64 NOT NULL,
account_id INT64 NOT NULL,
) PRIMARY KEY (owner_id, account_id);
Nesse caso, você pode usar a tabela de entrada Account
para definir o nó Account
e a borda PersonOwnAccount
, conforme mostrado no exemplo de esquema abaixo.
Para garantir que todos os nomes de tabela de elementos sejam exclusivos, o exemplo atribui o alias Owns
à definição da tabela de bordas.
CREATE PROPERTY GRAPH FinGraph
NODE TABLES (
Person,
Account
)
EDGE TABLES (
Account AS Owns
SOURCE KEY (owner_id) REFERENCES Person
DESTINATION KEY (owner_id, account_id) REFERENCES Account
);
A seguir
- Crie, atualize ou exclua um esquema de gráfico do Spanner.
- Inserir, atualizar ou excluir dados do Spanner Graph.