Cómo funciona una consulta de Spanner

Cliente

Spanner admite consultas SQL. Aquí tienes una consulta de ejemplo:

SELECT s.SingerId, s.FirstName, s.LastName, s.SingerInfo
FROM Singers AS s
WHERE s.FirstName = @firstName;

La construcción @firstName es una referencia a un parámetro de consulta. Puedes usar un parámetro de consulta en cualquier lugar donde se pueda usar un valor literal. Se recomienda usar parámetros en las APIs programáticas. El uso de parámetros de consulta ayuda a evitar ataques de inyección de SQL y es más probable que las consultas resultantes se beneficien de varias cachés del lado del servidor. Para obtener más información, consulta Almacenamiento en caché.

Los parámetros de consulta deben estar vinculados a un valor cuando se ejecute la consulta. Por ejemplo:

Statement statement =
    Statement.newBuilder("SELECT s.SingerId...").bind("firstName").to("Jimi").build();
try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
 while (resultSet.next()) {
 ...
 }
}

Cuando Spanner recibe una llamada a la API, analiza la consulta y los parámetros enlazados para determinar qué nodo de servidor de Spanner debe procesar la consulta. El servidor devuelve un flujo de filas de resultados que consumen las llamadas a ResultSet.next().

Ejecución de consultas

La ejecución de la consulta comienza cuando llega una solicitud de ejecución de consulta a algún servidor de Spanner. El servidor sigue estos pasos:

  • Validar la solicitud
  • Analizar el texto de la consulta
  • Generar un álgebra de consultas inicial
  • Generar un álgebra de consultas optimizado
  • Generar un plan de consultas ejecutable
  • Ejecuta el plan (comprueba los permisos, lee los datos, codifica los resultados, etc.).

Diagrama de flujo de ejecución de consultas que muestra los servidores cliente, raíz y hoja

Análisis

El analizador de SQL analiza el texto de la consulta y lo convierte en un árbol de sintaxis abstracta. Extrae la estructura básica de la consulta (SELECT … FROM … WHERE …) y realiza comprobaciones sintácticas.

Álgebra

El sistema de tipos de Spanner puede representar escalares, matrices, estructuras, etc. El álgebra de consultas define operadores para análisis de tablas, filtrado, ordenación o agrupación, todo tipo de combinaciones, agregación y mucho más. El álgebra de consultas inicial se crea a partir de la salida del analizador. Las referencias al nombre de los campos en el árbol de análisis se resuelven mediante el esquema de la base de datos. Este código también comprueba si hay errores semánticos (por ejemplo, un número incorrecto de parámetros, errores de tipo y así sucesivamente).

En el siguiente paso ("optimización de consultas"), se toma el álgebra inicial y se genera un álgebra más óptimo. Puede que sea más sencillo, eficiente o adecuado para las funciones del motor de ejecución. Por ejemplo, el álgebra inicial podría especificar solo una "unión", mientras que el álgebra optimizado especifica una "unión hash".

Ejecución

El plan de consulta ejecutable final se crea a partir del álgebra reescrita. Básicamente, el plan ejecutable es un grafo acíclico dirigido de "iteradores". Cada iterador expone una secuencia de valores. Los iteradores pueden consumir entradas para producir salidas (por ejemplo, el iterador de ordenación). Las consultas que implican una sola división pueden ejecutarse en un solo servidor (el que contiene los datos). El servidor analizará intervalos de varias tablas, ejecutará combinaciones, realizará agregaciones y todas las demás operaciones definidas por el álgebra de consultas.

Las consultas que impliquen varias divisiones se desglosarán en varias partes. Parte de la consulta se seguirá ejecutando en el servidor principal (raíz). Otras subconsultas parciales se transfieren a los nodos hoja (aquellos que tienen las divisiones que se están leyendo). Esta transferencia se puede aplicar de forma recursiva a consultas complejas, lo que da como resultado un árbol de ejecuciones de servidor. Todos los servidores se ponen de acuerdo en una marca de tiempo para que los resultados de las consultas sean una instantánea coherente de los datos. Cada servidor hoja envía una secuencia de resultados parciales. En el caso de las consultas que implican agregación, podrían ser resultados agregados parcialmente. El servidor raíz de la consulta procesa los resultados de los servidores hoja y ejecuta el resto del plan de consulta. Para obtener más información, consulta Planes de ejecución de consultas.

Cuando una consulta implica varias divisiones, Spanner puede ejecutar la consulta en paralelo en todas las divisiones. El grado de paralelismo depende del intervalo de datos que analiza la consulta, del plan de ejecución de la consulta y de la distribución de los datos en las divisiones. Spanner define automáticamente el grado máximo de paralelismo de una consulta en función del tamaño de su instancia y de la configuración de la instancia (regional o multirregional) para conseguir un rendimiento óptimo de la consulta y evitar sobrecargar la CPU.

Almacenamiento en caché

Muchos de los artefactos del procesamiento de consultas se almacenan automáticamente en caché y se reutilizan en consultas posteriores. Esto incluye álgebras de consultas, planes de consultas ejecutables, etc. El almacenamiento en caché se basa en el texto de la consulta, los nombres y los tipos de los parámetros enlazados. Por eso, es mejor usar parámetros enlazados (como @firstName en el ejemplo anterior) que valores literales en el texto de la consulta. El primero se puede almacenar en caché una vez y reutilizarlo independientemente del valor enlazado real. Consulta más información en el artículo Optimizar el rendimiento de las consultas de Spanner.

Gestión de errores

Los métodos executeQuery (o executeStreamingSql) y streamingRead devuelven un flujo de mensajes PartialResultSet. Para mejorar la eficiencia, es posible que un solo valor de fila o columna se divida en varios mensajes PartialResultSet, sobre todo en el caso de datos de gran tamaño.

Esta secuencia se puede interrumpir debido a errores de red transitorios, transferencias divididas o reinicios del servidor. Las transferencias divididas pueden producirse durante el equilibrio de carga y los reinicios del servidor pueden producirse durante las actualizaciones.

Para gestionar estas interrupciones, Spanner incluye cadenas opacas resume_token en algunos mensajes PartialResultSet.

Puntos clave sobre resume_token:

  • No todos los PartialResultSet contienen un resume_token.
  • Un resume_token se suele incluir solo al final de una fila completa para marcar un punto de reanudación seguro.
  • PartialResultSet con un chunked_value (para valores grandes divididos en varios mensajes) no tendrá un resume_token hasta que se envíen el valor y la fila completos.
  • Para reanudar una emisión interrumpida, envía una nueva solicitud con el resume_token no vacío last received.

Las bibliotecas de cliente de Spanner gestionan automáticamente este almacenamiento en búfer y la recuperación. Reúnen filas completas de PartialResultSet mensajes y hacen un seguimiento de los resume_token más recientes. Si se pierde la conexión, la biblioteca usa el último token válido para reiniciar el flujo y descarta los datos parciales recibidos después de ese token. Este proceso asegura que veas un flujo continuo y sin duplicados de filas completas, incluso si se producen fallos transitorios.