Este documento descreve como criar consultas eficientes usando as práticas recomendadas para projetar esquemas de gráfico do Spanner. O design do esquema pode ser iterativo, por isso recomendamos que você primeiro identifique padrões críticos de consulta 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 pelas arestas conectadas para alcançar outros nós. A travessia de arestas é uma operação fundamental no gráfico do Spanner, melhorar a eficiência da travessia de borda é essencial para o desempenho do seu aplicativo.
É possível atravessar uma aresta em duas direções: a travessia do nó de origem para o nó de destino é chamada de travessia de aresta para frente, enquanto a travessia do nó de destino para o nó de origem é chamada de travessia de aresta reversa.
Otimizar a travessia de borda para frente usando a intercalação
Para melhorar o desempenho de 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 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 transferir arestas invertidas com eficiência, crie uma restrição de chave estrangeira entre entre a borda e o nó de destino. Essa chave externa cria automaticamente índice secundário na borda codificadas 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;
Otimizar a travessia de borda reversa usando o índice secundário
Se você não quiser criar uma chave estrangeira 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);
Não permitir bordas pendentes
Uma borda pendurada é uma borda que 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.
Não permitir essa opção 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.
- Criar uma restrição de chave externa nas bordas para garantir que o nó de destino de uma borda sempre existe.
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 automaticamente bordas ao excluir um nó
Quando você usa a intercalação ou uma chave estrangeira 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
): excluindo um um nó com bordas vai 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
Excluir bordas 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 Crie tabelas intercaladas.Exclua as 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 asPersonOwnAccount
bordas de entrada no nóAccount
que está sendo excluído. Para mais informações, consulte Chaves estrangeiras.
Excluir cascata para bordas 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 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 nó ou propriedades de borda 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ê está trabalhando com gráficos grandes, porque a passagem de todos os nós e bordas pode ser muito ineficiente.
Acelerar a filtragem de nós por propriedade
Para acelerar a filtragem por propriedades do nó, crie índices secundários em
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 AccountByEmail
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
Quando você atravessa uma borda enquanto filtra suas propriedades, pode acelerar a consulta criando um índice secundário nas propriedades da borda e intercalando o índice para o nó de origem.
Por exemplo, a consulta a seguir encontra contas que pertencem a uma determinada pessoa depois de 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
.
Acelere a travessia de borda reversa com filtragem nas 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.
A consulta de exemplo a seguir realiza a travessia de borda reversa com filtragem ativada propriedades de borda:
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 com um índice secundário, use uma das opções a seguir 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 melhor desempenho porque as bordas invertidas são classificados por
account_id
ecreate_time
, o que permite que o mecanismo de consulta encontrar com eficiência arestas paraaccount_id
que atendam à condiçãocreate_time
. No entanto, se padrões de consulta diferentes filtrarem por diferentes propriedades, cada uma delas pode exigir um índice separado, que pode adicionar sobrecarga.Criar um índice secundário na referência do nó de destino da borda (
account_id
) e armazene a propriedade de borda (create_time
) em um coluna de armazenamento, conforme mostrado neste exemplo: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, ela precisa ler todas as bordas do nó de destino e filtrar as propriedades de borda.
É possível combinar essas abordagens seguindo estas diretrizes:
- Use as propriedades de borda em colunas de índice se elas forem usadas em consultas críticas para o desempenho.
- Adicione as propriedades usadas em consultas menos sensíveis à performance no das colunas de armazenamento.
Modelar tipos de nó e aresta com rótulos e propriedades
Os tipos de nó e borda geralmente são 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.
Inicie o processo de modelagem modelando os tipos com rótulos. Considere usar propriedades nos seguintes cenários.
Melhorar o gerenciamento de esquemas
Gerenciar uma entrada separada se o gráfico tiver muitos tipos de nó e borda para cada um deles pode ser difícil. 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 alterações em o esquema. Se você executar muitas atualizações de esquema em um curto período de tempo, o Spanner pode throttle o processamento de atualizações de esquema na fila. Para mais informações, consulte Limitar a frequência das atualizações de esquema.
Se precisar alterar o esquema com frequência, recomendamos que você modele a digite uma propriedade para contornar limitações na frequência do esquema atualizações.
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 do
SavingsAccount
e InvestmentAccount
pertencem a um Person
, supondo que a conta
são 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 este for um
consulta que tem alto 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 mudar durante o ciclo de vida do nó, siga estas etapas:
- Inclua a propriedade como parte da chave do elemento do nó.
- Adicione o tipo de nó à tabela de entrada da 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 ao
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 bordas
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 geralmente é usado para dados que têm uma limitação vida útil ou relevância, como informações da sessão, caches temporários ou ou de sistemas operacionais de contêineres. 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 a interdição:
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, o
intercalamento precisará ser definido 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, a transferência
o histórico também será 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
É possível usar a mesma tabela de entrada para definir mais de um nó e borda no 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, use a tabela de entrada Account
para definir a Account
.
e a borda PersonOwnAccount
, conforme mostrado no exemplo de esquema a seguir.
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 gráfico do Spanner.