En este documento, se describe cómo crear consultas eficientes con las prácticas recomendadas para diseñar esquemas de Spanner Graph. Puedes iterar en el diseño de tu esquema, por lo que te recomendamos que primero identifiques los patrones de consultas críticos para guiar el diseño de tu esquema.
Para obtener información general sobre las prácticas recomendadas de diseño de esquemas de Spanner, consulta Prácticas recomendadas de diseño de esquemas.
Optimiza el recorrido de los bordes
El recorrido de aristas es el proceso de navegar por un grafo siguiendo sus aristas, comenzando en un nodo en particular y moviéndose a lo largo de las aristas conectadas para llegar a otros nodos. La dirección de la arista se define en el esquema. El recorrido de bordes es una operación fundamental en Spanner Graph, por lo que mejorar la eficiencia del recorrido de bordes es clave para el rendimiento de tu aplicación.
Puedes recorrer un borde en dos direcciones:
Recorrido de aristas hacia adelante: Sigue las aristas salientes del nodo fuente.
Recorrido de borde inverso: Sigue los bordes entrantes del nodo de destino.
Dada una persona, la siguiente consulta de ejemplo realiza el recorrido de borde hacia adelante de los bordes Owns
:
GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;
Dada una cuenta, la siguiente consulta de ejemplo realiza el recorrido inverso de los bordes Owns
:
GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;
Optimiza el recorrido de borde hacia adelante con la intercalación
Para mejorar el rendimiento del recorrido de bordes hacia adelante, intercala la tabla de entrada de bordes en la tabla de entrada de nodos fuente para ubicar los bordes junto con los nodos fuente. La intercalación es una técnica de optimización del almacenamiento en Spanner que coloca físicamente las filas de la tabla secundaria con sus filas principales correspondientes en el almacenamiento. Para obtener más información sobre la intercalación, consulta Descripción general de los esquemas.
En el siguiente ejemplo, se muestran estas prácticas recomendadas:
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;
Optimiza el recorrido de bordes inversos con la clave externa
Para recorrer los bordes inversos de manera eficiente, crea una restricción de clave externa aplicada de forma forzosa entre el borde y el nodo de destino. Esta clave externa aplicada crea un índice secundario en el borde indexado por las claves del nodo de destino. El índice secundario se usa automáticamente durante la ejecución de la consulta.
En el siguiente ejemplo, se muestran estas prácticas recomendadas:
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;
Optimiza el recorrido de bordes inversos con un índice secundario
Si no deseas crear una clave externa obligatoria en el borde, por ejemplo, debido a la estricta integridad de los datos que impone, puedes crear directamente un índice secundario en la tabla de entrada del borde, como se muestra en el siguiente ejemplo:
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
declara una relación de localidad de datos entre el índice secundario y la tabla en la que se intercala (Account
, en el ejemplo). Con la intercalación, las filas del índice secundario AccountOwnedByPerson
se ubican en el mismo lugar que las filas correspondientes de la tabla Account
. Para obtener más información sobre la intercalación, consulta Relaciones entre tablas principales y secundarias. Para obtener más información sobre los índices intercalados, consulta Índices y entrelazado.
Optimiza el recorrido de borde con claves externas informativas
Si tu situación tiene cuellos de botella en el rendimiento de escritura causados por claves externas obligatorias, como cuando tienes actualizaciones frecuentes en nodos centrales que tienen muchas aristas conectadas, considera usar claves externas informativas. El uso de claves externas informativas en las columnas de referencia de una tabla de borde ayuda al optimizador de consultas a descartar los análisis redundantes de la tabla de nodos. Sin embargo, como las claves externas informativas no requieren índices secundarios en la tabla de borde, no mejoran la velocidad de las búsquedas cuando una consulta intenta encontrar bordes usando nodos finales. Para obtener más información, consulta Comparación de clave externa externas.
Es importante comprender que, si tu aplicación no puede garantizar la integridad referencial, el uso de claves externas informativas para la optimización de consultas podría generar resultados incorrectos.
En el siguiente ejemplo, se crea una tabla con una clave externa informativa en la columna 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;
Si la intercalación no es una opción, puedes marcar ambas referencias de borde con claves externas informativas, como en el siguiente ejemplo:
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);
No permitir bordes colgantes
Un borde colgante es un borde que conecta menos de dos nodos. Un borde aislado puede ocurrir cuando se borra un nodo sin quitar sus bordes asociados o cuando se crea un borde sin vincularlo correctamente a sus nodos.
No permitir bordes colgantes proporciona los siguientes beneficios:
- Aplica la integridad de la estructura del gráfico.
- Mejora el rendimiento de las consultas, ya que evita el trabajo adicional de filtrar las aristas en las que no existen extremos.
Cómo no permitir bordes colgantes con restricciones referenciales
Para no permitir bordes colgantes, especifica restricciones en ambos extremos:
- Intercala la tabla de entrada de aristas en la tabla de entrada de nodos fuente. Este enfoque garantiza que el nodo fuente de una arista siempre exista.
- Crea una restricción de clave externa aplicada en los bordes para garantizar que siempre exista el nodo de destino de un borde.
En el siguiente ejemplo, se usan la intercalación y una clave externa obligatoria para aplicar la integridad referencial:
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 ON DELETE CASCADE para quitar automáticamente las aristas cuando borres un nodo
Cuando usas la intercalación o una clave externa obligatoria para no permitir bordes colgantes, usa la cláusula ON DELETE
para controlar el comportamiento cuando quieras borrar un nodo con bordes que aún estén conectados. Para obtener más información, consulta Eliminación en cascada para tablas intercaladas y Acciones de claves externas.
Puedes usar ON DELETE
de las siguientes maneras:
ON DELETE NO ACTION
(o se omite la cláusulaON DELETE
): No se podrá borrar un nodo con aristas.ON DELETE CASCADE
: Cuando se borra un nodo, se quitan automáticamente las aristas asociadas en la misma transacción.
Eliminación en cascada para las aristas que conectan diferentes tipos de nodos
Borra las aristas cuando se borra el nodo fuente. Por ejemplo,
INTERLEAVE IN PARENT Person ON DELETE CASCADE
borra todas las aristas salientesPersonOwnAccount
del nodoPerson
que se borra. Para obtener más información, consulta Crea tablas intercaladas.Borra los bordes cuando se borra el nodo de destino. Por ejemplo,
CONSTRAINT FK_Account FOREIGN KEY(account_id) REFERENCES Account(id) ON DELETE CASCADE
borra todas las aristasPersonOwnAccount
entrantes en el nodoAccount
que se borra.
Eliminación en cascada para las aristas que conectan el mismo tipo de nodos
Cuando los nodos de origen y destino de una arista tienen el mismo tipo y la arista se entrelaza en el nodo de origen, puedes definir ON DELETE CASCADE
solo para el nodo de origen o el nodo de destino (pero no para ambos nodos).
Para quitar los bordes colgantes en ambos casos, crea una clave externa obligatoria en la referencia del nodo fuente del borde en lugar de intercalar la tabla de entrada del borde en la tabla de entrada del nodo fuente.
Recomendamos la intercalación para optimizar el recorrido de bordes hacia adelante.
Asegúrate de verificar el impacto en tus cargas de trabajo antes de continuar. Consulta el siguiente ejemplo, que usa AccountTransferAccount
como la tabla de entrada de borde:
--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);
Cómo filtrar por propiedades de nodos o aristas con índices secundarios
Los índices secundarios son fundamentales para el procesamiento eficiente de las consultas. Admiten búsquedas rápidas de nodos y aristas basadas en valores de propiedad específicos, sin tener que recorrer toda la estructura del gráfico. Esto es importante cuando trabajas con grafos grandes, ya que recorrer todos los nodos y aristas puede ser muy ineficiente.
Acelera el filtrado de nodos por propiedad
Para acelerar el filtrado por propiedades de nodos, crea índices secundarios en las propiedades. Por ejemplo, la siguiente consulta busca cuentas para un apodo determinado. Sin un índice secundario, se analizan todos los nodos Account
para que coincidan con los criterios de filtrado.
GRAPH FinGraph
MATCH (acct:Account)
WHERE acct.nick_name = "abcd"
RETURN acct.id;
Para acelerar la consulta, crea un índice secundario en la propiedad filtrada, como se muestra en el siguiente ejemplo:
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);
Nota: Usa índices filtrados por NULL para las propiedades dispersas. Para obtener más información, consulta Cómo inhabilitar la indexación de valores NULL.
Acelera el recorrido del borde delantero con el filtrado de las propiedades del borde
Cuando recorres un borde mientras filtras sus propiedades, puedes acelerar la consulta creando un índice secundario en las propiedades del borde y entrelazando el índice en el nodo de origen.
Por ejemplo, la siguiente consulta busca las cuentas que pertenecen a una persona determinada después de un momento específico:
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;
De forma predeterminada, esta consulta lee todas las aristas de la persona especificada y, luego, filtra las aristas que satisfacen la condición en create_time
.
En el siguiente ejemplo, se muestra cómo mejorar la eficiencia de las consultas creando un índice secundario en la referencia del nodo fuente de la arista (id
) y la propiedad de la arista (create_time
). Intercala el índice en la tabla de entrada del nodo fuente para colocar el índice junto al nodo fuente.
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 este enfoque, la consulta puede encontrar de manera eficiente todas las aristas que satisfacen la condición en create_time
.
Acelera el recorrido inverso de los bordes con el filtrado de propiedades de los bordes
Cuando recorres un borde inverso mientras filtras sus propiedades, puedes acelerar la consulta creando un índice secundario con el nodo de destino y las propiedades del borde para el filtrado.
En el siguiente ejemplo de consulta, se realiza un recorrido de borde inverso con filtrado en las propiedades del borde:
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;
Para acelerar esta consulta con un índice secundario, usa una de las siguientes opciones:
Crea un índice secundario en la referencia del nodo de destino del borde (
account_id
) y la propiedad del borde (create_time
), como se muestra en el siguiente ejemplo: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);
Este enfoque proporciona un mejor rendimiento porque los bordes inversos se ordenan por
account_id
ycreate_time
, lo que permite que el motor de búsqueda encuentre de manera eficiente los bordes paraaccount_id
que satisfacen la condición encreate_time
. Sin embargo, si diferentes patrones de búsqueda filtran diferentes propiedades, cada propiedad podría requerir un índice independiente, lo que puede agregar una sobrecarga.Crea un índice secundario en la referencia del nodo de destino del borde (
account_id
) y almacena la propiedad del borde (create_time
) en una columna de almacenamiento, como se muestra en el siguiente ejemplo: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);
Este enfoque puede almacenar varias propiedades. Sin embargo, la consulta debe leer todas las aristas del nodo de destino y, luego, filtrar las propiedades de las aristas.
Puedes combinar estos enfoques siguiendo estos lineamientos:
- Usa propiedades de borde en las columnas de índice si se usan en consultas críticas para el rendimiento.
- Para las propiedades que se usan en consultas menos sensibles al rendimiento, agrégalas en las columnas de almacenamiento.
Modelar tipos de nodos y bordes con etiquetas y propiedades
Los tipos de nodos y aristas se suelen modelar con etiquetas. Sin embargo, también puedes usar propiedades para modelar tipos. Considera un ejemplo en el que hay muchos tipos diferentes de cuentas, como BankAccount
, InvestmentAccount
y RetirementAccount
. Puedes almacenar las cuentas en tablas de entrada separadas y modelarlas como etiquetas separadas, o bien puedes almacenarlas en una sola tabla de entrada y usar una propiedad para diferenciar los tipos.
Comienza el proceso de modelado modelando los tipos con etiquetas. Considera usar propiedades en las siguientes situaciones.
Mejora la administración del esquema
Si tu gráfico tiene muchos tipos diferentes de nodos y aristas, puede ser difícil administrar una tabla de entrada independiente para cada uno. Para facilitar la administración del esquema, modela el tipo como una propiedad.
Tipos de modelos en una propiedad para administrar los tipos que cambian con frecuencia
Cuando modelas tipos como etiquetas, agregar o quitar tipos requiere cambios en el esquema. Si realizas demasiadas actualizaciones del esquema en un período breve, es posible que Spanner limite el procesamiento de las actualizaciones del esquema en cola. Para obtener más información, consulta Limita la frecuencia de las actualizaciones del esquema.
Si necesitas cambiar el esquema con frecuencia, te recomendamos que modeles el tipo en una propiedad para evitar las limitaciones en la frecuencia de las actualizaciones del esquema.
Acelera las consultas
Modelar tipos con propiedades puede acelerar las consultas cuando el patrón de nodos o bordes hace referencia a varias etiquetas. En el siguiente ejemplo de consulta, se encuentran todas las instancias de SavingsAccount
y InvestmentAccount
que pertenecen a un Person
, suponiendo que los tipos de cuentas se modelan con etiquetas:
GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:SavingsAccount|InvestmentAccount)
RETURN acct.id;
El patrón de nodo acct
hace referencia a dos etiquetas. Si se trata de una consulta fundamental para el rendimiento, considera modelar Account
con una propiedad. Este enfoque puede proporcionar un mejor rendimiento de las consultas, como se muestra en el siguiente ejemplo de consulta. Te recomendamos que compares ambas consultas.
GRAPH FinGraph
MATCH (:Person {id: 1})-[:Owns]->(acct:Account)
WHERE acct.type IN ("Savings", "Investment")
RETURN acct.id;
Almacena el tipo en la clave del elemento del nodo para acelerar las consultas
Para acelerar las consultas con filtrado en el tipo de nodo cuando un tipo de nodo se modela con una propiedad y el tipo no cambia durante la vida útil del nodo, sigue estos pasos:
- Incluye la propiedad como parte de la clave del elemento del nodo.
- Agrega el tipo de nodo en la tabla de entrada de aristas.
- Incluye el tipo de nodo en las claves de referencia de borde.
En el siguiente ejemplo, se aplica esta optimización al nodo Account
y a la arista 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 el TTL en nodos y aristas
El tiempo de actividad (TTL) de Spanner es un mecanismo que admite el vencimiento y la eliminación automáticos de datos después de un período especificado. Esto se suele usar para los datos que tienen una vida útil o relevancia limitadas, como la información de la sesión, las cachés temporales o los registros de eventos. En estos casos, el TTL ayuda a mantener el tamaño y el rendimiento de la base de datos.
En el siguiente ejemplo, se usa el TTL para borrar cuentas 90 días después de su cierre:
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 tabla de nodos tiene un TTL y una tabla de borde intercalada dentro de ella, la intercalación se debe definir con ON DELETE CASCADE
. Del mismo modo, si la tabla de nodos tiene un TTL y una tabla de aristas hace referencia a ella a través de una clave externa, la clave externa debe definirse con ON DELETE CASCADE
para mantener la integridad referencial o definirse como una clave externa informativa para permitir la existencia de aristas pendientes.
En el siguiente ejemplo, AccountTransferAccount
se almacena durante un máximo de diez años mientras una cuenta permanece activa. Cuando se borra una cuenta, también se borra el historial de transferencias.
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));
Cómo combinar tablas de entrada de nodos y aristas
Puedes usar la misma tabla de entrada para definir más de un nodo y borde en tu esquema.
En las siguientes tablas de ejemplo, los nodos Account
tienen una clave compuesta (owner_id, account_id)
. Hay una definición de borde implícita: el nodo Person
con la clave (id
) posee el nodo Account
con la clave compuesta (owner_id, account_id)
cuando id
es igual 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);
En este caso, puedes usar la tabla de entrada Account
para definir el nodo Account
y la arista PersonOwnAccount
, como se muestra en el siguiente ejemplo de esquema.
Para garantizar que todos los nombres de las tablas de elementos sean únicos, el ejemplo le asigna el alias Owns
a la definición de la tabla de borde.
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
);
¿Qué sigue?
- Crear, actualizar o descartar un esquema de Spanner Graph
- Insertar, actualizar o borrar datos del gráfico de Spanner