Best practice per la progettazione di uno schema di grafo Spanner

Questo documento descrive le best practice per la progettazione di uno schema Spanner Graph, concentrandosi su query efficienti, attraversamento ottimizzato degli archi e tecniche efficaci di gestione dei dati.

Per informazioni sulla progettazione degli schemi Spanner (non degli schemi Spanner Graph), consulta Best practice per la progettazione degli schemi.

Scegliere un design dello schema

La progettazione dello schema influisce sulle prestazioni del grafico. I seguenti argomenti ti aiutano a scegliere una strategia efficace.

Progettazioni schematizzate e senza schema

  • Un design schematizzato memorizza la definizione del grafico nello schema Spanner Graph, che è adatto a grafici stabili con modifiche alla definizione poco frequenti. Lo schema applica la definizione del grafico e le proprietà supportano tutti i tipi di dati Spanner.

  • Un design senza schema deduce la definizione del grafico dai dati, offrendo maggiore flessibilità senza richiedere modifiche allo schema. Le etichette e le proprietà dinamiche non vengono applicate per impostazione predefinita. Le proprietà devono essere valori JSON validi.

Di seguito sono riassunte le principali differenze tra la gestione dei dati con schema e senza schema. Inoltre, considera le query del grafico per decidere quale tipo di schema utilizzare.

Funzionalità Gestione dei dati schematizzata Gestione dei dati senza schema
Memorizzazione della definizione del grafico La definizione del grafico è memorizzata nello schema di Spanner Graph. La definizione del grafico è evidente dai dati. Tuttavia, Spanner Graph non esamina i dati per dedurre la definizione.
Aggiornamento della definizione del grafico Richiede una modifica dello schema di Spanner Graph. Adatto quando la definizione è ben definita e cambia di rado. Nessuna modifica dello schema di Spanner Graph necessaria.
Applicazione della definizione del grafico Uno schema del grafico delle proprietà applica i tipi di nodi consentiti per un arco. Inoltre, applica le proprietà e i tipi di proprietà consentiti di un tipo di nodo o arco del grafico. Non applicato per impostazione predefinita. Puoi utilizzare i vincoli di controllo per garantire l'integrità dei dati di etichette e proprietà.
Tipi di dati della proprietà Supporta qualsiasi tipo di dati Spanner, ad esempio timestamp. Le proprietà dinamiche devono essere un valore JSON valido.

Scegliere una progettazione dello schema basata sulle query del grafico

I progetti schematizzati e senza schema spesso offrono prestazioni comparabili. Tuttavia, quando le query utilizzano pattern di percorso quantificati che interessano più tipi di nodi o archi, un design senza schema offre prestazioni migliori.

Il modello dei dati sottostante è uno dei motivi principali. Un design senza schema archivia tutti i dati in singole tabelle di nodi e archi, che DYNAMIC LABEL applica. Le query che attraversano più tipi vengono eseguite con un numero minimo di scansioni delle tabelle.

Al contrario, i progetti schematizzati utilizzano in genere tabelle separate per ogni tipo di nodo e arco, pertanto le query che interessano più tipi devono scansionare e combinare i dati di tutte le tabelle corrispondenti.

Di seguito sono riportati esempi di query che funzionano bene con i design senza schema e un esempio di query che funziona bene con entrambi i design:

Progettazione senza schema

Le seguenti query hanno un rendimento migliore con una progettazione senza schema perché utilizzano pattern di percorso quantificati che possono corrispondere a più tipi di nodi e archi:

  • Il pattern del percorso quantificato di questa query utilizza più tipi di archi (Transfer o Withdraw) e non specifica i tipi di nodi intermedi per i percorsi più lunghi di un hop.

    GRAPH FinGraph
    MATCH p = (:Account {id:1})-[:Transfer|Withdraw]->{1,3}(:Account)
    RETURN TO_JSON(p) AS p;
    
  • Il pattern di percorso quantificato di questa query trova percorsi da uno a tre hop tra i nodi Person e Account, utilizzando più tipi di archi (Owns o Transfers), senza specificare i tipi di nodi intermedi per i percorsi più lunghi. In questo modo i percorsi possono attraversare nodi intermedi di vari tipi. Ad esempio, (:Person)-[:Owns]->(:Account)-[:Transfers]->(:Account).

    GRAPH FinGraph
    MATCH p = (:Person {id:1})-[:Owns|Transfers]->{1,3}(:Account)
    RETURN TO_JSON(p) AS p;
    
  • Il pattern di percorso quantificato di questa query trova percorsi da uno a tre hop tra i nodi Person e Account, senza specificare etichette dei bordi. Analogamente alla query precedente, consente ai percorsi di attraversare nodi intermedi di vari tipi.

    GRAPH FinGraph
    MATCH p = (:Person {id:1})-[]->{1,3}(:Account)
    RETURN TO_JSON(p) AS p;
    
  • Questa query trova percorsi da uno a tre hop tra i nodi Account utilizzando i bordi di tipo Owns in qualsiasi direzione (-[:Owns]-). Poiché i percorsi possono attraversare i bordi in entrambe le direzioni e i nodi intermedi non sono specificati, un percorso di due hop potrebbe passare attraverso nodi di tipi diversi. Ad esempio, (:Account)-[:Owns]-(:Person)-[:Owns]-(:Account).

    GRAPH FinGraph
    MATCH p = (:Account {id:1})-[:Owns]-{1,3}(:Account)
    RETURN TO_JSON(p) AS p;
    

Entrambi i design

La seguente query ha prestazioni paragonabili sia con i progetti schematizzati che con quelli senza schema. Il suo percorso quantificato, (:Account)-[:Transfer]->{1,3}(:Account), coinvolge un tipo di nodo, Account, e un tipo di arco, Transfer. Poiché il percorso coinvolge un solo tipo di nodo e un solo tipo di arco, il rendimento è paragonabile per entrambi i design. Anche se i nodi intermedi non sono etichettati in modo esplicito, il pattern li vincola a essere nodi Account. Il nodo Person viene visualizzato al di fuori di questo percorso quantificato.

GRAPH FinGraph
MATCH p = (:Person {id:1})-[:Owns]->(:Account)-[:Transfer]->{1,3}(:Account)
RETURN TO_JSON(p) AS p;

Ottimizzare le prestazioni dello schema di Spanner Graph

Dopo aver scelto di utilizzare uno schema Spanner Graph schematizzato o senza schema, puoi ottimizzarne il rendimento nei seguenti modi:

Ottimizza l'attraversamento degli archi

L'attraversamento degli archi è il processo di navigazione in un grafico seguendo i suoi archi, partendo da un nodo specifico e spostandosi lungo gli archi collegati per raggiungere altri nodi. Lo schema definisce la direzione del bordo. L'attraversamento dei bordi è un'operazione fondamentale in Spanner Graph, quindi migliorare l'efficienza dell'attraversamento dei bordi può migliorare significativamente il rendimento della tua applicazione.

Puoi attraversare un arco in due direzioni:

  • L'attraversamento degli archi in avanti segue gli archi in uscita del nodo di origine.
  • L'attraversamento degli archi inverso segue gli archi in entrata del nodo di destinazione.

Esempi di query di attraversamento degli archi in avanti e all'indietro

La seguente query di esempio esegue l'attraversamento degli archi in avanti degli archi Owns per una determinata persona:

GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;

La seguente query di esempio esegue l'attraversamento inverso degli archi Owns per un determinato account:

GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;

Ottimizzare l'attraversamento del bordo anteriore

Per migliorare le prestazioni di attraversamento del bordo di inoltro, ottimizza l'attraversamento dall'origine al bordo e dal bordo alla destinazione.

  • Per ottimizzare l'attraversamento dall'origine all'edge, interleva la tabella di input edge nella tabella di input del nodo di origine utilizzando la clausola INTERLEAVE IN PARENT. L'interleaving è una tecnica di ottimizzazione dell'archiviazione in Spanner che colloca le righe della tabella secondaria insieme alle righe principali corrispondenti nell'archiviazione. Per ulteriori informazioni sull'interleaving, consulta la panoramica degli schemi.

  • Per ottimizzare l'attraversamento dal nodo edge alla destinazione, crea un vincolo di chiave esterna tra il nodo edge e il nodo di destinazione
    . In questo modo viene applicato il vincolo edge-to-destination, che può migliorare il rendimento eliminando le scansioni della tabella di destinazione. Se le chiavi esterne applicate causano colli di bottiglia nelle prestazioni di scrittura (ad esempio, durante l'aggiornamento dei nodi hub), utilizza una chiave esterna informativa in alternativa.

I seguenti esempi mostrano come utilizzare l'interleaving con un vincolo di chiave esterna applicato e uno informativo.

Chiave esterna applicata

In questo esempio di tabella perimetrale, PersonOwnAccount esegue le seguenti operazioni:

  • Si intercala nella tabella dei nodi di origine Person.

  • Crea una chiave esterna forzata nella tabella dei nodi di destinazione 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;

Chiave esterna informativa

In questo esempio di tabella perimetrale, PersonOwnAccount esegue le seguenti operazioni:

  • Si intercala nella tabella dei nodi di origine Person.

  • Crea una chiave esterna informativa per la tabella dei nodi di destinazione 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;

Ottimizza l'attraversamento degli archi inversi

Ottimizza l'attraversamento degli archi inversi, a meno che le query non utilizzino solo l'attraversamento in avanti, perché le query che coinvolgono l'attraversamento inverso o bidirezionale sono comuni.

Per ottimizzare l'attraversamento degli archi inversi, puoi:

  • Crea un indice secondario nella tabella edge.

  • Interleave l'indice nella tabella di input del nodo di destinazione per collocare gli archi con i nodi di destinazione.

  • Memorizza le proprietà dei bordi nell'indice.

Questo esempio mostra un indice secondario per ottimizzare l'attraversamento inverso degli archi per la tabella degli archi PersonOwnAccount:

  • La clausola INTERLEAVE IN colloca i dati dell'indice con la tabella dei nodi di destinazione Account.

  • La clausola STORING memorizza le proprietà dei bordi nell'indice.

Per saperne di più sugli indici intercalati, consulta Indici e intercalazione.

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;

Utilizzare gli indici secondari per filtrare le proprietà

Un indice secondario consente di cercare in modo efficiente nodi e archi in base a valori di proprietà specifici. L'utilizzo di un indice consente di evitare un'analisi completa della tabella ed è particolarmente utile per i grafici di grandi dimensioni.

Accelerare il filtraggio dei nodi per proprietà

La seguente query trova gli account per un nickname specificato. Poiché non utilizza un indice secondario, tutti i nodi Account devono essere scansionati per trovare i risultati corrispondenti:

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

Crea un indice secondario sulla proprietà filtrata nello schema per velocizzare il processo di filtraggio:

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

Accelerare il filtraggio dei bordi per proprietà

Puoi utilizzare un indice secondario per migliorare le prestazioni del filtraggio dei bordi in base ai valori delle proprietà.

Attraversamento del bordo in avanti

Senza un indice secondario, questa query deve analizzare tutti gli archi di una persona per trovare quelli che corrispondono al 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;

Il seguente codice migliora l'efficienza delle query creando un indice secondario sul riferimento al nodo di origine del bordo (id) e sulla proprietà del bordo (create_time). La query definisce anche l'indice come elemento secondario interleaved della tabella di input del nodo di origine, che lo colloca 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;

Attraversamento inverso degli archi

Senza un indice secondario, la seguente query di attraversamento degli archi inversi deve leggere tutti gli archi prima di poter trovare la persona proprietaria dell'account specificato dopo il create_time specificato:

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;

Il seguente codice migliora l'efficienza delle query creando un indice secondario sul riferimento al nodo di destinazione del bordo (account_id) e sulla proprietà del bordo (create_time). La query definisce anche l'indice come figlio interleaved della tabella dei nodi di destinazione, che lo colloca insieme al nodo di destinazione.

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;

Evitare bordi sospesi

Un arco che collega zero o un nodo, un arco sospeso, può compromettere l'efficienza delle query Spanner Graph e l'integrità della struttura del grafico. Un arco sospeso può verificarsi se elimini un nodo senza eliminare gli archi associati. Un arco sospeso può verificarsi anche se crei un arco, ma il nodo di origine o di destinazione non esiste. Per evitare bordi sospesi, incorpora quanto segue nello schema di Spanner Graph:

Utilizzare i vincoli referenziali

Per evitare archi sospesi, puoi utilizzare l'interleaving e le chiavi esterne forzate su entrambi gli endpoint seguendo questi passaggi:

  1. Interleave la tabella di input dei nodi edge nella tabella di input dei nodi di origine per assicurarti che il nodo di origine di un edge esista sempre.

  2. Crea un vincolo di chiave esterna forzata sugli archi per assicurarti che il nodo di destinazione di un arco esista sempre. Sebbene le chiavi esterne forzate impediscano i bordi sospesi, rendono l'inserimento e l'eliminazione dei bordi più costosi.

Il seguente esempio utilizza una chiave esterna forzata e interleave la tabella di input edge nella tabella di input del nodo di origine utilizzando la clausola INTERLEAVE IN PARENT. L'utilizzo combinato di una chiave esterna forzata e dell'interleaving può anche contribuire a ottimizzare l'attraversamento degli archi in avanti.

  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;

Elimina bordi con ON DELETE CASCADE

Quando utilizzi l'interleaving o una chiave esterna forzata per evitare bordi sospesi, utilizza la clausola ON DELETE CASCADE nello schema Spanner Graph per eliminare i bordi associati di un nodo nella stessa transazione che elimina il nodo. Per ulteriori informazioni, consulta Eliminazione a cascata per le tabelle con interfoliazione e Azioni delle chiavi esterne.

Eliminazione a cascata per i bordi che collegano diversi tipi di nodi

Gli esempi seguenti mostrano come utilizzare ON DELETE CASCADE nello schema di Spanner Graph per eliminare gli archi sospesi quando elimini un nodo di origine o di destinazione. In entrambi i casi, il tipo del nodo eliminato e il tipo del nodo a cui è collegato da un arco sono diversi.

Nodo di origine

Utilizza l'interleaving per eliminare gli archi sospesi quando viene eliminato il nodo di origine. Di seguito viene mostrato come utilizzare l'interleaving per eliminare gli archi in uscita quando viene eliminato il nodo di origine (Person). Per saperne di più, consulta Creare tabelle interleaved.

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

Nodo di destinazione

Utilizza un vincolo di chiave esterna per eliminare gli archi sospesi quando viene eliminato il nodo di destinazione. Il seguente esempio mostra come utilizzare una chiave esterna con ON DELETE CASCADE in una tabella edge per eliminare gli edge in entrata quando viene eliminato il nodo di destinazione (Account):

CONSTRAINT FK_Account FOREIGN KEY(account_id)
  REFERENCES Account(id) ON DELETE CASCADE

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

Quando i nodi di origine e di destinazione di un arco sono dello stesso tipo e l'arco è interlacciato nel nodo di origine, puoi definire ON DELETE CASCADE per il nodo di origine o di destinazione, ma non per entrambi.

Per evitare bordi sospesi in questi scenari, non eseguire l'interleaving nella tabella di input del nodo di origine. Crea invece due chiavi esterne applicate ai riferimenti ai nodi di origine e di destinazione.

L'esempio seguente utilizza AccountTransferAccount come tabella di input dei bordi. Definisce due chiavi esterne, una su ciascun nodo finale del bordo di trasferimento, entrambe con l'azione 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);

Configurare la durata (TTL) su nodi e bordi

TTL consente di far scadere e rimuovere i dati dopo un periodo specificato. Puoi utilizzare il TTL nello schema per mantenere le dimensioni e le prestazioni del database rimuovendo i dati con una durata o pertinenza limitata. Ad esempio, puoi configurarlo per rimuovere informazioni sulla sessione, cache temporanee o log eventi.

L'esempio seguente utilizza il TTL per eliminare gli account 90 giorni dopo la 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));

Quando definisci una policy TTL in una tabella dei nodi, devi configurare la gestione degli archi correlati per evitare archi sospesi non intenzionali:

  • Per le tabelle edge con interleaving:se una tabella edge è interleaved nella tabella dei nodi, puoi definire la relazione di interleaving con ON DELETE CASCADE. In questo modo, quando TTL elimina un nodo, vengono eliminati anche gli archi interleaved associati.

  • Per le tabelle edge con chiavi esterne:se una tabella edge fa riferimento alla tabella dei nodi con una chiave esterna, hai due opzioni:

    • Per eliminare automaticamente gli archi quando il nodo di riferimento viene eliminato da TTL, utilizza ON DELETE CASCADE sulla chiave esterna. In questo modo viene mantenuta l'integrità referenziale.
    • Per consentire ai bordi di rimanere dopo l'eliminazione del nodo a cui fanno riferimento (creando un bordo sospeso), definisci la chiave esterna come chiave esterna informativa.

Nell'esempio seguente, la tabella degli archi AccountTransferAccount è soggetta a due norme di eliminazione dei dati:

  • Una policy TTL elimina i record di trasferimento che risalgono a più di dieci anni fa.
  • La clausola ON DELETE CASCADE elimina tutti i record di trasferimento associati a un'origine quando l'account viene eliminato.
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 di nodi e archi

Per ottimizzare lo schema, definisci un nodo e i relativi archi in entrata o in uscita all'interno di una singola tabella. Questo approccio offre i seguenti vantaggi:

  • Meno tabelle: riduce il numero di tabelle nello schema, il che semplifica la gestione dei dati.

  • Prestazioni delle query migliorate: elimina l'attraversamento che utilizza i join a una tabella edge separata.

Questa tecnica funziona bene quando la chiave primaria di una tabella definisce anche una relazione con un'altra tabella. Ad esempio, se la tabella Account ha una chiave primaria composita (owner_id, account_id), la parte owner_id può essere una chiave esterna che fa riferimento alla tabella Person. Questa struttura consente alla tabella Account di rappresentare sia il nodo Account sia l'arco in entrata dal nodo 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);

Puoi utilizzare la tabella Account per definire sia il nodo Account sia il relativo arco Owns in entrata. Ciò è mostrato nella seguente CREATE PROPERTY GRAPH dichiarazione. Nella clausola EDGE TABLES, assegni alla tabella Account l'alias Owns. Questo perché ogni elemento nello schema del grafico deve avere un nome univoco.

  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