Este documento descreve as práticas recomendadas para projetar um esquema de gráfico do Spanner, com foco em consultas eficientes, travessia de arestas otimizada e técnicas eficazes de gerenciamento de dados.
Para informações sobre o design de esquemas do Spanner (não esquemas do Spanner Graph), consulte Práticas recomendadas de design de esquema.
Escolher um design de esquema
O design do esquema afeta a performance do gráfico. Os tópicos a seguir ajudam você a escolher uma estratégia eficaz.
Designs com e sem esquema
Um design esquematizado armazena a definição do gráfico no esquema do Spanner Graph, que é adequado para gráficos estáveis com mudanças de definição pouco frequentes. O esquema impõe a definição do gráfico, e as propriedades são compatíveis com todos os tipos de dados do Spanner.
Um design sem esquema infere a definição do gráfico dos dados, oferecendo mais flexibilidade sem exigir mudanças de esquema. Os rótulos e propriedades dinâmicos não são aplicados por padrão. As propriedades precisam ser valores JSON válidos.
A seguir, resumimos as principais diferenças entre o gerenciamento de dados com e sem esquema. Além disso, considere suas consultas de gráfico para ajudar a decidir qual tipo de esquema usar.
Recurso | Gerenciamento de dados esquematizados | Gerenciamento de dados sem esquema |
---|---|---|
Como armazenar a definição do gráfico | A definição do gráfico é armazenada no esquema do gráfico do Spanner. | A definição do gráfico é evidente nos dados. No entanto, o Spanner Graph não inspeciona os dados para inferir a definição. |
Atualizando a definição do gráfico | Requer uma mudança no esquema do gráfico do Spanner. Adequado quando a definição é bem definida e muda com pouca frequência. | Não é necessário mudar o esquema do Spanner Graph. |
Como aplicar a definição de gráfico | Um esquema de gráfico de propriedades impõe os tipos de nós permitidos para uma aresta. Ele também aplica as propriedades e os tipos de propriedades permitidos de um nó ou tipo de aresta do gráfico. | Não é aplicada por padrão. É possível usar restrições de verificação para garantir a integridade dos dados de rótulo e propriedade. |
Tipos de dados de propriedade | Compatível com qualquer tipo de dados do Spanner, por exemplo, timestamp . |
Propriedades dinâmicas precisam ser um valor JSON válido. |
Escolher um design de esquema com base em consultas de gráfico
Projetos esquematizados e sem esquema geralmente oferecem desempenho comparável. A exceção é quando as consultas usam padrões de caminho quantificados que abrangem vários tipos de nós ou arestas. Nesse caso, um design sem esquema oferece melhor desempenho. Isso acontece porque os projetos sem esquema armazenam todos os dados em tabelas de nós e arestas únicas, o que minimiza as verificações de tabelas. Por outro lado, os projetos esquematizados usam tabelas separadas para cada tipo de nó e aresta. Portanto, as consultas que abrangem vários tipos precisam verificar e combinar dados de todas as tabelas correspondentes.
Confira a seguir exemplos de consultas que funcionam bem com designs sem esquema e uma consulta que funciona bem com os dois designs:
Design sem esquema
As consultas a seguir têm melhor desempenho com um design sem esquema porque usam padrões de caminho quantificados que podem corresponder a vários tipos de nós e arestas:
O padrão de caminho quantificado desta consulta usa vários tipos de arestas (
Transfer
ouWithdraw
) e não especifica tipos de nós intermediários para caminhos com mais de um salto.GRAPH FinGraph MATCH p = (:Account {id:1})-[:Transfer|Withdraw]->{1,3}(:Account) RETURN TO_JSON(p) AS p;
O padrão de caminho quantificado desta consulta encontra caminhos de um a três saltos entre os nós
Person
eAccount
, usando vários tipos de arestas (Owns
ouTransfers
), sem especificar tipos de nós intermediários para caminhos mais longos. Isso permite que os caminhos atravessem nós intermediários de vários tipos. Por exemplo,(:Person)-[:Owns]->(:Account)-[:Transfers]->(:Account)
.GRAPH FinGraph MATCH p = (:Person {id:1})-[:Owns|Transfers]->{1,3}(:Account) RETURN TO_JSON(p) AS p;
Essa consulta encontra caminhos de um a três saltos entre nós
Account
usando arestas do tipoOwns
em qualquer direção (-[:Owns]-
). Como os caminhos podem atravessar arestas em qualquer direção e os nós intermediários não são especificados, um caminho de dois saltos pode passar por nós de diferentes tipos. Por exemplo,(:Account)-[:Owns]-(:Person)-[:Owns]-(:Account)
.GRAPH FinGraph MATCH p = (:Account {id:1})-[:Owns]-{1,3}(:Account) RETURN TO_JSON(p) AS p;
Ambos os designs
A consulta a seguir tem desempenho comparável com designs esquematizados e sem esquema. O caminho quantificado, (:Account)-[:Transfer]->{1,3}(:Account)
, envolve um tipo de nó, Account
, e um tipo de aresta, Transfer
. Como o caminho envolve apenas um tipo de nó e um tipo de aresta, o desempenho é comparável para os dois projetos. Embora os nós intermediários não sejam rotulados explicitamente, o padrão os restringe a serem nós Account
. O nó Person
aparece fora desse caminho quantificado.
GRAPH FinGraph
MATCH p = (:Person {id:1})-[:Owns]->(:Account)-[:Transfer]->{1,3}(:Account)
RETURN TO_JSON(p) AS p;
Otimizar a performance do esquema do gráfico do Spanner
Depois de escolher usar um esquema do Spanner Graph esquematizado ou sem esquema, é possível otimizar a performance dele das seguintes maneiras:
Otimizar a travessia de arestas
O percurso de arestas é o processo de navegação em um gráfico seguindo as arestas dele, começando em um nó específico e movendo-se ao longo das arestas conectadas para alcançar outros nós. O esquema define a direção da borda. A travessia de arestas é uma operação fundamental no Spanner Graph. Por isso, melhorar a eficiência dela pode aumentar significativamente o desempenho do aplicativo.
É possível percorrer uma aresta em duas direções:
- O percurso de borda de encaminhamento segue as bordas de saída do nó de origem.
- O percurso de borda invertido segue as bordas de entrada do nó de destino.
Exemplos de consultas de travessia de arestas para frente e para trás
A consulta de exemplo a seguir realiza a travessia de arestas de Owns
para uma determinada pessoa:
GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;
A consulta de exemplo a seguir realiza a travessia de borda inversa das bordas Owns
para uma determinada conta:
GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;
Otimizar a travessia da borda de encaminhamento
Para melhorar o desempenho do percurso da borda direta, otimize o percurso da origem até a borda e da borda até o destino.
Para otimizar a travessia da origem até a borda, intercale a tabela de entrada de borda na tabela de entrada do nó de origem usando a cláusula
INTERLEAVE IN PARENT
. A intercalação é uma técnica de otimização de armazenamento no Spanner que coloca as linhas da tabela filha com as linhas mãe correspondentes no armazenamento. Para mais informações sobre intercalação, consulte Visão geral dos esquemas.Para otimizar a travessia da borda até o destino, crie uma restrição de chave estrangeira entre a borda e o nó de destino
. Isso impõe a restrição de origem para destino, o que pode melhorar a performance eliminando verificações da tabela de destino. Se as chaves externas aplicadas causarem gargalos de desempenho de gravação (por exemplo, ao atualizar nós de hub), use uma chave externa informativa.
Os exemplos a seguir mostram como usar o intercalamento com uma restrição chave externa obrigatória e outra informativa.
Chave externa aplicada
Neste exemplo de tabela de borda, PersonOwnAccount
faz o seguinte:
Intercala na tabela de nós de origem
Person
.Cria uma chave externa aplicada à tabela de nós de destino
Account
.
CREATE TABLE Person (
id INT64 NOT NULL,
name STRING(MAX),
) PRIMARY KEY (id);
CREATE TABLE Account (
id INT64 NOT NULL,
create_time TIMESTAMP,
close_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;
Chave externa informativa
Neste exemplo de tabela de borda, PersonOwnAccount
faz o seguinte:
Intercala na tabela de nós de origem
Person
.Cria uma chave externa informativa para a tabela de nós de destino
Account
.
CREATE TABLE Person (
id INT64 NOT NULL,
name STRING(MAX),
) PRIMARY KEY (id);
CREATE TABLE Account (
id INT64 NOT NULL,
create_time TIMESTAMP,
close_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) NOT ENFORCED
) PRIMARY KEY (id, account_id),
INTERLEAVE IN PARENT Person ON DELETE CASCADE;
Otimizar a travessia de borda inversa
Otimize a travessia de arestas invertidas, a menos que suas consultas usem apenas a travessia direta, porque consultas que envolvem travessia invertida ou bidirecional são comuns.
Para otimizar a travessia de arestas inversas, faça o seguinte:
Crie um índice secundário na tabela de arestas.
Intercale o índice na tabela de entrada do nó de destino para alocar as arestas com os nós de destino.
Armazene as propriedades de aresta no índice.
Este exemplo mostra um índice secundário para otimizar a travessia de arestas inversas na tabela de arestas PersonOwnAccount
:
A cláusula
INTERLEAVE IN
coloca os dados de índice com a tabela de nós de destinoAccount
.A cláusula
STORING
armazena propriedades de aresta no índice.
Para mais informações sobre intercalação de índices, consulte Índices e intercalação.
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 AccountOwnedByPerson
ON PersonOwnAccount (account_id)
STORING (create_time),
INTERLEAVE IN Account;
Usar índices secundários para filtrar propriedades
Um índice secundário permite a pesquisa eficiente de nós e arestas com base em valores de propriedade específicos. O uso de um índice ajuda a evitar uma verificação completa da tabela e é especialmente útil para gráficos grandes.
Acelerar a filtragem de nós por propriedade
A consulta a seguir encontra contas para um apelido especificado. Como ele não usa um índice secundário, todos os nós Account
precisam ser verificados para encontrar os resultados correspondentes:
GRAPH FinGraph
MATCH (acct:Account)
WHERE acct.nick_name = "abcd"
RETURN acct.id;
Crie um índice secundário na propriedade filtrada do seu esquema para acelerar o processo de filtragem:
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);
Acelerar a filtragem de arestas por propriedade
É possível usar um índice secundário para melhorar o desempenho da filtragem de arestas com base em valores de propriedade.
Travessia de borda para frente
Sem um índice secundário, essa consulta precisa verificar todas as arestas de uma pessoa para encontrar as que correspondem ao filtro create_time
:
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;
O código a seguir melhora a eficiência da consulta criando um índice secundário na referência do nó de origem da aresta (id
) e na propriedade da aresta (create_time
). A consulta também define o índice como um filho intercalado da tabela de entrada do nó de origem, que coloca 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;
Travessia de borda inversa
Sem um índice secundário, a seguinte consulta de travessia de aresta reversa precisa ler
todas as arestas antes de encontrar a pessoa proprietária da conta especificada
após o create_time
especificado:
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;
O código a seguir melhora a eficiência da consulta criando um índice secundário na referência do nó de destino da aresta (account_id
) e na propriedade da aresta (create_time
). A consulta também define o índice como o filho intercalado da tabela de nós de destino, que coloca o índice com o nó de destino.
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 AccountOwnedByPersonByCreateTime
ON PersonOwnAccount (account_id, create_time),
INTERLEAVE IN Account;
Evitar bordas penduradas
Uma aresta que conecta zero ou um nó, uma aresta pendente, pode comprometer a eficiência da consulta do Spanner Graph e a integridade da estrutura do gráfico. Uma aresta pendente pode ocorrer se você excluir um nó sem excluir as arestas associadas. Uma aresta solta também pode ocorrer se você criar uma aresta, mas o nó de origem ou de destino não existir. Para evitar arestas soltas, incorpore o seguinte no esquema do gráfico do Spanner:
- Use restrições referenciais.
- Opcional: use a cláusula
ON DELETE CASCADE
ao excluir um nó com arestas ainda anexadas. Se você não usarON DELETE CASCADE
, as tentativas de excluir um nó sem excluir as arestas correspondentes vão falhar.
Usar restrições referenciais
É possível usar o intercalamento e chaves estrangeiras aplicadas nos dois endpoints para evitar arestas soltas seguindo estas etapas:
Intercale a tabela de entrada de aresta na tabela de entrada do nó de origem para garantir que o nó de origem de uma aresta sempre exista.
Crie uma restrição chave externa obrigatória nas arestas para garantir que o nó de destino de uma aresta sempre exista. Embora as chaves externas aplicadas evitem arestas soltas, elas tornam a inserção e a exclusão de arestas mais caras.
O exemplo a seguir usa uma chave externa aplicada e intercala a tabela de entrada de aresta na tabela de entrada do nó de origem usando a cláusula INTERLEAVE IN PARENT
. Juntos, o uso de uma chave externa aplicada e a intercalação também podem ajudar a otimizar o percurso de borda direta.
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;
Excluir arestas com ON DELETE CASCADE
Ao usar o intercalamento ou uma chave externa forçada para evitar arestas soltas, use a cláusula ON DELETE CASCADE
no esquema de grafo do Spanner para excluir as arestas associadas de um nó na mesma transação que exclui o nó.
Para mais informações, consulte
Exclusão em cascata para tabelas intercaladas
e
Ações de chave externa.
Exclusão em cascata para arestas que conectam diferentes tipos de nós
Os exemplos a seguir mostram como usar ON DELETE CASCADE
no esquema do
Spanner Graph para excluir arestas soltas ao excluir um
nó de origem ou destino. Em ambos os casos, o tipo do nó excluído e o tipo do nó conectado a ele por uma aresta são diferentes.
Nó de origem
Use o intercalamento para excluir arestas soltas quando o nó de origem for excluído. O
exemplo a seguir mostra como usar o intercalamento para excluir as arestas de saída quando o
nó de origem (Person
) é excluído. Para mais informações, consulte Criar tabelas intercaladas.
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
Nó de destino
Use uma restrição chave externa para excluir arestas soltas quando o nó de destino for excluído. O exemplo a seguir mostra como usar uma chave externa com ON
DELETE CASCADE
em uma tabela de arestas para excluir arestas de entrada quando o nó de destino (Account
) é excluído:
CONSTRAINT FK_Account FOREIGN KEY(account_id)
REFERENCES Account(id) ON DELETE CASCADE
Exclusão em cascata para arestas que conectam o mesmo tipo de nós
Quando os nós de origem e destino de uma aresta são do mesmo tipo e a aresta é intercalada no nó de origem, é possível definir ON DELETE CASCADE
para o nó de origem ou de destino, mas não para ambos.
Para evitar arestas soltas nesses cenários, não intercale na tabela de entrada do nó de origem. Em vez disso, crie duas chaves estrangeiras obrigatórias nas referências de nós de origem e destino.
O exemplo a seguir usa AccountTransferAccount
como a tabela de entrada de arestas. Ele
define duas chaves estrangeiras, uma em cada nó de extremidade da aresta de transferência, ambas com
a ação ON DELETE CASCADE
.
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);
Configurar o time to live (TTL) em nós e arestas
Com o TTL, é possível expirar e remover dados após um período especificado. É possível usar o TTL no seu esquema para manter o tamanho e a performance do banco de dados removendo dados com vida útil ou relevância limitadas. Por exemplo, é possível configurar para remover informações de sessão, caches temporários ou registros de eventos.
O exemplo a seguir usa o TTL para excluir contas 90 dias após o encerramento:
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));
Ao definir uma política de TTL em uma tabela de nós, é necessário configurar como as arestas relacionadas são processadas para evitar arestas soltas não intencionais:
Para tabelas de arestas intercaladas:se uma tabela de arestas for intercalada na tabela de nós, você poderá definir a relação de intercalação com
ON DELETE CASCADE
. Isso garante que, quando o TTL exclui um nó, as arestas intercaladas associadas também sejam excluídas.Para tabelas de arestas com chaves externas:se uma tabela de arestas faz referência à tabela de nós com uma chave externa, você tem duas opções:
- Para excluir automaticamente as arestas quando o nó referenciado for excluído por
TTL, use
ON DELETE CASCADE
na chave externa. Isso mantém a integridade referencial. - Para permitir que as arestas permaneçam após a exclusão do nó referenciado (criando uma aresta pendente), defina a chave externa como uma chave estrangeira informativa.
- Para excluir automaticamente as arestas quando o nó referenciado for excluído por
TTL, use
No exemplo a seguir, a tabela de arestas AccountTransferAccount
está sujeita a duas políticas de exclusão de dados:
- Uma política de TTL exclui registros de transferência com mais de dez anos.
- A cláusula
ON DELETE CASCADE
exclui todos os registros de transferência associados a uma origem quando essa conta é excluída.
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 nós e arestas
Para otimizar seu esquema, defina um nó e as arestas de entrada ou saída dele em uma única tabela. Essa abordagem oferece os seguintes benefícios:
Menos tabelas: reduz o número de tabelas no esquema, o que simplifica o gerenciamento de dados.
Melhoria na performance de consultas: elimina a travessia que usa junções em uma tabela de arestas separada.
Essa técnica funciona bem quando a chave primária de uma tabela também define uma relação com outra tabela. Por exemplo, se a tabela Account
tiver uma chave primária composta (owner_id, account_id)
, a parte owner_id
poderá ser uma chave externa que faz referência à tabela Person
. Essa estrutura permite que a tabela Account
represente o nó Account
e a aresta de entrada do nó Person
.
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);
É possível usar a tabela Account
para definir o nó Account
e a aresta Owns
de entrada. Isso é mostrado na seguinte instrução CREATE PROPERTY GRAPH
. Na cláusula EDGE TABLES
, você atribui o alias Owns
à tabela Account
. Isso acontece porque cada elemento no esquema do gráfico precisa ter um nome exclusivo.
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
- Criar, atualizar ou descartar um esquema de gráfico do Spanner.
- Inserir, atualizar ou excluir dados do gráfico do Spanner.