Práticas recomendadas para projetar um esquema do gráfico do Spanner

Neste documento, descrevemos como criar consultas eficientes usando as práticas recomendadas para projetar esquemas do 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 a melhor concepção de esquemas do Spanner práticas recomendadas de criação de esquema, consulte Práticas recomendadas de criação de esquema.

Otimizar a travessia de borda

A travessia de borda é o processo de navegar por um grafo seguindo o em cada ponto, começando em um nó específico e movendo-se pelas extremidades 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 é fundamental para o desempenho do seu aplicativo.

É possível atravessar uma aresta em duas direções: atravessando do nó de origem até nó de destino é chamada de travessia de arestas de encaminhamento, do nó de destino para a origem é chamada de travessia de arestas reversa.

Otimizar a travessia de borda para frente usando 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 da tabela filha com suas linhas pai correspondentes na 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 é usada 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 índice secundário

Se não quiser criar uma chave externa na borda, por exemplo, devido à a estrita integridade de dados que impõe, é possível criar diretamente um índice secundário 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. Um pêndulo uma borda pode ocorrer quando um nó é excluído sem remover as bordas associadas ou quando uma borda é criada sem vinculá-la corretamente aos nós.

Não permitir essa opção oferece os seguintes benefícios:

  • Aplica a integridade da estrutura do gráfico.
  • Melhora o desempenho da consulta, evitando o trabalho extra para filtrar as bordas quando não há endpoints.

Não permitir bordas pendentes usando restrições referenciais

Para não permitir bordas pendentes, especifique restrições em ambos endpoints:

  • Intercalar a tabela de entrada de borda na tabela de entrada do nó de origem. Isso garante que o nó de origem de uma borda 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 a intercalação e uma chave externa para impor 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ó

Ao usar a intercalação ou uma chave externa para proibir arestas pendentes, use o método cláusula ON DELETE para controlar o comportamento quando você quiser excluir um nó com que ainda estejam conectadas. Para mais informações, consulte excluir em cascata para tabelas intercaladas e excluir em cascata com chaves estrangeiras.

É possível usar ON DELETE das seguintes maneiras:

  • ON DELETE NO ACTION (ou omitindo a cláusula ON DELETE): excluindo um um nó com bordas vai falhar.
  • ON DELETE CASCADE: a exclusão de um nó remove automaticamente o as arestas associadas na mesma transação.

Excluir cascata para bordas 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 mensagens PersonOwnAccount borda do nó Person que está sendo excluída. Para mais informações, consulte Crie tabelas intercaladas.

  • Excluir bordas 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 PersonOwnAccount 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 borda são do mesmo tipo, e a é intercalada no nó de origem, é possível definir ON DELETE CASCADE somente para o nó de origem ou de destino (mas não para os dois nós).

Para remover automaticamente bordas pendentes em ambos os casos, crie uma chave externa em referência do nó de origem na borda em vez de intercalar a tabela de entrada da borda a tabela de entrada do nó de origem.

Recomendamos a intercalação para otimização da travessia de borda para frente. Verifique o impacto nas cargas de trabalho antes de continuar. Consulte a exemplo a seguir, que usa AccountTransferAccount como a entrada de borda tabela:

--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 o processamento eficiente de consultas. Eles dão suporte 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 Account nós são verificados para corresponder os 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 como mostrado no exemplo a seguir:

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.

Acelere a travessia de borda avançada com a filtragem nas 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 certo tempo:

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 bordas da pessoa especificada e filtra as arestas que satisfazem a 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 da borda (id) e a propriedade de borda (create_time). Intercale o índice na tabela de entrada do nó de origem para coloque 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;

Usando essa abordagem, a consulta pode encontrar com eficiência todas as arestas que atendam em create_time.

Acelere a travessia de borda reversa com filtragem nas propriedades de borda

Ao atravessar uma borda invertida 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 usando um índice secundário, use um dos seguintes opções:

  • Criar um índice secundário na referência do nó de destino da borda (account_id) e a propriedade de borda (create_time), como mostrado 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 PersonOwnAccountByCreateTime
    ON PersonOwnAccount (account_id, create_time);
    

    Essa abordagem oferece melhor desempenho porque as bordas invertidas são classificados por account_id e create_time, o que permite que o mecanismo de consulta encontrar com eficiência arestas para account_id que atendam à condição create_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 de borda 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 de modelos para tipos de modelos. Considere um exemplo em que há muitas tipos de conta, como BankAccount, InvestmentAccount e RetirementAccount É possível armazenar as contas em tabelas de entrada separadas e modelá-las como rótulos separados ou armazenar as contas em uma única entrada tabela e usar uma propriedade para diferenciar os tipos.

Inicie o processo de modelagem modelando os tipos com rótulos. Considere usando propriedades nos cenários a seguir.

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 limitar o processamento de atualizações de esquema na fila. Para mais informações, consulte Limite a frequência das atualizações do 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.

Acelere as consultas

Tipos de modelagem com propriedades podem acelerar consultas quando o nó ou padrão de borda 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. Isso pode melhorar o desempenho da consulta, como mostrado na consulta a seguir exemplo. Recomendamos comparar as duas consultas.

GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:Account)
WHERE acct.type IN ("Savings", "Investment")
RETURN acct.id;

Armazene 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:

  1. Inclua a propriedade como parte da chave do elemento do nó.
  2. Adicione o tipo de nó à tabela de entrada da borda.
  3. 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

Spanner Time to live (TTL) é um mecanismo com suporte expiração e remoção 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 intercaladas, o intercalado precisa 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 borda, por meio de uma chave externa, ela deve ser definida com ON DELETE CASCADE:

No exemplo a seguir, AccountTransferAccount é armazenado por até dez anos enquanto uma 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 borda e nó

É 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 borda implícita, o nó Person. com a chave (id) tem o nó Account com a chave composta (owner_id, account_id) quando id for 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 tabelas de elementos sejam únicos, o exemplo dá à borda definição da tabela ao alias Owns.

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