En este documento se describe cómo crear consultas eficientes siguiendo 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 consulta críticos para guiar el diseño de tu esquema.
Para obtener información general sobre las prácticas recomendadas para el diseño de esquemas de Spanner, consulta Prácticas recomendadas para el diseño de esquemas.
Optimizar el recorrido de los bordes
El recorrido de aristas es el proceso de desplazarse por un grafo siguiendo sus aristas, empezando por un nodo concreto y moviéndose por las aristas conectadas para llegar a otros nodos. El esquema define la dirección del borde. El recorrido de aristas es una operación fundamental en Spanner Graph, por lo que mejorar la eficiencia de este recorrido 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 de origen.
Recorrido de aristas inversas: sigue las aristas entrantes del nodo de destino.
Dada una persona, la siguiente consulta de ejemplo realiza un recorrido de aristas hacia delante de las aristas Owns
:
GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;
Dada una cuenta, la siguiente consulta de ejemplo realiza un recorrido de aristas inverso de las aristas Owns
:
GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;
Optimizar el recorrido del borde hacia delante mediante la intercalación
Para mejorar el rendimiento del recorrido de los bordes hacia delante, intercala la tabla de entrada de los bordes en la tabla de entrada de los nodos de origen para colocar los bordes junto a los nodos de origen. El entrelazado es una técnica de optimización del almacenamiento de Spanner que coloca físicamente las filas de una tabla secundaria junto con las filas de su tabla principal correspondiente en el almacenamiento. Para obtener más información sobre el entrelazado, consulta la 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;
Optimizar el recorrido de aristas inversas mediante claves externas
Para recorrer los bordes inversos de forma eficiente, crea una restricción de clave externa obligatoria entre el borde y el nodo de destino. Esta clave externa obligatoria 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;
Optimizar el recorrido de aristas inversas mediante un índice secundario
Si no quieres crear una clave externa obligatoria en el borde (por ejemplo, debido a la estricta integridad de los datos que aplica), puedes crear directamente un índice secundario en la tabla de entrada de 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 ubicación de datos entre el índice secundario y la tabla en la que está intercalado (Account
en el ejemplo). Con el entrelazado, las filas del índice secundario AccountOwnedByPerson
se colocan junto a las filas correspondientes de la tabla Account
. Para obtener más información sobre el entrelazado, consulta Relaciones entre tablas principales y secundarias. Para obtener más información sobre los índices intercalados, consulta Índices e intercalación.
Optimizar el recorrido de aristas con claves externas informativas
Si tu situación tiene cuellos de botella de rendimiento de escritura causados por claves externas obligatorias, como cuando tienes actualizaciones frecuentes en nodos de concentrador que tienen muchas aristas conectadas, considera la posibilidad de usar claves externas informativas. Usar claves externas informativas en las columnas de referencia de una tabla de aristas ayuda al optimizador de consultas a eliminar los análisis de tablas de nodos redundantes. Sin embargo, como las claves externas informativas no requieren índices secundarios en la tabla de aristas, no mejoran la velocidad de las búsquedas cuando una consulta intenta encontrar aristas mediante nodos finales. Para obtener más información, consulta la comparación de los tipos de claves externas.
Es importante tener en cuenta que, si tu aplicación no puede garantizar la integridad referencial, usar claves externas informativas para optimizar las consultas puede dar lugar a 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 no puedes usar el entrelazado, 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
Una arista colgante es una arista que conecta menos de dos nodos. Se puede producir un borde colgante cuando se elimina un nodo sin quitar sus bordes asociados o cuando se crea un borde sin vincularlo correctamente a sus nodos.
No permitir los bordes colgantes ofrece las siguientes ventajas:
- Implementa la integridad de la estructura del gráfico.
- Mejora el rendimiento de las consultas al evitar el trabajo adicional de filtrar las aristas donde no existen los extremos.
No permitir bordes colgantes mediante restricciones referenciales
Para no permitir aristas colgantes, especifica restricciones en ambos extremos:
- Entrelaza la tabla de entrada de los bordes en la tabla de entrada del nodo de origen. De esta forma, se asegura de que el nodo de origen de una arista siempre exista.
- Crea una restricción de clave externa obligatoria en los bordes para asegurarte de que el nodo de destino de un borde siempre exista.
En el siguiente ejemplo se usa 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 eliminar automáticamente las aristas al eliminar un nodo
Cuando usas la intercalación o una clave externa obligatoria para no permitir los bordes colgantes, usa la cláusula ON DELETE
para controlar el comportamiento cuando quieras eliminar un nodo con bordes que aún estén conectados. Para obtener más información, consulta los artículos Eliminación en cascada de tablas intercaladas y Acciones de clave externa.
Puedes usar ON DELETE
de las siguientes formas:
ON DELETE NO ACTION
(u omitir la cláusulaON DELETE
): no se podrá eliminar un nodo con aristas.ON DELETE CASCADE
: Si eliminas un nodo, se eliminarán automáticamente las aristas asociadas en la misma transacción.
Eliminación en cascada de las aristas que conectan diferentes tipos de nodos
Elimina las aristas cuando se elimina el nodo de origen. Por ejemplo,
INTERLEAVE IN PARENT Person ON DELETE CASCADE
elimina todas las aristas salientesPersonOwnAccount
del nodoPerson
que se va a eliminar. Para obtener más información, consulta el artículo Crear tablas intercaladas.Elimina las aristas cuando se elimina el nodo de destino. Por ejemplo,
CONSTRAINT FK_Account FOREIGN KEY(account_id) REFERENCES Account(id) ON DELETE CASCADE
elimina todas las aristasPersonOwnAccount
entrantes en el nodoAccount
que se va a eliminar.
Eliminación en cascada de las aristas que conectan nodos del mismo tipo
Cuando los nodos de origen y destino de un borde tienen el mismo tipo y el borde se entrelaza en el nodo de origen, puede definir ON DELETE CASCADE
solo para el nodo de origen o el nodo de destino (pero no para ambos nodos).
Para eliminar las aristas colgantes en ambos casos, crea una clave externa obligatoria en la referencia del nodo de origen de la arista en lugar de intercalar la tabla de entrada de la arista en la tabla de entrada del nodo de origen.
Recomendamos intercalar para optimizar el recorrido de los bordes hacia delante.
Asegúrate de verificar el impacto en tus cargas de trabajo antes de continuar. Consulta el siguiente ejemplo, que usa AccountTransferAccount
como 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);
Filtrar por propiedades de nodos o aristas con índices secundarios
Los índices secundarios son esenciales para procesar las consultas de forma eficiente. Admiten búsquedas rápidas de nodos y aristas basadas en valores de propiedades específicos, sin tener que recorrer toda la estructura del gráfico. Esto es importante cuando trabajas con gráficos grandes, ya que recorrer todos los nodos y aristas puede ser muy ineficiente.
Acelerar el filtrado de nodos por propiedad
Para acelerar el filtrado por propiedades de nodo, crea índices secundarios en las propiedades. Por ejemplo, la siguiente consulta busca cuentas con un determinado nombre. 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 propiedades dispersas. Para obtener más información, consulta Inhabilitar la indexación de valores NULL.
Acelerar el recorrido de los bordes hacia delante filtrando las propiedades de los bordes
Cuando recorre un borde mientras filtra por sus propiedades, puede acelerar la consulta creando un índice secundario en las propiedades del borde e intercalando el índice en el nodo de origen.
Por ejemplo, la siguiente consulta busca cuentas que pertenecen a una persona determinada después de un momento concreto:
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, a continuación, filtra las aristas que cumplen la condición de 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 de origen de la arista (id
) y en la propiedad de la arista (create_time
). Intercala el índice en la tabla de entrada del nodo de origen para colocarlo junto al nodo de origen.
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 forma eficiente todas las aristas que cumplan la condición de create_time
.
Acelerar el recorrido de aristas inversas filtrando por propiedades de aristas
Cuando recorres un borde inverso mientras filtras por sus propiedades, puedes acelerar la consulta creando un índice secundario con el nodo de destino y las propiedades del borde para filtrar.
La siguiente consulta de ejemplo realiza un recorrido de aristas inverso con filtrado en las propiedades de las aristas:
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 mediante un índice secundario, usa una de las siguientes opciones:
Crea un índice secundario en la referencia del nodo de destino de la arista (
account_id
) y en la propiedad de la arista (create_time
), tal 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 ofrece un mejor rendimiento porque las aristas inversas se ordenan por
account_id
ycreate_time
, lo que permite que el motor de consultas encuentre de forma eficiente las aristas deaccount_id
que cumplen la condición decreate_time
. Sin embargo, si los distintos patrones de consulta filtran propiedades diferentes, cada propiedad puede requerir un índice independiente, lo que puede añadir sobrecarga.Crea un índice secundario en la referencia del nodo de destino de arista (
account_id
) y almacena la propiedad de arista (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);
Con este enfoque se pueden almacenar varias propiedades, pero la consulta debe leer todos los bordes del nodo de destino y, a continuación, filtrar por las propiedades de los bordes.
Puedes combinar estos enfoques siguiendo estas directrices:
- Usa propiedades de arista en las columnas de índice si se utilizan en consultas críticas para el rendimiento.
- En el caso de las propiedades que se usen en consultas que no requieran un rendimiento tan alto, añádelas a las columnas de almacenamiento.
Tipos de nodos y aristas de modelos 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. Por ejemplo, supongamos que hay muchos tipos de cuentas diferentes, como BankAccount
, InvestmentAccount
y RetirementAccount
. Puedes almacenar las cuentas en tablas de entrada independientes y modelarlas como etiquetas independientes, o bien puedes almacenarlas en una sola tabla de entrada y usar una propiedad para diferenciar los tipos.
Empieza el proceso de modelado modelando los tipos con etiquetas. Considera la posibilidad de usar propiedades en los siguientes casos.
Mejorar la gestión de esquemas
Si tu gráfico tiene muchos tipos de nodos y aristas diferentes, puede resultar difícil gestionar una tabla de entrada independiente para cada uno. Para facilitar la gestión del esquema, modela el tipo como una propiedad.
Tipos de modelo de una propiedad para gestionar los tipos que cambian con frecuencia
Cuando modelas tipos como etiquetas, añadir o quitar tipos requiere cambios en el esquema. Si realizas demasiadas actualizaciones de esquemas en un breve periodo, es posible que Spanner limite el procesamiento de las actualizaciones de esquemas en cola. Para obtener más información, consulta Limitar la frecuencia de las actualizaciones de esquemas.
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.
Acelerar las consultas
La modelización de tipos con propiedades puede acelerar las consultas cuando el patrón de nodo o de arista hace referencia a varias etiquetas. La siguiente consulta de ejemplo busca todas las instancias de SavingsAccount
y InvestmentAccount
propiedad de un Person
, suponiendo que los tipos de cuenta 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 crítica para el rendimiento, plantéate modelizar Account
mediante 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 de nodo para acelerar las consultas
Para acelerar las consultas con filtros por tipo de nodo cuando se modela un tipo de nodo con una propiedad y el tipo no cambia durante el ciclo de vida del nodo, sigue estos pasos:
- Incluye la propiedad como parte de la clave del elemento de nodo.
- Añade el tipo de nodo en la tabla de entrada de aristas.
- Incluye el tipo de nodo en las claves de referencia de los bordes.
En el ejemplo siguiente 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
);
Configurar el TTL en nodos y aristas
El tiempo de vida (TTL) de Spanner es un mecanismo que permite que los datos caduquen y se eliminen automáticamente después de un periodo especificado. Se suele usar para datos que tienen una vida útil o una relevancia limitadas, como información de sesiones, cachés temporales o 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 TTL para eliminar 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 aristas intercalada, la intercalación
debe definirse con ON DELETE CASCADE
. Del mismo modo, si la tabla de nodos tiene un TTL y una tabla de aristas hace referencia a ella mediante una clave externa, la clave externa debe definirse con ON DELETE CASCADE
para mantener la integridad referencial o como una clave externa informativa para permitir la existencia de aristas huérfanas.
En el ejemplo siguiente, AccountTransferAccount
se almacena durante un máximo de diez años mientras una cuenta permanece activa. Cuando se elimina una cuenta, también se elimina 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));
Combinar tablas de entrada de nodos y aristas
Puedes usar la misma tabla de entrada para definir más de un nodo y un 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 arista implícita: el nodo Person
con la clave (id
) es propietario del 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, puede usar la tabla de entrada Account
para definir el nodo Account
y el borde PersonOwnAccount
, como se muestra en el siguiente ejemplo de esquema.
Para asegurarse de que todos los nombres de las tablas de elementos sean únicos, en el ejemplo se asigna el alias Owns
a la definición de la tabla de aristas.
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
);
Siguientes pasos
- Crea, actualiza o elimina un esquema de gráfico de Spanner.
- Insertar, actualizar o eliminar datos de Spanner Graph.