Este documento descreve as práticas recomendadas para criar um esquema do Spanner Graph, com foco em consultas eficientes, travessia de arestas otimizada e técnicas de gestão de dados eficazes.
Para informações sobre a criação de esquemas do Spanner (não esquemas do Spanner Graph), consulte as Práticas recomendadas de criação de esquemas.
Escolha um design de esquema
O design do esquema afeta o desempenho do gráfico. Os seguintes tópicos ajudam a escolher uma estratégia eficaz.
Designs esquematizados versus designs 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 alterações de definição pouco frequentes. O esquema aplica a definição do gráfico e as propriedades suportam todos os tipos de dados do Spanner.
Um design sem esquema infere a definição do gráfico a partir dos dados, oferecendo mais flexibilidade sem exigir alterações ao esquema. As etiquetas e as propriedades dinâmicas não são aplicadas por predefinição. As propriedades têm de ser valores JSON válidos.
O seguinte resume as principais diferenças entre a gestão de dados com esquema e sem esquema. Além disso, considere as suas consultas de gráficos para ajudar a decidir que tipo de esquema usar.
Funcionalidade | Gestão de dados esquematizados | Gestão de dados sem esquema |
---|---|---|
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 a partir dos dados. No entanto, o Spanner Graph não inspeciona os dados para inferir a definição. |
A atualizar a definição do gráfico | Requer uma alteração do esquema do Spanner Graph. Adequado quando a definição está bem definida e muda com pouca frequência. | Não é necessária nenhuma alteração ao esquema do Spanner Graph. |
Impor definição de gráfico | Um esquema de gráfico de propriedades aplica os tipos de nós permitidos para uma aresta. Também aplica as propriedades e os tipos de propriedades permitidos de um nó ou um tipo de aresta do gráfico. | Não aplicada por predefinição. Pode usar as restrições de verificação para aplicar a integridade dos dados de etiquetas e propriedades. |
Tipos de dados de propriedades | Suportar qualquer tipo de dados do Spanner, por exemplo,
timestamp . |
As propriedades dinâmicas têm de ser um valor JSON válido. |
Escolha um design de esquema com base em consultas de grafos
Os designs esquematizados e sem esquema oferecem frequentemente um desempenho comparável. No entanto, quando as consultas usam padrões de caminhos quantificados que abrangem vários tipos de nós ou arestas, um design sem esquema oferece um melhor desempenho.
O modelo de dados subjacente é um motivo fundamental para isto. Um design sem esquema armazena todos os dados em tabelas de nós e arestas únicas, o que DYNAMIC LABEL
aplica.
As consultas que atravessam vários tipos são executadas com o mínimo de análises de tabelas.
Por outro lado, os esquemas estruturados usam normalmente tabelas separadas para cada tipo de nó e aresta, pelo que as consultas que abrangem vários tipos têm de analisar e combinar dados de todas as tabelas correspondentes.
Seguem-se exemplos de consultas que funcionam bem com designs sem esquema e um exemplo de consulta que funciona bem com ambos os designs:
Design sem esquemas
As seguintes consultas têm um melhor desempenho com um design sem esquema porque usam padrões de caminhos 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 intermédios 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 intermédios para caminhos mais longos. Isto permite que os caminhos atravessem nós intermédios 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;
O padrão de caminho quantificado desta consulta encontra caminhos de um a três saltos entre os nós
Person
eAccount
, sem especificar etiquetas de arestas. Semelhante à consulta anterior, permite que os caminhos atravessem nós intermédios de vários tipos.GRAPH FinGraph MATCH p = (:Person {id:1})-[]->{1,3}(:Account) RETURN TO_JSON(p) AS p;
Esta consulta encontra caminhos de um a três saltos entre nós
Account
usando arestas do tipoOwns
em qualquer direção (-[:Owns]-
). Uma vez que os caminhos podem atravessar arestas em qualquer direção e os nós intermédios 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 seguinte consulta tem um desempenho comparável com designs esquematizados e sem esquema. O respetivo caminho quantificado, (:Account)-[:Transfer]->{1,3}(:Account)
, envolve um tipo de nó, Account
, e um tipo de aresta, Transfer
. Uma vez que o caminho envolve apenas um tipo de nó e um tipo de aresta, o desempenho é comparável para ambos os designs. Embora os nós intermédios não sejam etiquetados explicitamente, o padrão restringe-os a nós Account
. O nó Person
aparece
fora deste caminho quantificado.
GRAPH FinGraph
MATCH p = (:Person {id:1})-[:Owns]->(:Account)-[:Transfer]->{1,3}(:Account)
RETURN TO_JSON(p) AS p;
Otimize o desempenho do esquema do Spanner Graph
Depois de optar por usar um esquema do Spanner Graph esquematizado ou sem esquema, pode otimizar o respetivo desempenho das seguintes formas:
Otimize a travessia de arestas
A travessia de arestas é o processo de navegação num gráfico seguindo as respetivas arestas, começando num nó específico e movendo-se ao longo das arestas ligadas para alcançar outros nós. O esquema define a direção da aresta. A travessia de arestas é uma operação fundamental no Spanner Graph, pelo que a melhoria da eficiência da travessia de arestas pode melhorar significativamente o desempenho da sua aplicação.
Pode percorrer um limite em duas direções:
- O deslocamento da extremidade para a frente segue as extremidades de saída do nó de origem.
- A travessia de arestas inversa segue as arestas de entrada do nó de destino.
Exemplos de consultas de travessia de arestas para a frente e para trás
A seguinte consulta de exemplo executa a travessia de arestas para a frente de arestas Owns
para uma determinada pessoa:
GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;
A seguinte consulta de exemplo executa a travessia de arestas inversa de arestas Owns
para uma determinada conta:
GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;
Otimize o percurso de arestas para a frente
Para melhorar o desempenho da travessia de arestas para a frente, otimize a travessia da origem para a aresta e da aresta para o destino.
Para otimizar o percurso da origem para o limite, intercale a tabela de entrada de limites na tabela de entrada de nós de origem usando a cláusula
INTERLEAVE IN PARENT
. A intercalação é uma técnica de otimização do armazenamento no Spanner que coloca as linhas da tabela secundária com as linhas principais correspondentes no armazenamento. Para mais informações sobre a intercalação, consulte a vista geral dos esquemas.Para otimizar a travessia da aresta para o destino, crie uma restrição de chave externa entre a aresta e o nó de destino
. Isto aplica a restrição de origem a destino, o que pode melhorar o desempenho eliminando as análises da tabela de destino. Se as chaves externas aplicadas causarem gargalos no desempenho de escrita (por exemplo, ao atualizar nós de hub), use uma chave externa informativa em alternativa.
Os exemplos seguintes mostram como usar a intercalação com uma restrição de chave externa aplicada e uma restrição de chave externa informativa.
Chave externa aplicada
Neste exemplo de tabela de relações, 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 relações, 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;
Otimize a travessia de arestas invertidas
Otimize a travessia de arestas invertidas, a menos que as suas consultas usem apenas a travessia para a frente, porque as consultas que envolvem a travessia invertida ou bidirecional são comuns.
Para otimizar a travessia de arestas invertidas, pode fazer o seguinte:
Crie um índice secundário na tabela de arestas.
Intercale o índice na tabela de entrada do nó de destino para colocar as arestas juntamente com os nós de destino.
Armazenar as propriedades das arestas no índice.
Este exemplo mostra um índice secundário para otimizar a travessia de arestas inversas para a 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 arestas no índice.
Para mais informações sobre a intercalação de índices, consulte o artigo Í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;
Use índices secundários para filtrar propriedades
Um índice secundário permite uma pesquisa eficiente de nós e arestas com base em valores de propriedades específicos. A utilização de um índice ajuda a evitar uma análise completa da tabela e é especialmente útil para grafos grandes.
Acelere a filtragem de nós por propriedade
A seguinte consulta que encontra contas para um determinado pseudónimo. Como não usa um índice secundário, todos os nós Account
têm de ser analisados 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 no 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);
Acelere a filtragem de arestas por propriedade
Pode usar um índice secundário para melhorar o desempenho da filtragem de arestas com base nos valores das propriedades.
Deslocamento da aresta para a frente
Sem um índice secundário, esta consulta tem de analisar todas as arestas de uma pessoa para encontrar as arestas 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 seguinte melhora a eficiência da consulta através da criação de 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;
Deslocamento inverso de arestas
Sem um índice secundário, a seguinte consulta de travessia de arestas inversa tem de ler todas as arestas antes de poder 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 seguinte melhora a eficiência da consulta através da criação de 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;
Evite extremidades pendentes
Um limite que liga zero ou um nó, um limite pendente, pode comprometer a eficiência da consulta do Spanner Graph e a integridade da estrutura do gráfico. Pode ocorrer uma aresta pendente se eliminar um nó sem eliminar as respetivas arestas associadas. Também pode ocorrer uma aresta pendente se criar uma aresta, mas o respetivo nó de origem ou de destino não existir. Para evitar arestas pendentes, incorpore o seguinte no esquema do Spanner Graph:
- Use restrições referenciais.
- Opcional: use a cláusula
ON DELETE CASCADE
quando eliminar um nó com arestas que ainda estão anexadas. Se não usarON DELETE CASCADE
, as tentativas de eliminar um nó sem eliminar as arestas correspondentes falham.
Use restrições referenciais
Pode usar a intercalação e as chaves externas aplicadas em ambos os pontos finais para evitar arestas pendentes seguindo estes passos:
Intercale a tabela de entrada de arestas na tabela de entrada do nó de origem para garantir que o nó de origem de uma aresta existe sempre.
Crie uma restrição de chave externa aplicada em arestas para garantir que o nó de destino de uma aresta existe sempre. Embora as chaves estrangeiras aplicadas impeçam arestas pendentes, tornam a inserção e a eliminação de arestas mais dispendiosas.
O exemplo seguinte usa uma chave externa aplicada e intercala a tabela de entrada de arestas na tabela de entrada do nó de origem através da cláusula INTERLEAVE IN PARENT
. Em conjunto, a utilização de uma chave externa aplicada e a intercalação também podem ajudar a otimizar a travessia de arestas para a frente.
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;
Elimine arestas com ON DELETE CASCADE
Quando usa a intercalação ou uma chave externa aplicada para evitar arestas pendentes, use a cláusula ON DELETE CASCADE
no esquema do gráfico do Spanner para eliminar as arestas associadas de um nó na mesma transação que elimina o nó.
Para mais informações, consulte os artigos
Elimine a cascata para tabelas intercaladas
e
Ações de chaves externas.
Eliminação em cascata para arestas que ligam diferentes tipos de nós
Os exemplos seguintes mostram como usar ON DELETE CASCADE
no esquema do gráfico do Spanner para eliminar arestas pendentes quando elimina um nó de origem ou de destino. Em ambos os casos, o tipo do nó eliminado e o tipo do nó ligado a este por uma aresta são diferentes.
Nó de origem
Use a intercalação para eliminar arestas pendentes quando o nó de origem é eliminado. O exemplo seguinte mostra como usar a intercalação para eliminar as arestas de saída quando o nó de origem (Person
) é eliminado. Para mais informações, consulte o artigo Crie 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 de chave externa para eliminar arestas pendentes quando o nó de destino é eliminado. O exemplo seguinte mostra como usar uma chave externa com ON
DELETE CASCADE
numa tabela de arestas para eliminar arestas recebidas quando o nó de destino (Account
) é eliminado:
CONSTRAINT FK_Account FOREIGN KEY(account_id)
REFERENCES Account(id) ON DELETE CASCADE
Eliminação em cascata para arestas que ligam o mesmo tipo de nós
Quando os nós de origem e de destino de uma aresta são do mesmo tipo e a aresta está intercalada no nó de origem, pode definir ON DELETE CASCADE
para o nó de origem ou de destino, mas não para ambos.
Para evitar arestas pendentes nestes cenários, não entrelace na tabela de entrada do nó de origem. Em alternativa, crie duas chaves externas aplicadas nas referências dos nós de origem e de destino.
O exemplo seguinte usa AccountTransferAccount
como a tabela de entrada de arestas. Define duas chaves externas, uma em cada nó final 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);
Configure o tempo de vida (TTL) em nós e limites
O TTL permite-lhe fazer expirar e remover dados após um período especificado. Pode usar o TTL no seu esquema para manter o tamanho e o desempenho da base de dados removendo dados com uma duração ou uma relevância limitadas. Por exemplo, pode configurá-lo para remover informações de sessões, caches temporárias ou registos de eventos.
O exemplo seguinte usa o TTL para eliminar contas 90 dias após o respetivo 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));
Quando define uma política de TTL numa tabela de nós, tem de configurar a forma como as arestas relacionadas são processadas para evitar arestas pendentes não intencionais:
Para tabelas de arestas intercaladas: se uma tabela de arestas estiver intercalada na tabela de nós, pode definir a relação de intercalação com
ON DELETE CASCADE
. Isto garante que, quando o TTL elimina um nó, as respetivas arestas intercaladas associadas também são eliminadas.Para tabelas de arestas com chaves externas: se uma tabela de arestas fizer referência à tabela de nós com uma chave externa, tem duas opções:
- Para eliminar automaticamente arestas quando o nó referenciado é eliminado pelo TTL, use
ON DELETE CASCADE
na chave externa. Isto mantém a integridade referencial. - Para permitir que as arestas permaneçam após a eliminação do nó referenciado (criando uma aresta pendente), defina a chave externa como uma chave externa informativa.
- Para eliminar automaticamente arestas quando o nó referenciado é eliminado pelo TTL, use
No exemplo seguinte, a tabela de arestas AccountTransferAccount
está sujeita a duas políticas de eliminação de dados:
- Uma política de TTL elimina registos de transferências com mais de dez anos.
- A cláusula
ON DELETE CASCADE
elimina todos os registos de transferências associados a uma origem quando essa conta é eliminada.
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));
Unir tabelas de entrada de nós e arestas
Para otimizar o seu esquema, defina um nó e as respetivas arestas de entrada ou saída numa única tabela. Esta abordagem oferece as seguintes vantagens:
Menos tabelas: reduz o número de tabelas no seu esquema, o que simplifica a gestão de dados.
Desempenho das consultas melhorado: elimina a travessia que usa junções a uma tabela de arestas separada.
Esta técnica funciona bem quando a chave principal 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
pode ser uma chave externa que faz referência à tabela Person
. Esta 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);
Pode usar a tabela Account
para definir o nó Account
e a respetiva aresta Owns
de entrada. Isto é apresentado na seguinte CREATE PROPERTY GRAPH
declaração. Na cláusula EDGE TABLES
, atribui o alias Owns
à tabela Account
. Isto deve-se ao facto de cada elemento no esquema do gráfico ter de 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
);
O que se segue?
- Criar, atualizar ou eliminar um esquema do Spanner Graph.
- Inserir, atualizar ou eliminar dados do Spanner Graph.