Best practice per la progettazione di uno schema di grafo Spanner

Questo documento descrive come creare query efficienti utilizzando le best practice per la progettazione degli schemi di Spanner Graph. La progettazione dello schema può in modo iterativo, per cui ti consigliamo di identificare prima i pattern di query per la progettazione dello schema.

Per informazioni generali sulla progettazione dello schema Spanner consulta le Best practice per la progettazione di schemi.

Ottimizza l'attraversamento dei bordi

La percorrenza degli spigoli è il processo di navigazione in un grafo seguendo i suoi spigoli, partendo da un determinato nodo e spostandosi lungo gli spigoli collegati per raggiungere altri nodi. L'attraversamento perimetrale è un'operazione fondamentale nel grafico di Spanner, il miglioramento dell'efficienza dell'attraversamento perimetrale è fondamentale per le prestazioni dell'applicazione.

Puoi attraversare un bordo in due direzioni: dal nodo di origine a il nodo di destinazione è chiamato forward edge traversal, mentre attraversa dal nodo di destinazione a quello di origine è detto inverti attraversamento.

Ottimizza l'esplorazione dell'edge in avanti utilizzando l'interlacciamento

Per migliorare le prestazioni dell'attraversamento in avanti, utilizza l'interfoliazione della tabella di input dell'edge nella tabella di input del nodo di origine per collocare gli bordi con i nodi di origine. L'interlacciamento è una tecnica di ottimizzazione dello spazio di archiviazione in Spanner che colloca fisicamente le righe delle tabelle figlio con le righe principali corrispondenti nello spazio di archiviazione. Per ulteriori informazioni sull'interleaving, vedi Panoramica degli schemi.

L'esempio seguente mostra queste best practice:

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;

Ottimizza l'attraversamento inverso utilizzando una chiave esterna

Per attraversare in modo efficiente i bordi inversi, crea un vincolo di chiave esterna tra tra il perimetro e il nodo di destinazione. Questa chiave esterna crea automaticamente indice secondario sul perimetro bloccato dalle chiavi del nodo di destinazione. L'indice secondario viene utilizzato automaticamente durante l'esecuzione della query.

L'esempio seguente mostra queste best practice:

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;

Ottimizzare il traversale degli archi in senso inverso utilizzando l'indice secondario

Se non vuoi creare una chiave esterna nell'edge, ad esempio a causa dell'integrità rigorosa dei dati che applica, puoi creare direttamente un indice secondario nella tabella di input dell'edge, come mostrato nell'esempio seguente:

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);

Non consentire bordi sporgenti

Un bordo pendente è un bordo che collega meno di due nodi. Una penzola un perimetro può verificarsi quando un nodo viene eliminato senza rimuovere i bordi associati quando viene creato un perimetro senza collegarlo correttamente ai suoi nodi.

La disattivazione degli spigoli sporgenti offre i seguenti vantaggi:

  • Forza l'integrità della struttura del grafico.
  • Migliora le prestazioni delle query evitando il lavoro aggiuntivo necessario per filtrare i bordi dove non esistono endpoint.

Non consentire i bordi pendenti usando i vincoli referenziali

Per non consentire bordi sporgenti, specifica vincoli su entrambi gli endpoint:

  • Intercala la tabella di input dell'elemento perimetrale nella tabella di input del nodo di origine. Questo metodo garantisce che il nodo di origine di un bordo esista sempre.
  • Crea una limitazione della chiave esterna sugli spigoli per assicurarti che il nodo di destinazione di uno spigolo esista sempre.

L'esempio seguente utilizza l'interleaving e una chiave esterna per applicare integrità referenziale:

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;

Usa ELIMINA CASCATA per rimuovere automaticamente i bordi durante l'eliminazione di un nodo

Se utilizzi gli interfoli o una chiave esterna per impedire i bordi pendenti, utilizza la ON DELETE per controllare il comportamento quando vuoi eliminare un nodo con bordi ancora attaccati. Per ulteriori informazioni, vedi eliminare a cascata per le tabelle con interleaving e elimina a cascata con chiavi esterne.

Puoi utilizzare ON DELETE nei seguenti modi:

  • ON DELETE NO ACTION (o omissione della clausola ON DELETE): l'eliminazione di un nodo con bordi non andrà a buon fine.
  • ON DELETE CASCADE: l'eliminazione di un nodo rimuove automaticamente i bordi associati nella stessa transazione.

Eliminazione in cascata per gli archi che collegano tipi diversi di nodi

  • Elimina gli archi quando viene eliminato il nodo di origine. Ad esempio,INTERLEAVE IN PARENT Person ON DELETE CASCADE elimina tutti i bordi PersonOwnAccount in uscita dal nodo Person da eliminare. Per ulteriori informazioni, consulta Creare tabelle con interfoliazione.

  • Elimina i bordi quando viene eliminato il nodo di destinazione. Ad esempio, CONSTRAINT FK_Account FOREIGN KEY(account_id) REFERENCES Account(id) ON DELETE CASCADE elimina tutti gli archi PersonOwnAccount in entrata nel nodo Account da eliminare. Per ulteriori informazioni, vedi Chiavi esterne.

Elimina a cascata per i bordi che collegano lo stesso tipo di nodi

Quando i nodi di origine e di destinazione di un perimetro hanno lo stesso tipo è con interleaving nel nodo di origine, puoi definire ON DELETE CASCADE solo per il nodo di origine o di destinazione (ma non per entrambi i nodi).

Per rimuovere automaticamente gli spigoli inutilizzati in entrambi i casi, crea una chiave esterna sul riferimento del nodo di origine dell'elemento anziché interlacciare la tabella di input dell'elemento con la tabella di input del nodo di origine.

Consigliamo di interfoliare per ottimizzare l'attraversamento del bordo in avanti. Prima di procedere, assicurati di verificare l'impatto sui tuoi carichi di lavoro. Guarda l'esempio seguente, che utilizza AccountTransferAccount come tabella di input di bordo:

--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);

Filtrare in base alle proprietà dei nodi o degli archi con indici secondari

Gli indici secondari sono essenziali per un'elaborazione efficiente delle query. Supportano la ricerca rapida di nodi ed archi in base a valori di proprietà specifici, senza dover attraversare l'intera struttura del grafo. Questo è importante quando stai lavorando con grafici di grandi dimensioni, perché attraversando tutti i nodi e gli archi può essere molto inefficiente.

Velocizzare il filtro dei nodi per proprietà

Per velocizzare il filtro in base alle proprietà dei nodi, crea indici secondari sulle proprietà. Ad esempio, la seguente query trova gli account per un determinato nickname. Senza un indice secondario, tutti i nodi (Account) vengono analizzati per trovare una corrispondenza i criteri di filtro.

GRAPH FinGraph
MATCH (acct:Account)
WHERE acct.nick_name = "abcd"
RETURN acct.id;

Per velocizzare la query, crea un indice secondario per la proprietà filtrata, come показано показано nell'esempio seguente:

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);

Suggerimento:usa indici con filtri NULL per le proprietà sparse. Per ulteriori informazioni, consulta Disattivare l'indicizzazione dei valori NULL.

Accelera l'attraversamento in avanti utilizzando i filtri sulle proprietà degli spigoli

Quando esegui la traversata di un bordo applicando un filtro alle relative proprietà, puoi velocizzare la query creando un indice secondario sulle proprietà del bordo e intercalando l'indice nel nodo di origine.

Ad esempio, la seguente query trova gli account di proprietà di una determinata persona dopo un determinato momento:

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;

Per impostazione predefinita, questa query legge tutti i bordi della persona specificata, quindi filtra i bordi che soddisfano la condizione in create_time.

L'esempio seguente mostra come migliorare l'efficienza delle query creando un indice secondario sul riferimento del nodo di origine perimetrale (id) e la proprietà perimetrale (create_time). Interfoliare l'indice sotto la tabella di input del nodo di origine in collocare l'indice con il nodo di origine.

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;

Con questo approccio, la query può trovare in modo efficiente tutti gli spigoli che soddisfano la condizione su create_time.

Accelerare il traversale degli edge in senso inverso con il filtro delle proprietà degli edge

Quando attraversi un bordo inverso mentre ne filtri le proprietà, puoi velocizzare la query creando un indice secondario tramite il nodo di destinazione le proprietà degli angoli per i filtri.

La query di esempio seguente esegue l'attraversamento inverso con il filtro attivo proprietà degli spigoli:

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;

Per velocizzare questa query utilizzando un indice secondario, utilizza una delle seguenti opzioni:

  • Crea un indice secondario nel riferimento del nodo di destinazione perimetrale (account_id) e la proprietà perimetrale (create_time), come mostrato in nell'esempio seguente:

    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);
    

    Questo approccio offre prestazioni migliori perché gli archi inversi sono ordinati per account_id e create_time, il che consente al motore di query di trovare in modo efficiente gli archi per account_id che soddisfano la condizione su create_time. Tuttavia, se pattern di query diversi filtrano in base a , ogni proprietà potrebbe richiedere un indice separato, che può essere aggiungere overhead.

  • Crea un indice secondario sul riferimento del nodo di destinazione dell'elemento perimetrale (account_id) e memorizza la proprietà dell'elemento perimetrale (create_time) in una colonna di archiviazione, come mostrato nell'esempio seguente:

    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);
    

    Questo approccio può memorizzare più proprietà, ma la query deve leggere tutti gli archi del nodo di destinazione e poi filtrare in base alle proprietà degli archi.

Puoi combinare questi approcci seguendo queste linee guida:

  • Utilizza le proprietà di confine nelle colonne dell'indice se vengono utilizzate nelle query critiche per le prestazioni.
  • Per le proprietà utilizzate in query meno sensibili alle prestazioni, aggiungile in le colonne di archiviazione.

Modella i tipi di nodi e archi con etichette e proprietà

I tipi di nodi ed archi vengono comunemente modellati con le etichette. Tuttavia, puoi anche utilizzare le proprietà per i tipi di modelli. Prendiamo ad esempio un caso in cui esistono molti tipi di account diversi, come BankAccount, InvestmentAccount e RetirementAccount. Puoi archiviare gli account in tabelle di input separate e modellizzarli come etichette separate oppure puoi archiviarli in un'unica tabella di input e utilizzare una proprietà per distinguere i tipi.

Inizia il processo di definizione del modello creando i tipi con le etichette. Prendi in considerazione utilizzando le proprietà nei seguenti scenari.

Migliorare la gestione dello schema

Se il grafo contiene molti tipi diversi di nodi ed archi, la gestione di una tabella di input distinta per ciascuno può diventare difficile. Per semplificare la gestione dello schema, il tipo come proprietà.

Tipi di modelli in una proprietà per gestire i tipi che cambiano frequentemente

Quando modelli i tipi come etichette, l'aggiunta o la rimozione dei tipi richiede modifiche a lo schema. Se esegui troppi aggiornamenti dello schema in un breve periodo di tempo, Spanner potrebbe throttle l'elaborazione degli aggiornamenti dello schema in coda. Per ulteriori informazioni, vedi Limita la frequenza degli aggiornamenti dello schema.

Se devi modificare spesso lo schema, ti consigliamo di modellare lo schema digita una proprietà per aggirare le limitazioni di frequenza dello schema aggiornamenti.

Velocizzare le query

I tipi di modellazione con proprietà potrebbero accelerare le query quando il nodo o il pattern perimetrale fa riferimento a più etichette. La seguente query di esempio trova tutte le istanze SavingsAccount e InvestmentAccount di proprietà di Person, presuppone che l'account i tipi sono modellati con etichette:

GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:SavingsAccount|InvestmentAccount)
RETURN acct.id;

Il pattern del nodo acct fa riferimento a due etichette. Se si tratta di un query critica per le prestazioni, valuta la possibilità di modellare Account utilizzando una proprietà. Questo potrebbe fornire prestazioni delle query migliori, come mostrato nell'esempio esempio. Ti consigliamo di eseguire il benchmark di entrambe le query.

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

Archivia il tipo nella chiave dell'elemento node per velocizzare le query

Per velocizzare le query con filtri in base al tipo di nodo quando un tipo di nodo è modellato con una proprietà e il tipo non cambia durante la vita del nodo, segui questi passaggi:

  1. Includi la proprietà nella chiave dell'elemento node.
  2. Aggiungi il tipo di nodo nella tabella di input perimetrale.
  3. Includi il tipo di nodo nelle chiavi di riferimento all'elemento.

L'esempio seguente applica questa ottimizzazione al nodo Account e al Bordo 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
  );

Configura il TTL su nodi e perimetrali

La durata (TTL) di Spanner è un meccanismo che supporta la scadenza e la rimozione automatica dei dati dopo un periodo specificato. Questo viene spesso utilizzato per i dati con una durata o pertinenza limitata, come informazioni sulle sessioni, cache temporanee o log degli eventi. In questi casi, il TTL contribuisce a mantenere le dimensioni e le prestazioni del database.

L'esempio seguente utilizza il TTL per eliminare gli account 90 giorni dopo la loro chiusura:

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 la tabella dei nodi ha un TTL e una tabella perimetrale con interleaving al suo interno, l'interlea deve essere definita con ON DELETE CASCADE Analogamente, se la tabella dei nodi ha un TTL e fa riferimento a una tabella perimetrale, attraverso una chiave esterna, quest'ultima deve essere definita con ON DELETE CASCADE.

Nell'esempio seguente, AccountTransferAccount viene archiviato per un massimo di dieci anni mentre un account rimane attivo. Quando un account viene eliminato, viene eliminata anche la cronologia dei trasferimenti.

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));

Unisci le tabelle di input dei nodi e degli edge

Puoi utilizzare la stessa tabella di input per definire più di un nodo e un'associazione nello schema.

Nelle seguenti tabelle di esempio, i nodi Account hanno una chiave composta(owner_id, account_id). Esiste una definizione implicita dell'elemento di bordo: il nodo Person con chiave (id) possiede il nodo Account con chiave composita (owner_id, account_id) quando id è uguale 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);

In questo caso, puoi utilizzare la tabella di input Account per definire il valore Account e sul perimetro PersonOwnAccount, come mostrato nell'esempio di schema che segue. Per garantire che tutti i nomi delle tabelle degli elementi siano univoci, l'esempio fornisce il bordo definizione della tabella per l'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
  );

Passaggi successivi