Ce document explique comment créer des requêtes efficaces en suivant les bonnes pratiques de conception des schémas de graphiques Spanner. Vous pouvez itérer sur la conception de votre schéma. Nous vous recommandons donc de commencer par identifier les modèles de requêtes critiques pour guider la conception de votre schéma.
Pour en savoir plus sur les bonnes pratiques de conception de schémas Spanner, consultez la page Bonnes pratiques de conception de schémas.
Optimiser la traversée des bords
La parcours des arêtes consiste à parcourir un graphe en suivant ses arêtes, en commençant par un nœud particulier et en se déplaçant le long des arêtes connectées pour atteindre d'autres nœuds. Le sens de l'arc est défini par le schéma. La traversée de bords est une opération fondamentale dans Spanner Graph. Par conséquent, améliorer l'efficacité de la traversée de bords est essentiel pour les performances de votre application.
Vous pouvez traverser un bord dans deux directions:
Parcours des arêtes avant: suit les arêtes sortantes du nœud source.
Parcours des arêtes inverses: suit les arêtes entrantes du nœud de destination.
Pour une personne donnée, l'exemple de requête suivant effectue une traversée de bord avant des bords Owns
:
GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;
Pour un compte donné, l'exemple de requête suivant effectue une traversée des bords inversée des bords Owns
:
GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;
Optimiser la traversée de l'arête avant à l'aide de l'entrelacement
Pour améliorer les performances de la traversée des arêtes avant, intercalez la table d'entrée des arêtes dans la table d'entrée des nœuds sources afin de placer les arêtes avec les nœuds sources. L'entrelacement est une technique d'optimisation du stockage dans Spanner qui place physiquement les lignes de table enfant avec leurs lignes parentes correspondantes dans l'espace de stockage. Pour en savoir plus sur l'entrelacement, consultez la section Présentation des schémas.
L'exemple suivant illustre ces bonnes pratiques:
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;
Optimiser la traversée des arêtes inverses à l'aide d'une clé étrangère
Pour parcourir efficacement les arêtes inverses, créez une contrainte de clé étrangère entre l'arête et le nœud de destination. Cette clé étrangère crée automatiquement un index secondaire sur l'arc associé aux clés de nœud de destination. L'index secondaire est utilisé automatiquement lors de l'exécution de la requête.
L'exemple suivant illustre ces bonnes pratiques:
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;
Optimiser la traversée des bords inverses à l'aide d'un indice secondaire
Si vous ne souhaitez pas créer de clé étrangère sur le bord, par exemple en raison de l'intégrité stricte des données qu'elle applique, vous pouvez créer directement un indice secondaire sur la table d'entrée de bord, comme illustré dans l'exemple suivant:
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);
Interdire les bords non connectés
Une arête pendante est une arête qui relie moins de deux nœuds. Une arête pendante peut se produire lorsqu'un nœud est supprimé sans supprimer ses arêtes associées ou lorsqu'une arête est créée sans l'associer correctement à ses nœuds.
L'interdiction des bords non connectés offre les avantages suivants:
- Applique l'intégrité de la structure du graphique.
- Améliore les performances des requêtes en évitant le travail supplémentaire de filtrage des arêtes où les points de terminaison n'existent pas.
Interdire les arêtes sans extrémité à l'aide de contraintes référentielles
Pour interdire les arêtes pendantes, spécifiez des contraintes sur les deux points de terminaison:
- Intercalez la table d'entrée de l'arête dans la table d'entrée du nœud source. Cette approche garantit que le nœud source d'une arête existe toujours.
- Créez une contrainte de clé étrangère sur les arêtes pour vous assurer que le nœud de destination d'une arête existe toujours.
L'exemple suivant utilise l'entrelacement et une clé étrangère pour appliquer l'intégrité référentielle:
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;
Utiliser ON DELETE CASCADE pour supprimer automatiquement les arêtes lors de la suppression d'un nœud
Lorsque vous utilisez l'intercalation ou une clé étrangère pour interdire les arêtes pendantes, utilisez la clause ON DELETE
pour contrôler le comportement lorsque vous souhaitez supprimer un nœud avec des arêtes encore attachées. Pour en savoir plus, consultez les sections Suppression en cascade pour les tables entrelacées et Suppression en cascade avec des clés étrangères.
Vous pouvez utiliser ON DELETE
de différentes manières:
ON DELETE NO ACTION
(ou omission de la clauseON DELETE
): la suppression d'un nœud avec des arêtes échouera.ON DELETE CASCADE
: la suppression d'un nœud supprime automatiquement les arêtes associées dans la même transaction.
Cascade de suppression pour les arêtes reliant différents types de nœuds
Supprimez les arêtes lorsque le nœud source est supprimé. Par exemple,
INTERLEAVE IN PARENT Person ON DELETE CASCADE
supprime toutes les arêtesPersonOwnAccount
sortantes du nœudPerson
en cours de suppression. Pour en savoir plus, consultez la section Créer des tables entrelacées.Supprimez les arêtes lorsque le nœud de destination est supprimé. Par exemple,
CONSTRAINT FK_Account FOREIGN KEY(account_id) REFERENCES Account(id) ON DELETE CASCADE
supprime toutes les arêtesPersonOwnAccount
entrantes dans le nœudAccount
en cours de suppression. Pour en savoir plus, consultez la section Clés étrangères.
Cascade de suppression pour les arêtes reliant le même type de nœuds
Lorsque les nœuds source et de destination d'une arête ont le même type et que l'arête est entrelacée dans le nœud source, vous ne pouvez définir ON DELETE CASCADE
que pour le nœud source ou le nœud de destination (mais pas les deux).
Pour supprimer automatiquement les arêtes sans extrémité dans les deux cas, créez une clé étrangère sur la référence du nœud source de l'arête au lieu d'intercaler la table d'entrée de l'arête dans la table d'entrée du nœud source.
Nous vous recommandons d'intercaler des éléments pour optimiser la traversée de l'arête avant.
Assurez-vous de vérifier l'impact sur vos charges de travail avant de continuer. Consultez l'exemple suivant, qui utilise AccountTransferAccount
comme table d'entrée de bord:
--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);
Filtrer par propriétés de nœud ou d'arête avec des index secondaires
Les index secondaires sont essentiels pour un traitement efficace des requêtes. Ils permettent des recherches rapides de nœuds et d'arêtes en fonction de valeurs de propriété spécifiques, sans avoir à parcourir l'ensemble de la structure du graphique. Cela est important lorsque vous travaillez avec de grands graphiques, car parcourir tous les nœuds et les arêtes peut être très inefficace.
Accélérer le filtrage des nœuds par propriété
Pour accélérer le filtrage par propriétés de nœud, créez des index secondaires sur les propriétés. Par exemple, la requête suivante recherche les comptes associés à un pseudo donné. Sans indice secondaire, tous les nœuds Account
sont analysés pour correspondre aux critères de filtrage.
GRAPH FinGraph
MATCH (acct:Account)
WHERE acct.nick_name = "abcd"
RETURN acct.id;
Pour accélérer la requête, créez un indice secondaire sur la propriété filtrée, comme indiqué dans l'exemple suivant:
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);
Conseil:Utilisez des index filtrés par NULL pour les propriétés peu denses. Pour en savoir plus, consultez la section Désactiver l'indexation des valeurs NULL.
Accélérez la traversée des bords avant avec le filtrage des propriétés de bord
Lorsque vous parcourez une arête en filtrant sur ses propriétés, vous pouvez accélérer la requête en créant un indice secondaire sur les propriétés de l'arête et en intercalant l'indice dans le nœud source.
Par exemple, la requête suivante recherche les comptes appartenant à une personne donnée après un certain temps:
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;
Par défaut, cette requête lit tous les bords de la personne spécifiée, puis filtre les bords qui répondent à la condition sur create_time
.
L'exemple suivant montre comment améliorer l'efficacité des requêtes en créant un indice secondaire sur la référence du nœud source de l'arc (id
) et la propriété de l'arc (create_time
). Intercalez l'index sous la table d'entrée du nœud source pour le placer à côté du nœud source.
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;
Grâce à cette approche, la requête peut trouver efficacement tous les bords qui satisfont la condition sur create_time
.
Accélérer la traversée des bords inverses avec le filtrage des propriétés de bord
Lorsque vous parcourez une arête inverse en filtrant sur ses propriétés, vous pouvez accélérer la requête en créant un indice secondaire à l'aide du nœud de destination et des propriétés de l'arête pour le filtrage.
L'exemple de requête suivant effectue un balayage des arêtes inverse avec filtrage sur les propriétés des arêtes:
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;
Pour accélérer cette requête à l'aide d'un indice secondaire, utilisez l'une des options suivantes:
Créez un indice secondaire sur la référence du nœud de destination de l'edge (
account_id
) et la propriété de l'edge (create_time
), comme illustré dans l'exemple suivant: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);
Cette approche offre de meilleures performances, car les arêtes inverses sont triées par
account_id
etcreate_time
, ce qui permet au moteur de requêtes de trouver efficacement des arêtes pouraccount_id
satisfaisant la condition surcreate_time
. Toutefois, si différents modèles de requêtes filtrent sur différentes propriétés, chaque propriété peut nécessiter un indice distinct, ce qui peut entraîner des coûts supplémentaires.Créez un indice secondaire sur la référence du nœud de destination de l'edge (
account_id
) et stockez la propriété de l'edge (create_time
) dans une colonne de stockage, comme illustré dans l'exemple suivant: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);
Cette approche peut stocker plusieurs propriétés. Toutefois, la requête doit lire tous les arcs du nœud de destination, puis filtrer en fonction des propriétés des arcs.
Vous pouvez combiner ces approches en suivant ces consignes:
- Utilisez des propriétés de bordure dans les colonnes d'index si elles sont utilisées dans des requêtes critiques pour les performances.
- Pour les propriétés utilisées dans les requêtes moins sensibles aux performances, ajoutez-les dans les colonnes de stockage.
Modéliser les types de nœuds et d'arêtes avec des libellés et des propriétés
Les types de nœuds et d'arêtes sont généralement modélisés à l'aide de libellés. Toutefois, vous pouvez également utiliser des propriétés pour modéliser des types. Prenons l'exemple d'un cas où il existe de nombreux types de comptes, comme BankAccount
, InvestmentAccount
et RetirementAccount
. Vous pouvez stocker les comptes dans des tables d'entrée distinctes et les modéliser en tant que libellés distincts, ou stocker les comptes dans une seule table d'entrée et utiliser une propriété pour différencier les types.
Commencez le processus de modélisation en modélisant les types avec des étiquettes. Envisagez d'utiliser des propriétés dans les scénarios suivants.
Améliorer la gestion des schémas
Si votre graphique comporte de nombreux types de nœuds et d'arêtes différents, la gestion d'une table d'entrée distincte pour chacun d'eux peut s'avérer difficile. Pour faciliter la gestion du schéma, modélisez le type en tant que propriété.
Modéliser des types dans une propriété pour gérer les types qui changent fréquemment
Lorsque vous modélisez des types en tant que libellés, l'ajout ou la suppression de types nécessite des modifications du schéma. Si vous effectuez trop de mises à jour de schéma sur une courte période, Spanner peut limiter le traitement des mises à jour de schéma en file d'attente. Pour en savoir plus, consultez la section Limiter la fréquence des mises à jour de schéma.
Si vous devez modifier fréquemment le schéma, nous vous recommandons de modéliser le type dans une propriété pour contourner les limites liées à la fréquence des mises à jour du schéma.
Accélérer les requêtes
Modéliser des types avec des propriétés peut accélérer les requêtes lorsque le modèle de nœud ou d'arête fait référence à plusieurs libellés. L'exemple de requête suivant recherche toutes les instances de SavingsAccount
et InvestmentAccount
appartenant à un Person
, en supposant que les types de comptes sont modélisés avec des libellés:
GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:SavingsAccount|InvestmentAccount)
RETURN acct.id;
Le modèle de nœud acct
fait référence à deux libellés. S'il s'agit d'une requête critique pour les performances, envisagez de modéliser Account
à l'aide d'une propriété. Cette approche peut améliorer les performances des requêtes, comme illustré dans l'exemple de requête suivant. Nous vous recommandons de comparer les deux requêtes.
GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:Account)
WHERE acct.type IN ("Savings", "Investment")
RETURN acct.id;
Stocker le type dans la clé de l'élément de nœud pour accélérer les requêtes
Pour accélérer les requêtes avec filtrage sur le type de nœud lorsqu'un type de nœud est modélisé avec une propriété et que le type ne change pas au cours de la durée de vie du nœud, procédez comme suit:
- Incluez la propriété dans la clé de l'élément de nœud.
- Ajoutez le type de nœud dans le tableau d'entrée des arêtes.
- Incluez le type de nœud dans les clés de référence des arêtes.
L'exemple suivant applique cette optimisation au nœud Account
et à l'arête 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
);
Configurer le TTL sur les nœuds et les arêtes
La valeur TTL (Time To Live) de Spanner est un mécanisme qui permet l'expiration et la suppression automatiques des données après un certain temps. Cette méthode est souvent utilisée pour les données dont la durée de vie ou la pertinence sont limitées, comme les informations de session, les caches temporaires ou les journaux d'événements. Dans ce cas, le TTL permet de maintenir la taille et les performances de la base de données.
L'exemple suivant utilise un TTL pour supprimer les comptes 90 jours après leur clôture:
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));
Si la table de nœuds contient une valeur TTL et une table d'arcs entrelacées, l'entrelacement doit être défini avec ON DELETE CASCADE
.
De même, si la table des nœuds comporte un TTL et qu'elle est référencée par une table des arêtes via une clé étrangère, la clé étrangère doit être définie avec ON DELETE CASCADE
.
Dans l'exemple suivant, AccountTransferAccount
est stocké pendant dix ans au maximum tant qu'un compte reste actif. Lorsqu'un compte est supprimé, l'historique des transferts est également supprimé.
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));
Fusionner les tables d'entrée des nœuds et des arêtes
Vous pouvez utiliser la même table d'entrée pour définir plusieurs nœuds et arêtes dans votre schéma.
Dans les exemples de tables suivants, les nœuds Account
ont une clé composite (owner_id, account_id)
. Il existe une définition implicite des arêtes. Le nœud Person
avec la clé (id
) est propriétaire du nœud Account
avec la clé composite (owner_id, account_id)
lorsque id
est égal à 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);
Dans ce cas, vous pouvez utiliser le tableau d'entrée Account
pour définir le nœud Account
et l'arête PersonOwnAccount
, comme illustré dans l'exemple de schéma suivant.
Pour vous assurer que tous les noms de tables d'éléments sont uniques, l'exemple attribue à la définition de la table de bordure 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
);
Étape suivante
- Créer, mettre à jour ou supprimer un schéma de graphique Spanner
- Insérer, mettre à jour ou supprimer des données Spanner Graph