Questo documento descrive come creare query efficienti utilizzando le best practice per la progettazione di schemi Spanner Graph. Puoi iterare la progettazione dello schema, quindi ti consigliamo di identificare prima i pattern di query critici per guidare la progettazione dello schema.
Per informazioni generali sulle best practice di progettazione dello schema di Spanner, consulta Best practice per la progettazione degli schemi.
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. La direzione del bordo è definita dallo schema. L'attraversamento degli archi è un'operazione fondamentale in Spanner Graph, quindi migliorare l'efficienza dell'attraversamento degli archi è fondamentale per le prestazioni della tua applicazione.
Puoi attraversare un arco in due direzioni:
attraversamento degli archi in avanti: segue gli archi in uscita del nodo di origine.
reverse edge traversal: segue gli archi in entrata del nodo di destinazione.
Dato un utente, la seguente query di esempio esegue l'attraversamento degli archi in avanti degli archi Owns
:
GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;
Dato un account, la seguente query di esempio esegue l'attraversamento degli archi inverso degli archi Owns
:
GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;
Ottimizzare l'attraversamento del bordo anteriore utilizzando l'interleaving
Per migliorare le prestazioni di attraversamento degli archi in avanti, interleava la tabella di input degli archi nella tabella di input dei nodi di origine per collocare gli archi con i nodi di origine. L'interleaving è una tecnica di ottimizzazione dell'archiviazione in Spanner che colloca fisicamente le righe della tabella secondaria con le righe principali corrispondenti nell'archiviazione. Per saperne di più sull'interleaving, consulta la 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 degli archi inversi utilizzando la chiave esterna
Per attraversare in modo efficiente gli archi inversi, crea un vincolo di chiave esterna forzata tra l'arco e il nodo di destinazione. Questa chiave esterna forzata crea un indice secondario sul lato con chiave in base alle 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;
Ottimizza l'attraversamento degli archi inversi utilizzando l'indice secondario
Se non vuoi creare una chiave esterna forzata sul bordo, ad esempio a causa della rigida integrità dei dati che impone, puoi creare direttamente un indice secondario nella tabella di input del bordo, 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 AccountOwnedByPerson
ON PersonOwnAccount (account_id), INTERLEAVE IN Account;
INTERLEAVE IN
dichiara una relazione di località dei dati tra l'indice secondario e la tabella in cui è intercalato (Account
nell'esempio). Con l'interleaving, le righe dell'indice secondario AccountOwnedByPerson
si trovano insieme alle righe corrispondenti della tabella Account
. Per saperne di più sull'interleaving, consulta Relazioni tra tabelle padre-figlio. Per ulteriori informazioni sugli indici intercalati, consulta la sezione Indici e intercalazione.
Ottimizzare l'attraversamento dei bordi utilizzando le chiavi esterne informative
Se il tuo scenario presenta colli di bottiglia delle prestazioni di scrittura causati da chiavi esterne forzate, ad esempio quando aggiorni frequentemente i nodi hub con molti archi connessi, valuta la possibilità di utilizzare chiavi esterne informative. L'utilizzo di chiavi esterne informative nelle colonne di riferimento di una tabella edge consente all'ottimizzatore delle query di eliminare le scansioni ridondanti delle tabelle dei nodi. Tuttavia, poiché le chiavi esterne informative non richiedono indici secondari nella tabella edge, non migliorano la velocità delle ricerche quando una query tenta di trovare gli edge utilizzando i nodi finali. Per saperne di più, consulta Confronto tra chiave esterna esterne.
È importante capire che se la tua applicazione non può garantire l'integrità referenziale, l'utilizzo di chiavi esterne informative per l'ottimizzazione delle query potrebbe portare a risultati errati.
L'esempio seguente crea una tabella con una chiave esterna informativa nella colonna
account_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;
Se l'interleaving non è un'opzione, puoi contrassegnare entrambi i riferimenti perimetrali con chiavi esterne informative, come nel seguente esempio:
CREATE TABLE PersonOwnAccount (
id INT64 NOT NULL,
account_id INT64 NOT NULL,
create_time TIMESTAMP,
CONSTRAINT FK_Person FOREIGN KEY (id)
REFERENCES Person (id) NOT ENFORCED,
CONSTRAINT FK_Account FOREIGN KEY (account_id)
REFERENCES Account (id) NOT ENFORCED
) PRIMARY KEY (id, account_id);
Non consentire bordi sospesi
Un arco pendente è un arco che collega meno di due nodi. Un arco pendente può verificarsi quando un nodo viene eliminato senza rimuovere gli archi associati oppure quando un arco viene creato senza collegarlo correttamente ai relativi nodi.
Il divieto di utilizzare bordi sospesi offre i seguenti vantaggi:
- Applica l'integrità della struttura del grafico.
- Migliora le prestazioni delle query evitando il lavoro aggiuntivo di filtrare i bordi in cui non esistono endpoint.
Non consentire i bordi sospesi utilizzando i vincoli referenziali
Per non consentire i bordi sospesi, specifica i vincoli su entrambi gli endpoint:
- Interlaccia la tabella di input degli archi nella tabella di input dei nodi di origine. Questo approccio garantisce che il nodo di origine di un arco esista sempre.
- Crea un vincolo di chiave esterna forzato sugli archi per assicurarti che il nodo di destinazione di un arco esista sempre.
L'esempio seguente utilizza l'interleaving e una chiave esterna forzata 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'interleaving o una chiave esterna forzata per impedire i bordi sospesi,
utilizza la clausola ON DELETE
per controllare il comportamento quando vuoi eliminare un
nodo con bordi ancora collegati. Per ulteriori informazioni, consulta
Eliminazione a cascata per le tabelle con interfoliazione
e
Azioni delle chiavi esterne.
Puoi utilizzare ON DELETE
nei seguenti modi:
ON DELETE NO ACTION
(o omettendo la clausolaON DELETE
): l'eliminazione di un nodo con bordi non riuscirà.ON DELETE CASCADE
: L'eliminazione di un nodo rimuove automaticamente gli archi associati nella stessa transazione.
Eliminazione a cascata per i bordi che collegano diversi tipi di nodi
Elimina i bordi quando viene eliminato il nodo di origine. Ad esempio,
INTERLEAVE IN PARENT Person ON DELETE CASCADE
elimina tutti gli archiPersonOwnAccount
in uscita dal nodoPerson
in fase di eliminazione. Per ulteriori informazioni, vedi 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 archiPersonOwnAccount
in entrata nel nodoAccount
in fase di eliminazione.
Eliminazione a cascata per i bordi che collegano lo stesso tipo di nodi
Quando i nodi di origine e di destinazione di un arco hanno lo stesso tipo e l'arco è intercalato nel nodo di origine, puoi definire ON DELETE CASCADE
solo per il nodo di origine o di destinazione (ma non per entrambi).
Per rimuovere gli archi sospesi in entrambi i casi, crea una chiave esterna forzata sul riferimento al nodo di origine dell'arco anziché intercalare la tabella di input dell'arco nella tabella di input del nodo di origine.
Consigliamo l'interleaving per
ottimizzare l'attraversamento del bordo in avanti.
Assicurati di verificare l'impatto sui tuoi workload prima di procedere. Vedi l'esempio
seguente, che utilizza AccountTransferAccount
come tabella di input
dei bordi:
--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);
Filtra per proprietà di nodi o archi con indici secondari
Gli indici secondari sono essenziali per l'elaborazione efficiente delle query. Supportano ricerche rapide di nodi e archi in base a valori di proprietà specifici, senza dover attraversare l'intera struttura del grafico. Questo è importante quando lavori con grafici di grandi dimensioni, perché attraversare tutti i nodi e gli archi può essere molto inefficiente.
Accelerare il filtraggio dei nodi per proprietà
Per velocizzare il filtraggio 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 corrispondere
ai criteri di filtro.
GRAPH FinGraph
MATCH (acct:Account)
WHERE acct.nick_name = "abcd"
RETURN acct.id;
Per velocizzare la query, crea un indice secondario sulla proprietà filtrata, come mostrato 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 NULL per le proprietà sparse. Per maggiori informazioni, vedi Disattivare l'indicizzazione dei valori NULL.
Accelerare l'attraversamento del bordo in avanti con il filtro delle proprietà del bordo
Quando attraversi un arco filtrando in base alle sue proprietà, puoi velocizzare la query creando un indice secondario sulle proprietà dell'arco 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 periodo di 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;
Per impostazione predefinita, questa query legge tutti i bordi della persona specificata e poi filtra
i bordi che soddisfano la condizione su create_time
.
L'esempio seguente mostra come migliorare l'efficienza delle query creando
un indice secondario sul riferimento al nodo di origine del bordo (id
) e sulla proprietà del bordo
(create_time
). Interleave l'indice nella 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;
Utilizzando questo approccio, la query può trovare in modo efficiente tutti gli archi che soddisfano la condizione su create_time
.
Accelerare l'attraversamento degli archi inversi con il filtro delle proprietà degli archi
Quando attraversi un arco inverso durante il filtraggio in base alle relative proprietà, puoi velocizzare la query creando un indice secondario utilizzando il nodo di destinazione e le proprietà dell'arco per il filtraggio.
La seguente query di esempio esegue l'attraversamento inverso degli archi con il filtro delle proprietà degli archi:
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 al nodo di destinazione del bordo (
account_id
) e sulla proprietà del bordo (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 archi inversi sono ordinati per
account_id
ecreate_time
, il che consente al motore di query di trovare in modo efficiente gli archi peraccount_id
che soddisfano la condizione sucreate_time
. Tuttavia, se diversi pattern di query filtrano proprietà diverse, ogni proprietà potrebbe richiedere un indice separato, il che può aumentare il sovraccarico.Crea un indice secondario sul riferimento al nodo di destinazione edge (
account_id
) e memorizza la proprietà edge (create_time
) in una colonna di archiviazione, come mostrato nel seguente esempio: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à; tuttavia, la query deve leggere tutti i bordi del nodo di destinazione e poi filtrare in base alle proprietà dei bordi.
Puoi combinare questi approcci seguendo queste linee guida:
- Utilizza le proprietà edge nelle colonne dell'indice se vengono utilizzate in query fondamentali per il rendimento.
- 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 e archi vengono spesso modellati con etichette. Tuttavia, puoi anche utilizzare le proprietà per modellare i tipi. Considera un esempio in cui sono presenti molti tipi diversi di account, come BankAccount
, InvestmentAccount
e RetirementAccount
. Puoi memorizzare gli account in tabelle di input separate e
modellarli come etichette separate oppure puoi memorizzare gli account in un'unica tabella di input
e utilizzare una proprietà per distinguere i tipi.
Inizia il processo di modellazione modellando i tipi con le etichette. Prendi in considerazione l'utilizzo delle proprietà nei seguenti scenari.
Migliorare la gestione degli schemi
Se il grafico ha molti tipi diversi di nodi e archi, la gestione di una tabella di input separata 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 di 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, vedi Limitare la frequenza degli aggiornamenti dello schema.
Se devi modificare spesso lo schema, ti consigliamo di modellare il tipo in una proprietà per aggirare i limiti alla frequenza degli aggiornamenti dello schema.
Velocizzare le query
La modellazione di tipi con proprietà può velocizzare le query quando il pattern di nodi o archi
fa riferimento a più etichette. La seguente query di esempio trova tutte le istanze di
SavingsAccount
e InvestmentAccount
di proprietà di un Person
, supponendo che i tipi di account
siano 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 una query
critica per il rendimento, valuta la possibilità di modellare Account
utilizzando una proprietà. Questo
approccio potrebbe fornire prestazioni delle 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 di negozio nella chiave dell'elemento nodo per velocizzare le query
Per velocizzare le query con il filtro in base al tipo di nodo quando un tipo di nodo viene modellato con una proprietà e il tipo non cambia durante il ciclo di vita del nodo, segui questi passaggi:
- Includi la proprietà come parte della chiave dell'elemento nodo.
- Aggiungi il tipo di nodo nella tabella di input degli archi.
- Includi il tipo di nodo nelle chiavi di riferimento degli archi.
Il seguente esempio applica questa ottimizzazione al nodo Account
e al
nodo 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 e bordi
La durata (TTL) di Spanner è un meccanismo che supporta la scadenza e la rimozione automatiche dei dati dopo un periodo di tempo 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 aiuta 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 degli archi intercalata, l'interleave
deve essere definito con ON DELETE CASCADE
. Allo stesso modo, se
la tabella dei nodi ha un TTL e viene referenziata da una tabella degli archi tramite una chiave
esterna, la chiave esterna deve essere definita con ON DELETE CASCADE
per mantenere
l'integrità referenziale oppure come chiave esterna informativa per consentire
l'esistenza di un arco sospeso.
Nell'esempio seguente, AccountTransferAccount
viene memorizzato 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));
Unire le tabelle di input di nodi e archi
Puoi utilizzare la stessa tabella di input per definire più di un nodo e un arco nello schema.
Nelle tabelle di esempio seguenti, i nodi Account
hanno una chiave composta
(owner_id, account_id)
. Esiste una definizione di edge implicita: il nodo Person
con chiave (id
) è proprietario del 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 il bordo PersonOwnAccount
, come mostrato nell'esempio di schema seguente.
Per garantire che tutti i nomi delle tabelle degli elementi siano univoci, l'esempio assegna alla definizione della tabella edge 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
- Crea, aggiorna o elimina uno schema Spanner Graph.
- Inserire, aggiornare o eliminare i dati di Spanner Graph.