En este documento se describen las prácticas recomendadas para diseñar un esquema de gráfico de Spanner, centrándonos en las consultas eficientes, el recorrido de aristas optimizado y las técnicas de gestión de datos eficaces.
Para obtener información sobre el diseño de esquemas de Spanner (no de Spanner Graph), consulta las prácticas recomendadas para el diseño de esquemas.
Elegir un diseño de esquema
El diseño del esquema afecta al rendimiento del gráfico. En los siguientes temas se explica cómo elegir una estrategia eficaz.
Diseños esquematizados y sin esquema
Un diseño esquematizado almacena la definición del gráfico en el esquema de gráfico de Spanner, que es adecuado para gráficos estables con cambios de definición poco frecuentes. El esquema aplica la definición del gráfico y las propiedades admiten todos los tipos de datos de Spanner.
Un diseño sin esquema infiere la definición del gráfico a partir de los datos, lo que ofrece más flexibilidad sin necesidad de cambiar el esquema. Las etiquetas y las propiedades dinámicas no se aplican de forma predeterminada. Las propiedades deben ser valores JSON válidos.
A continuación, se resumen las principales diferencias entre la gestión de datos con esquema y sin esquema. También debes tener en cuenta tus consultas de gráficos para decidir qué tipo de esquema usar.
Función | Gestión de datos esquematizados | Gestión de datos sin esquema |
---|---|---|
Almacenar la definición del gráfico | La definición del gráfico se almacena en el esquema de Spanner Graph. | La definición del gráfico se deduce de los datos. Sin embargo, Spanner Graph no inspecciona los datos para inferir la definición. |
Actualizando la definición del gráfico | Requiere un cambio de esquema de Spanner Graph. Adecuado cuando la definición está bien definida y cambia con poca frecuencia. | No es necesario cambiar el esquema de Spanner Graph. |
Aplicar la definición del gráfico | Un esquema de gráfico de propiedades aplica los tipos de nodo permitidos para una arista. También aplica las propiedades y los tipos de propiedad permitidos de un nodo o un tipo de arista de un gráfico. | No se aplica de forma predeterminada. Puedes usar restricciones de comprobación para asegurar la integridad de los datos de etiquetas y propiedades. |
Tipos de datos de propiedad | Admite cualquier tipo de datos de Spanner, por ejemplo,:
timestamp . |
Las propiedades dinámicas deben ser un valor JSON válido. |
Elegir un diseño de esquema basado en consultas de gráficos
Los diseños esquematizados y sin esquema suelen ofrecer un rendimiento comparable. Sin embargo, cuando las consultas usan patrones de ruta cuantificados que abarcan varios tipos de nodos o aristas, un diseño sin esquema ofrece un mejor rendimiento.
El modelo de datos subyacente es uno de los motivos principales. Un diseño sin esquema almacena todos los datos en tablas de nodos y aristas únicas, lo que DYNAMIC LABEL
aplica.
Las consultas que atraviesan varios tipos se ejecutan con un número mínimo de análisis de tablas.
Por el contrario, los diseños esquematizados suelen usar tablas independientes para cada tipo de nodo y de arista, por lo que las consultas que abarcan varios tipos deben analizar y combinar datos de todas las tablas correspondientes.
A continuación, se muestran consultas de ejemplo que funcionan bien con diseños sin esquema y una consulta de ejemplo que funciona bien con ambos diseños:
Diseño sin esquema
Las siguientes consultas funcionan mejor con un diseño sin esquema porque usan patrones de ruta cuantificados que pueden coincidir con varios tipos de nodos y aristas:
El patrón de ruta cuantificado de esta consulta usa varios tipos de aristas (
Transfer
oWithdraw
) y no especifica tipos de nodos intermedios para rutas de más de un salto.GRAPH FinGraph MATCH p = (:Account {id:1})-[:Transfer|Withdraw]->{1,3}(:Account) RETURN TO_JSON(p) AS p;
El patrón de ruta cuantificado de esta consulta busca rutas de entre uno y tres saltos entre los nodos
Person
yAccount
, usando varios tipos de aristas (Owns
oTransfers
), sin especificar tipos de nodos intermedios para rutas más largas. De esta forma, las rutas pueden atravesar nodos intermedios de varios tipos. Por ejemplo,(:Person)-[:Owns]->(:Account)-[:Transfers]->(:Account)
.GRAPH FinGraph MATCH p = (:Person {id:1})-[:Owns|Transfers]->{1,3}(:Account) RETURN TO_JSON(p) AS p;
El patrón de ruta cuantificado de esta consulta busca rutas de entre uno y tres saltos entre los nodos
Person
yAccount
, sin especificar ninguna etiqueta de arista. Al igual que la consulta anterior, permite que las rutas atraviesen nodos intermedios de varios tipos.GRAPH FinGraph MATCH p = (:Person {id:1})-[]->{1,3}(:Account) RETURN TO_JSON(p) AS p;
Esta consulta busca rutas de entre uno y tres saltos entre nodos
Account
mediante aristas de tipoOwns
en cualquier dirección (-[:Owns]-
). Como las rutas pueden atravesar aristas en cualquier dirección y no se especifican nodos intermedios, una ruta de dos saltos puede pasar por nodos de diferentes tipos. Por ejemplo,(:Account)-[:Owns]-(:Person)-[:Owns]-(:Account)
.GRAPH FinGraph MATCH p = (:Account {id:1})-[:Owns]-{1,3}(:Account) RETURN TO_JSON(p) AS p;
Ambos diseños
La siguiente consulta tiene un rendimiento comparable con los diseños esquematizados y sin esquema. Su ruta cuantificada, (:Account)-[:Transfer]->{1,3}(:Account)
, incluye un tipo de nodo, Account
, y un tipo de arista, Transfer
. Como la ruta solo implica un tipo de nodo y un tipo de arista, el rendimiento es comparable en ambos diseños. Aunque los nodos intermedios no se etiquetan explícitamente, el patrón los restringe a nodos Account
. El nodo Person
aparece
fuera de esta ruta cuantificada.
GRAPH FinGraph
MATCH p = (:Person {id:1})-[:Owns]->(:Account)-[:Transfer]->{1,3}(:Account)
RETURN TO_JSON(p) AS p;
Optimizar el rendimiento del esquema de Spanner Graph
Después de elegir un esquema de Spanner Graph con o sin esquema, puedes optimizar su rendimiento de las siguientes formas:
Optimizar el recorrido de los bordes
El recorrido de aristas es el proceso de desplazarse por un grafo siguiendo sus aristas. Se empieza en un nodo concreto y se avanza 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 del recorrido de aristas puede mejorar significativamente el rendimiento de tu aplicación.
Puedes recorrer un borde en dos direcciones:
- El recorrido por el borde hacia adelante sigue los bordes salientes del nodo de origen.
- Recorrido de aristas inversas: sigue las aristas entrantes del nodo de destino.
Ejemplos de consultas de recorrido de aristas hacia delante y hacia atrás
La siguiente consulta de ejemplo realiza un recorrido por los bordes de Owns
de una persona determinada:
GRAPH FinGraph
MATCH (person:Person {id: 1})-[owns:Owns]->(accnt:Account)
RETURN accnt.id;
La siguiente consulta de ejemplo realiza un recorrido inverso de las aristas Owns
de una cuenta determinada:
GRAPH FinGraph
MATCH (accnt:Account {id: 1})<-[owns:Owns]-(person:Person)
RETURN person.name;
Optimizar el recorrido del borde hacia delante
Para mejorar el rendimiento del recorrido de los bordes hacia delante, optimiza el recorrido desde el origen hasta el borde y desde el borde hasta el destino.
Para optimizar el recorrido de la fuente al borde, intercala la tabla de entrada del borde en la tabla de entrada del nodo de origen mediante la cláusula
INTERLEAVE IN PARENT
. El entrelazado es una técnica de optimización del almacenamiento en Spanner que coloca 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.Para optimizar el recorrido de los bordes a los destinos, crea una restricción de clave externa entre el borde y el nodo de destino.
De esta forma, se aplica la restricción de origen a destino, lo que puede mejorar el rendimiento al eliminar los análisis de la tabla de destino. Si las claves externas obligatorias provocan cuellos de botella en el rendimiento de escritura (por ejemplo, al actualizar nodos de concentrador), utilice una clave externa informativa.
En los siguientes ejemplos se muestra cómo usar el entrelazado con una restricción de clave externa obligatoria y otra informativa.
Clave externa obligatoria
En este ejemplo de tabla de extremos, PersonOwnAccount
hace lo siguiente:
Se entrelaza en la tabla de nodos de origen
Person
.Crea una clave externa obligatoria en la tabla de nodos de destino
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;
Clave externa informativa
En este ejemplo de tabla de extremos, PersonOwnAccount
hace lo siguiente:
Se entrelaza en la tabla de nodos de origen
Person
.Crea una clave externa informativa en la tabla de nodos de destino.
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;
Optimizar el recorrido de aristas inversas
Optimiza el recorrido de aristas inversas, a menos que tus consultas solo usen el recorrido hacia delante, ya que las consultas que implican recorridos inversos o bidireccionales son habituales.
Para optimizar el recorrido por el borde inverso, puede hacer lo siguiente:
Crea un índice secundario en la tabla de aristas.
Entrelaza el índice en la tabla de entrada del nodo de destino para colocar las aristas junto a los nodos de destino.
Almacena las propiedades de los bordes en el índice.
En este ejemplo se muestra un índice secundario para optimizar el recorrido de los bordes inversos de la tabla de bordes PersonOwnAccount
:
La cláusula
INTERLEAVE IN
coloca los datos de índice junto a la tabla de nodos de destinoAccount
.La cláusula
STORING
almacena las propiedades de los bordes en el índice.
Para obtener más información sobre los índices intercalados, consulta Índices e intercalación.
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;
Usar índices secundarios para filtrar propiedades
Un índice secundario permite buscar nodos y aristas de forma eficiente en función de valores de propiedad específicos. Usar un índice ayuda a evitar un análisis de tabla completo y es especialmente útil en gráficos grandes.
Acelerar el filtrado de nodos por propiedad
La siguiente consulta busca cuentas con un apodo específico. Como no usa un índice secundario, se deben analizar todos los nodos Account
para encontrar los resultados coincidentes:
GRAPH FinGraph
MATCH (acct:Account)
WHERE acct.nick_name = "abcd"
RETURN acct.id;
Crea un índice secundario en la propiedad filtrada de tu esquema para acelerar el proceso de filtrado:
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);
Acelerar el filtrado de aristas por propiedad
Puedes usar un índice secundario para mejorar el rendimiento de los filtros de aristas en función de los valores de las propiedades.
Recorrido por el borde hacia delante
Sin un índice secundario, esta consulta debe analizar todos los bordes de una persona para encontrar los que coincidan con el 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;
El siguiente código mejora 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
). La consulta también define el índice como un elemento secundario intercalado de la tabla de entrada del nodo de origen, lo que coloca el índice 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;
Recorrido de aristas inverso
Sin un índice secundario, la siguiente consulta de recorrido de aristas inversas debe leer todas las aristas antes de poder encontrar a la persona propietaria de la cuenta especificada después del create_time
especificado:
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;
El siguiente código mejora la eficiencia de las consultas creando un índice secundario en la referencia del nodo de destino de la arista (account_id
) y en la propiedad de la arista (create_time
). La consulta también define el índice como elemento secundario intercalado de la tabla de nodos de destino, lo que coloca el índice junto al nodo de destino.
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;
Evitar bordes colgantes
Una arista que conecta cero o un nodo, una arista colgante, puede comprometer la eficiencia de las consultas de Spanner Graph y la integridad de la estructura del gráfico. Se puede producir un borde colgante si eliminas un nodo sin eliminar sus bordes asociados. También puede ocurrir que haya un borde aislado si creas un borde, pero no existe el nodo de origen o de destino. Para evitar aristas colgantes, incorpora lo siguiente en tu esquema de gráfico de Spanner:
- Usa restricciones referenciales.
- Opcional: Usa la cláusula
ON DELETE CASCADE
cuando elimines un nodo con aristas que aún estén conectadas. Si no usasON DELETE CASCADE
, no podrás eliminar un nodo sin eliminar las aristas correspondientes.
Usar restricciones referenciales
Puedes usar el entrelazado y las claves externas obligatorias en ambos endpoints para evitar aristas huérfanas. Para ello, sigue estos pasos:
Entrelaza la tabla de entrada de aristas en la tabla de entrada de nodos de origen para asegurarte 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. Aunque las claves externas obligatorias evitan los bordes colgantes, hacen que insertar y eliminar bordes sea más costoso.
En el siguiente ejemplo se usa una clave externa obligatoria y se intercalan los datos de la tabla de entrada de aristas en la tabla de entrada del nodo de origen mediante la cláusula INTERLEAVE IN PARENT
. Si se usan una clave externa obligatoria y el entrelazado, también se puede optimizar el recorrido de los bordes hacia delante.
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;
Eliminar aristas con ON DELETE CASCADE
Cuando uses la intercalación o una clave externa obligatoria para evitar aristas huérfanas, usa la cláusula ON DELETE CASCADE
en tu esquema de gráfico de Spanner para eliminar las aristas asociadas de un nodo en la misma transacción que elimina el nodo.
Para obtener más información, consulta los artículos Eliminación en cascada de tablas intercaladas y Acciones de clave externa.
Eliminación en cascada de las aristas que conectan diferentes tipos de nodos
En los siguientes ejemplos se muestra cómo usar ON DELETE CASCADE
en tu esquema de Spanner Graph para eliminar las aristas huérfanas cuando eliminas un nodo de origen o de destino. En ambos casos, el tipo del nodo eliminado y el tipo del nodo conectado a él por una arista son diferentes.
Nodo de origen
Usa la intercalación para eliminar los bordes colgantes cuando se elimine el nodo de origen. A continuación, se muestra cómo usar el entrelazado para eliminar las aristas salientes cuando se elimina el nodo de origen (Person
). Para obtener más información, consulta Crear tablas intercaladas.
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 de destino
Usa una restricción de clave externa para eliminar las aristas huérfanas cuando se elimine el nodo de destino. En el siguiente ejemplo se muestra cómo usar una clave externa con ON
DELETE CASCADE
en una tabla de aristas para eliminar las aristas entrantes cuando se elimina el nodo de destino (Account
):
CONSTRAINT FK_Account FOREIGN KEY(account_id)
REFERENCES Account(id) ON DELETE CASCADE
Eliminación en cascada de las aristas que conectan nodos del mismo tipo
Cuando los nodos de origen y destino de un borde son del mismo tipo y el borde está intercalado en el nodo de origen, puedes definir ON DELETE CASCADE
para el nodo de origen o el de destino, pero no para ambos.
Para evitar que queden bordes sueltos en estos casos, no intercales datos en la tabla de entrada del nodo de origen. En su lugar, crea dos claves externas obligatorias en las referencias de nodos de origen y de destino.
En el siguiente ejemplo se usa AccountTransferAccount
como tabla de entrada de borde. Define dos claves externas, una en cada nodo de extremo de la arista de transferencia, ambas con la acción 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);
Configurar el tiempo de vida (TTL) en nodos y aristas
TTL te permite caducar y eliminar datos después de un periodo especificado. Puedes usar TTL en tu esquema para mantener el tamaño y el rendimiento de la base de datos eliminando los datos que tengan una vida útil o una relevancia limitadas. Por ejemplo, puedes configurarlo para que elimine información de sesiones, cachés temporales o registros de eventos.
En el siguiente ejemplo, se usa el TTL para eliminar las 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));
Cuando defines una política de TTL en una tabla de nodos, debes configurar cómo se gestionan los bordes relacionados para evitar que queden bordes colgantes de forma accidental:
En el caso de las tablas de aristas intercaladas: si una tabla de aristas está intercalada en la tabla de nodos, puedes definir la relación de intercalación con
ON DELETE CASCADE
. De esta forma, cuando TTL elimina un nodo, también se eliminan sus aristas intercaladas asociadas.En el caso de las tablas de aristas con claves externas: si una tabla de aristas hace referencia a la tabla de nodos con una clave externa, tienes dos opciones:
- Para eliminar automáticamente las aristas cuando el nodo al que se hace referencia se elimine por el TTL, usa
ON DELETE CASCADE
en la clave externa. De esta forma, se mantiene la integridad referencial. - Para permitir que los bordes permanezcan después de que se elimine el nodo al que hacen referencia (creando un borde colgante), define la clave externa como una clave externa informativa.
- Para eliminar automáticamente las aristas cuando el nodo al que se hace referencia se elimine por el TTL, usa
En el siguiente ejemplo, la tabla de aristas AccountTransferAccount
está sujeta a dos políticas de eliminación de datos:
- Una política de TTL elimina los registros de transferencias que tienen más de diez años.
- La cláusula
ON DELETE CASCADE
elimina todos los registros de transferencias asociados a una fuente cuando se elimina esa cuenta.
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
Para optimizar tu esquema, define un nodo y sus aristas entrantes o salientes en una sola tabla. Este enfoque ofrece las siguientes ventajas:
Menos tablas: se reduce el número de tablas del esquema, lo que simplifica la gestión de datos.
Rendimiento de las consultas mejorado: elimina el recorrido que usa combinaciones en una tabla de aristas independiente.
Esta técnica funciona bien cuando la clave principal de una tabla también define una relación con otra tabla. Por ejemplo, si la tabla Account
tiene una clave principal compuesta (owner_id, account_id)
, la parte owner_id
puede ser una clave externa que haga referencia a la tabla Person
. Esta estructura permite que la tabla Account
represente tanto el nodo Account
como la arista entrante del 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);
Puedes usar la tabla Account
para definir tanto el nodo Account
como su arista Owns
entrante. Esto se muestra en la siguiente CREATE PROPERTY GRAPH
declaración. En la cláusula EDGE TABLES
, asignas el alias Owns
a la tabla Account
. Esto se debe a que cada elemento del esquema de gráfico debe tener un nombre único.
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.