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 grafici Spanner. La progettazione dello schema può essere iterativa, pertanto ti consigliamo di identificare innanzitutto i pattern di query critici per orientare la progettazione dello schema.

Per informazioni generali sulle best practice per la progettazione dello schema di Spanner, consulta Best practice per la progettazione dello schema.

Ottimizza il traversale 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'esplorazione degli spigoli è un'operazione fondamentale in Spanner Graph, pertanto migliorare l'efficienza dell'esplorazione degli spigoli è fondamentale per il rendimento dell'applicazione.

Puoi attraversare un bordo in due direzioni: l'attraversamento dal nodo di origine al nodo di destinazione è chiamato attraversamento del bordo in avanti, mentre l'attraversamento dal nodo di destinazione al nodo di origine è chiamato attraversamento del bordo in retromarcia.

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

Per migliorare le prestazioni di attraversamento degli archi in avanti, intercala la tabella di input degli archi nella tabella di input del nodo di origine per collocare gli archi 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'interlacciamento, consulta la sezione 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;

Ottimizzare il traversale degli archi in senso inverso utilizzando la chiave esterna

Per attraversare in modo efficiente gli archi inversi, crea un vincolo di chiave esterna tra l'arco e il nodo di destinazione. Questa chiave esterna crea automaticamente un indice secondario sul lato basato sulle chiavi dei nodi 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 sporgente è un bordo che collega meno di due nodi. Un assomigliato bordo può verificarsi quando un nodo viene eliminato senza rimuovere i bordi associati o quando un bordo viene creato senza collegarlo correttamente ai relativi nodi.

La disattivazione degli spigoli sporgenti offre i seguenti vantaggi:

  • Impone 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 bordi sporgenti utilizzando vincoli di riferimento

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

  • Intercala la tabella di input degli edge 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'interlacciamento e una chiave esterna per applicare l'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;

Utilizza ON DELETE CASCADE per rimuovere automaticamente gli archi quando elimini un nodo

Quando utilizzi l'interlacciamento o una chiave esterna per non consentire i bordi inutilizzati, utilizza la clausola ON DELETE per controllare il comportamento quando vuoi eliminare un nodo con bordi ancora collegati. Per ulteriori informazioni, consulta la sezione relativa all'eliminazione a cascata per le tabelle con interfoliazione e all'eliminazione a cascata con chiavi estranee.

Puoi utilizzare ON DELETE nei seguenti modi:

  • ON DELETE NO ACTION (o omissione della clausola ON DELETE): l'eliminazione di un node con edge 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 gli archi 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, consulta Chiavi esterne.

Eliminazione in cascata per gli archi che collegano lo stesso tipo di nodi

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

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 l'interlacciamento per ottimizzare il traversale dell'edge in avanti. Assicurati di verificare l'impatto sui tuoi carichi di lavoro prima di procedere. Guarda l'esempio che segue, che utilizza AccountTransferAccount come tabella di input di Edge:

--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 per proprietà di nodi o archi con indici secondari

Gli indici secondari sono essenziali per un'elaborazione efficiente delle query. Supportano la ricerca rapida di nodi e archi in base a valori di proprietà specifici, senza dover attraversare l'intera struttura del grafo. Questo è importante quando lavori con grafici di grandi dimensioni, perché l'esplorazione di 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 con 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 AccountByNickName
ON Account (nick_name);

Suggerimento:utilizza gli indici filtrati per NULL per le proprietà sparse. Per ulteriori informazioni, consulta Disattivare l'indicizzazione dei valori NULL.

Accelerare l'esplorazione in avanti dei bordi con il filtro delle proprietà dei bordi

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 gli spigoli della persona specificata e poi filtra quelli che soddisfano la condizione su create_time.

L'esempio seguente mostra come migliorare l'efficienza delle query creando un indice secondario sul riferimento del nodo di origine dell'elemento laterale (id) e sulla proprietà dell'elemento laterale (create_time). Intercala l'indice sotto la tabella di input del nodo di origine per collocarlo insieme al 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 esegui la traversata di un bordo inverso filtrando in base alle sue proprietà, puoi accelerare la query creando un indice secondario utilizzando il nodo di destinazione e le proprietà del bordo per il filtro.

La seguente query di esempio esegue il traversale inverso degli spigoli con filtri sulle 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 sul riferimento del nodo di destinazione dell'elemento perimetrale (account_id) e sulla proprietà dell'elemento perimetrale (create_time), 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, create_time);
    

    Questo approccio offre prestazioni migliori perché gli spigoli inversi sono ordinati per account_id e create_time, il che consente al motore di query di trovare in modo efficiente gli spigoli per account_id che soddisfano la condizione su create_time. Tuttavia, se pattern di query diversi filtrano su proprietà diverse, ogni proprietà potrebbe richiedere un indice separato, il che può comportare un 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 al rendimento, aggiungile alle 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. Prendi in considerazione un esempio 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. Valuta la possibilità di utilizzare 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, modella il tipo come proprietà.

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

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

Se devi modificare spesso lo schema, ti consigliamo di modellare il tipo in una proprietà per aggirare le limitazioni relative alla frequenza degli aggiornamenti dello schema.

Velocizzare le query

I tipi di modelli con proprietà potrebbero velocizzare le query quando il pattern di nodi o bordi fa riferimento a più etichette. La query di esempio seguente trova tutte le istanze di SavingsAccount e InvestmentAccount di proprietà di un Person, supponendo che i tipi di account siano modellati con le 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 una query critica per le prestazioni, valuta la possibilità di creare un modello per Account utilizzando una proprietà. Questo approccio potrebbe offrire prestazioni di query migliori, come mostrato nel seguente esempio di query. 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;

Memorizza il tipo nella chiave dell'elemento del nodo 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à all'interno della chiave dell'elemento del nodo.
  2. Aggiungi il tipo di nodo nella tabella di input degli edge.
  3. Includi il tipo di nodo nelle chiavi di riferimento all'elemento.

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

Configurare il TTL su nodi ed edge

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 sulla sessione, 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 contiene una tabella TTL e una tabella di arista interlacciata, l'interlacciamento deve essere definito con ON DELETE CASCADE. Analogamente, se la tabella dei nodi ha un TTL e viene fatto riferimento a una tabella di angolo tramite una chiave esterna, la chiave esterna 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 nodo Account e l'elemento PersonOwnAccount, come mostrato nell'esempio di schema seguente. Per garantire che tutti i nomi delle tabelle degli elementi siano univoci, nell'esempio alla definizione della tabella di Owns viene assegnato 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