Estadísticas de división

En esta página, se describe cómo detectar y depurar hotspots en tu base de datos. Puedes acceder a las estadísticas sobre los hotspots en las divisiones con GoogleSQL y PostgreSQL

Spanner almacena tus datos como un espacio de claves contiguo, ordenado por las claves primarias de tus índices y tablas. Una división es un rango de filas de un conjunto de tablas o un índice. El inicio de la división se denomina inicio de la división. El límite de división establece el final de la división. La división incluye el inicio de la división, pero no el límite de división.

En Spanner, los hotspots son situaciones en las que se envían demasiadas solicitudes al mismo servidor, lo que satura los recursos del servidor y puede causar latencias altas. Las divisiones afectadas por hotspots se conocen como divisiones activas o activas.

La estadística de hotspot de una división (identificada en el sistema como CPU_USAGE_SCORE) es una medición de la carga en una división que está restringida por los recursos disponibles en el servidor. Esta medición se da como porcentaje. Si más del 50% de la carga en una división está restringida por recursos disponibles, la división se considera templada. Si el 100% de la carga en una división está restringida, esta se considera caliente.

Spanner usa la división basada en la carga para distribuir de manera uniforme la carga de datos entre los servidores de la instancia. Las divisiones tibias y calientes se pueden mover entre servidores para el balanceo de cargas o se pueden dividir en divisiones más pequeñas. Sin embargo, es posible que Spanner no pueda equilibrar la carga, incluso después de varios intentos de división, debido a los antipatrones en la aplicación. Por lo tanto, los hotspots persistentes que duran al menos 10 minutos pueden necesitar la solución de problemas y los posibles cambios en la aplicación.

Las estadísticas de división caliente de Spanner te ayudan a identificar las divisiones en las que se producen los hotspots. Luego, puedes realizar cambios en tu aplicación o esquema según sea necesario. Puedes recuperar estas estadísticas desde Tablas del sistema SPANNER_SYS.SPLIT_STATS_TOP_MINUTE con instrucciones de SQL.

Disponibilidad de estadísticas de división en caliente

Spanner proporciona las estadísticas de división en caliente en el esquema SPANNER_SYS. Los datos de SPANNER_SYS solo están disponibles a través de las interfaces de Google SQL y PostgreSQL. Tú puedes usar las siguientes formas para acceder a estos datos:

Las APIs de lectura única de Spanner no son compatibles con SPANNER_SYS.

Estadísticas sobre las divisiones activas

Usa la siguiente tabla para hacer un seguimiento de las divisiones activas:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE: Muestra las divisiones que están activas. en intervalos de 1 minuto.

Estas tablas tienen las siguientes propiedades:

  • Cada tabla contiene datos de intervalos de tiempo que no se superponen de la duración que se especifica en el nombre de la tabla.
  • Los intervalos se basan en la hora del reloj:

    • Los intervalos de 1 minuto finalizan en el minuto.
  • Después de cada intervalo, Spanner recopila datos de todos los servidores y, luego, los pone a disposición en las tablas SPANNER_SYS poco después.

    Por ejemplo, a las 11:59:30 a.m., los intervalos más recientes disponibles para las consultas de SQL son los siguientes:

    • 1 minuto: de 11:58:00 a 11:58:59 a.m.
  • Spanner agrupa las estadísticas por divisiones.

  • Cada fila contiene un porcentaje que indica qué tan caliente o caliente es una división, por para cada división para la que Spanner captura estadísticas durante en un intervalo especificado.

  • Si menos del 50% de la carga en una división está limitada por la política recursos, Spanner no captura las estadísticas. Si Spanner no puede almacenar todas las divisiones activas durante el intervalo, el sistema prioriza las divisiones con el porcentaje de CPU_USAGE_SCORE más alto durante el intervalo especificado. Si hay no se muestran divisiones, es una indicación de la ausencia de hotspots.

Esquema de la tabla

En la siguiente tabla, se muestra el esquema de la tabla para las siguientes estadísticas:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE
Nombre de la columna Tipo Descripción
INTERVAL_END TIMESTAMP Final del intervalo de tiempo en el cual la división estaba activa
SPLIT_START STRING Clave inicial del rango de filas en la división. El inicio de la división también puede ser <begin>, lo que indica el inicio del espacio de claves.
SPLIT_LIMIT STRING Es la clave de límite para el rango de filas en la división. El límite: también puede ser <end>, lo que indica el final del espacio de claves|
CPU_USAGE_SCORE INT64 El porcentaje de CPU_USAGE_SCORE de las divisiones. R El porcentaje de CPU_USAGE_SCORE del 50% indica la presencia de calor o calor. | divisiones |
AFFECTED_TABLES STRING ARRAY Son las tablas cuyas filas podrían estar en la división.

Divide las claves de inicio y límite de división

Una división es un rango de filas contiguo de una base de datos y se define por su valor de inicio y limit. Una división puede ser una sola fila, un rango de filas estrecho o un rango de filas amplio, y la división puede incluir varias tablas o índices.

Las columnas SPLIT_START y SPLIT_LIMIT identifican las claves primarias de una tibia o en caliente.

Esquema de ejemplo

El siguiente esquema es una tabla de ejemplo para los temas de esta página.

GoogleSQL

CREATE TABLE Users (
  UserId INT64 NOT NULL,
  FirstName STRING(MAX),
  LastName STRING(MAX),
) PRIMARY KEY(UserId);

CREATE INDEX UsersByFirstName ON Users(FirstName DESC);

CREATE TABLE Threads (
  UserId INT64 NOT NULL,
  ThreadId INT64 NOT NULL,
  Starred BOOL,
) PRIMARY KEY(UserId, ThreadId),
  INTERLEAVE IN PARENT Users ON DELETE CASCADE;

CREATE TABLE Messages (
  UserId INT64 NOT NULL,
  ThreadId INT64 NOT NULL,
  MessageId INT64 NOT NULL,
  Subject STRING(MAX),
  Body STRING(MAX),
) PRIMARY KEY(UserId, ThreadId, MessageId),
  INTERLEAVE IN PARENT Threads ON DELETE CASCADE;

CREATE INDEX MessagesIdx ON Messages(UserId, ThreadId, Subject),
INTERLEAVE IN Threads;

PostgreSQL

CREATE TABLE users
(
   userid    BIGINT NOT NULL PRIMARY KEY,-- INT64 to BIGINT
   firstname VARCHAR(max),-- STRING(MAX) to VARCHAR(MAX)
   lastname  VARCHAR(max)
);

CREATE INDEX usersbyfirstname
  ON users(firstname DESC);

CREATE TABLE threads
  (
    userid   BIGINT NOT NULL,
    threadid BIGINT NOT NULL,
    starred  BOOLEAN, -- BOOL to BOOLEAN
    PRIMARY KEY (userid, threadid),
    CONSTRAINT fk_threads_user FOREIGN KEY (userid) REFERENCES users(userid) ON
    DELETE CASCADE -- Interleave to Foreign Key constraint
  );

CREATE TABLE messages
  (
    userid    BIGINT NOT NULL,
    threadid  BIGINT NOT NULL,
    messageid BIGINT NOT NULL PRIMARY KEY,
    subject   VARCHAR(max),
    body      VARCHAR(max),
    CONSTRAINT fk_messages_thread FOREIGN KEY (userid, threadid) REFERENCES
    threads(userid, threadid) ON DELETE CASCADE
  -- Interleave to Foreign Key constraint
  );

CREATE INDEX messagesidx ON messages(userid, threadid, subject), REFERENCES
threads(userid, threadid);

Imagina que tu espacio de claves se ve de la siguiente manera:

CLAVE PRIMARIA
<begin>
Users()
Threads()
Users(2)
Users(3)
Threads(3)
Threads(3,"a")
Messages(3,"a",1)
Messages(3,"a",2)
Threads(3, "aa")
Users(9)
Users(10)
Threads(10)
UsersByFirstName("abc")
UsersByFirstName("abcd")
<end>

Ejemplo de divisiones

A continuación, se muestran algunos ejemplos de divisiones para ayudarte a comprender cómo se ven.

SPLIT_START y SPLIT_LIMIT pueden indicar la fila de una tabla o un índice, o pueden ser <begin> y <end>, que representan los límites del espacio de claves de la base de datos. SPLIT_START y SPLIT_LIMIT también pueden contener las claves truncadas, que son las que preceden a cualquier clave completa de la tabla. Por ejemplo: Threads(10) es un prefijo para cualquier fila de Threads intercalada en Users(10).

SPLIT_START SPLIT_LIMIT AFFECTED_TABLES EXPLICACIÓN
Users(3) Users(10) UsersByFirstName, Users, Threads, Messages, MessagesIdx La división comienza en la fila con UserId=3 y termina en la fila anterior a la fila con UserId = 10. La división contiene las filas de la tabla Users y todas las filas de sus tablas intercaladas de UserId=3 a 10.
Messages(3,"a",1) Threads(3,"aa") Threads, Messages, MessagesIdx La división comienza en la fila con UserId=3, ThreadId="a" y MessageId=1, y finaliza en la fila que precede a la fila con la clave de UserId=3 y ThreadsId = "aa". La división contiene todas las tablas entre Messages(3,"a",1) y Threads(3,"aa"). Como split_start y split_limit se intercalan en la misma fila de la tabla de nivel superior, la división contiene las filas de las tablas intercaladas entre el inicio y el límite. Consulta schemas-overview para comprender cómo se ubican las tablas intercaladas.
Messages(3,"a",1) <end> UsersByFirstName, Users, Threads, Messages, MessagesIdx La división comienza en la tabla de mensajes en la fila con las claves UserId=3, ThreadId="a" y MessageId=1. La división aloja todas las filas desde split_start hasta <end>, el final del espacio de claves de la base de datos. Todas las filas de las tablas que siguen a split_start, como Users(4), se incluyen en la división.
<begin> Users(9) UsersByFirstName, Users, Threads, Messages, MessagesIdx La división comienza en <begin>, el inicio del espacio de claves de la base de datos, y finaliza en la fila anterior a la fila Users con UserId=9. Por lo tanto, la división tiene todas las filas de la tabla que preceden a Users y todas las filas de la tabla Users que preceden a UserId=9 y las filas de sus tablas intercaladas.
Messages(3,"a",1) Threads(10) UsersByFirstName, Users, Threads, Messages, MessagesIdx La división comienza en Messages(3,"a", 1) intercalado en Users(3) y finaliza en la fila que antecede a Threads(10). Threads(10) es una clave de división truncada que es un prefijo de cualquier clave de la tabla Threads intercalada en Users(10).
Users() <end> UsersByFirstName, Users, Threads, Messages, MessagesIdx La división comienza en la clave de división truncada de Users(), que precede a cualquier clave completa de la tabla Users. La división se extiende hasta el final del espacio de claves posible en la base de datos. Por lo tanto, affected_tables abarca la tabla Users, sus tablas y índices intercalados, y todas las tablas que puedan aparecer después de los usuarios.
Threads(10) UsersByFirstName("abc") UsersByFirstName, Users, Threads, Messages, MessagesIdx La división comienza en la fila Threads con UserId = 10 y termina en el índice, UsersByFirstName, en la clave que precede a "abc".

Consultas de ejemplo para encontrar divisiones populares

En el siguiente ejemplo, se muestra una sentencia SQL que puedes usar para recuperar las estadísticas de división en caliente. Puedes ejecutar estas instrucciones de SQL con las bibliotecas cliente, gcloud o la consola de Google Cloud.

GoogleSQL

SELECT t.split_start,
       t.split_limit,
       t.cpu_usage_score,
       t.affected_tables,
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.interval_end =
  (SELECT MAX(interval_end)
  FROM    SPANNER_SYS.SPLIT_STATS_TOP_MINUTE)
ORDER BY  t.cpu_usage_score DESC;

PostgreSQL

SELECT t.split_start,
       t.split_limit,
       t.cpu_usage_score,
       t.affected_tables
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.interval_end = (
  SELECT MAX(interval_end)
  FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE
)
ORDER BY t.cpu_usage_score DESC;

El resultado de la consulta se ve de la siguiente manera:

SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE AFFECTED_TABLES
Users(13) Users(76) 82 Messages,Users,Threads
Users(101) Users(102) 90 Messages,Users,Threads
Threads(10, "a") Threads(10, "aa") 100 Messages,Threads
Messages(631, "abc", 1) Messages(631, "abc", 3) 100 Messages
Threads(12, "zebra") Users(14) 76 Messages,Users,Threads
Users(620) <end> 100 Messages,Users,Threads

Retención de datos para las estadísticas de división en caliente

Como mínimo, Spanner conserva los datos de cada tabla para lo siguiente: período:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE: Intervalos que cubren en las últimas 6 horas.

Soluciona problemas de hotspots con estadísticas de división en caliente

En esta sección, se describe cómo detectar hotspots y solucionar problemas relacionados.

Selecciona un período para investigar

Verifica las métricas de latencia de tu base de datos de Spanner para encontrar el período en el que tu aplicación experimentó una latencia alta y un uso alto de CPU. Por ejemplo, podría mostrarte que un comenzó alrededor de las 10:50 p.m. del 18 de mayo de 2024.

Cómo encontrar hotspots persistentes

A medida que Spanner balancea la carga con la división basada en la carga, te recomendamos que investigues si la generación de hotspots continuó durante más de 10 minutos. Para ello, consulta la tabla SPANNER_SYS.SPLIT_STATS_TOP_MINUTE, como se muestra en el siguiente ejemplo:

GoogleSQL

SELECT Count(DISTINCT t.interval_end)
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.utilization >= 50
  AND  t.interval_end >= "interval_end_date_time"
  AND  t.interval_end <= "interval_end_date_time";

Reemplaza interval_end_date_time por la fecha y hora de la de servicio con el formato 2024-05-18T17:40:00Z.

PostgreSQL

SELECT COUNT(DISTINCT t.interval_end)
FROM   SPLIT_STATS_TOP_MINUTE t
WHERE  t.utilization >= 50
  AND  t.interval_end >= 'interval_end_date_time'::timestamptz
  AND  t.interval_end <= 'interval_end_date_time'::timestamptz;

Reemplaza interval_end_date_time por la fecha y hora de la de servicio con el formato 2024-05-18T17:40:00Z.

Si el resultado de la consulta anterior es igual a 10, significa que tu base de datos está la generación de hotspots que podría necesitar más depuración.

Busca las divisiones con el nivel de CPU_USAGE_SCORE más alto

Para este ejemplo, ejecutamos el siguiente SQL para encontrar los rangos de filas con el nivel más alto de CPU_USAGE_SCORE:

GoogleSQL

SELECT t.split_start,
       t.split_limit,
       t.affected_tables,
       t.cpu_usage_score
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.cpu_usage_score >= 50
  AND  t.interval_end = "interval_end_date_time";

Reemplaza interval_end_date_time por la fecha y hora de la de servicio con el formato 2024-05-18T17:40:00Z.

PostgreSQL

SELECT t.split_start,
       t.split_limit,
       t.affected_tables,
       t.cpu_usage_score
FROM   SPLIT_STATS_TOP_MINUTE t
WHERE  t.cpu_usage_score = 100
  AND  t.interval_end = 'interval_end_date_time'::timestamptz;

Reemplaza interval_end_date_time por la fecha y hora de la de servicio con el formato 2024-05-18T17:40:00Z.

La sentencia SQL anterior muestra lo siguiente:

SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE AFFECTED_TABLES
Users(180) <end> 85 Messages,Users,Threads
Users(24) Users(76) 76 Messages,Users,Threads

En esta tabla de resultados, podemos ver que los hotspots ocurrieron en dos divisiones. La división basada en la carga de Spanner podría intentar resolver los hotspots en estas divisiones. Sin embargo, es posible que no pueda hacerlo si hay patrones problemáticos en el esquema o la carga de trabajo. Para detectar si hay divisiones que necesitan tu intervención, te recomendamos hacer un seguimiento de las divisiones durante, al menos, 10 minutos. Por ejemplo, la siguiente sentencia SQL realiza un seguimiento de la primera división en los últimos diez minutos.

GoogleSQL

SELECT t.interval_end,
       t.split_start,
       t.split_limit,
       t.cpu_usage_score
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.split_start = "users(180)"
  AND  t.split_limit = "<end>"
  AND  t.interval_end >= "interval_end_date_time"
  AND  t.interval_end <= "interval_end_date_time";

Reemplaza interval_end_date_time por la fecha y hora de la de servicio con el formato 2024-05-18T17:40:00Z.

PostgreSQL

SELECT t.interval_end,
       t.split_start,
       t.split_limit,
       t.cpu_usage_score
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.split_start = 'users(180)'
  AND  t.split_limit = ''
  AND  t.interval_end >= 'interval_end_date_time'::timestamptz
  AND  t.interval_end <= 'interval_end_date_time'::timestamptz;

Reemplaza interval_end_date_time por la fecha y hora de la de servicio con el formato 2024-05-18T17:40:00Z.

La sentencia SQL anterior muestra lo siguiente:

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-18T17:46:00Z Users(180) <end> 85
2024-05-18T17:47:00Z Users(180) <end> 85
2024-05-18T17:48:00Z Users(180) <end> 85
2024-05-18T17:49:00Z Users(180) <end> 85
2024-05-18T17:50:00Z Users(180) <end> 85

Parece que la división estuvo caliente durante los últimos minutos. Podrías observar la dividir por más tiempo para determinar que el servidor basado en cargas de Spanner dividir, mitiga el hotspot. Puede haber casos en los que Spanner no pueda realizar más balanceo de cargas.

Por ejemplo, consulta la tabla SPANNER_SYS.SPLIT_STATS_TOP_MINUTE. Consulta las siguientes situaciones de ejemplo.

GoogleSQL

SELECT t.interval_end,
      t.split_start,
      t.split_limit,
      t.cpu_usage_score
FROM  SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE t.interval_end >= "interval_end_date_time"
      AND t.interval_end <= "interval_end_date_time";

Reemplaza interval_end_date_time por la fecha y hora de la de servicio con el formato 2024-05-18T17:40:00Z.

PostgreSQL

SELECT t.interval_end,
       t.split_start,
       t.split_limit,
       t._cpu_usage
FROM   SPANNER_SYS.SPLIT_STATS_TOP_MINUTE t
WHERE  t.interval_end >= 'interval_end_date_time'::timestamptz
  AND  t.interval_end <= 'interval_end_date_time'::timestamptz;

Reemplaza interval_end_date_time por la fecha y hora de la de servicio con el formato 2024-05-18T17:40:00Z.

Fila activa única

En el siguiente ejemplo, parece que Threads(10,"spanner") está en una división de fila única que permaneció activa durante más de 10 minutos. Esto puede suceder cuando hay una carga persistente en una fila popular.

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-16T20:40:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:41:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:42:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:43:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:44:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:45:00Z Threads(10,"spanner") Threads(10,"spanner1") 62
2024-05-16T20:46:00Z Threads(10,"spanner") Threads(10,"spanner1") 80
2024-05-16T20:47:00Z Threads(10,"spanner") Threads(10,"spanner1") 80
2024-05-16T20:48:00Z Threads(10,"spanner") Threads(10,"spanner1") 80
2024-05-16T20:49:00Z Threads(10,"spanner") Threads(10,"spanner1") 100
2024-05-16T20:50:00Z Threads(10,"spanner") Threads(10,"spanner1") 100

Spanner no puede equilibrar la carga de esta clave única, ya que no se puede dividir más.

Hotspot móvil

En el siguiente ejemplo, la carga se mueve a través de divisiones contiguas con el tiempo. se mueve a una nueva división en intervalos de tiempo.

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-16T20:40:00Z Threads(1,"a") Threads(1,"aa") 100
2024-05-16T20:41:00Z Threads(1,"aa") Threads(1,"ab") 100
2024-05-16T20:42:00Z Threads(1,"ab") Threads(1,"c") 100
2024-05-16T20:43:00Z Threads(1,"c") Threads(1,"ca") 100

Esto podría ocurrir, por ejemplo, debido a una carga de trabajo que lee o escribe claves en orden monótonamente creciente. Spanner no puede equilibrar la carga para mitigar los efectos de este comportamiento de la aplicación.

Balanceo de cargas normal

Spanner intenta equilibrar la carga agregando más divisiones o moviéndolas. En el siguiente ejemplo, se muestra cómo podría ser.

INTERVAL_END SPLIT_START SPLIT_LIMIT CPU_USAGE_SCORE
2024-05-16T20:40:00Z Threads(1000,"zebra") <end> 82
2024-05-16T20:41:00Z Threads(1000,"zebra") <end> 90
2024-05-16T20:42:00Z Threads(1000,"zebra") <end> 100
2024-05-16T20:43:00Z Threads(1000,"zebra") Threads(2000,"spanner") 100
2024-05-16T20:44:00Z Threads(1200,"c") Threads(2000) 92
2024-05-16T20:45:00Z Threads(1500,"c") Threads(1700,"zach") 76
2024-05-16T20:46:00Z Threads(1700) Threads(1700,"c") 76
2024-05-16T20:47:00Z Threads(1700) Threads(1700,"c") 50
2024-05-16T20:48:00Z Threads(1700) Threads(1700,"c") 39

Aquí, la división más grande en 2024-05-16T17:40:00Z se dividió aún más en una dividida y, como resultado, la estadística CPU_USAGE_SCORE disminuyó. Es posible que Spanner no cree divisiones en filas individuales. Los splits duplicar la carga de trabajo que causa la estadística alta de CPU_USAGE_SCORE.

Si observaste una división caliente persistente durante más de 10 minutos, consulta Prácticas recomendadas para mitigar los hotspots.

Prácticas recomendadas para mitigar los hotspots

Si el balanceo de cargas no disminuye la latencia, el siguiente paso es identificar la causa principal de los hotspots. Después de eso, las opciones son reducir la carga de trabajo de los hotspots o optimizar el esquema y la lógica de la aplicación para evitarlos.

Identifica la causa

  • Usa Bloquear y Estadísticas de transacciones para buscar transacciones con un alto rendimiento tiempo de espera de bloqueo en el que la clave de inicio del rango de filas se encuentra dentro de la división en caliente.

  • Usa las estadísticas de consultas para buscar consultas que lean de la tabla que contiene la división activa y que hayan aumentado recientemente la latencia o tengan una proporción más alta de latencia a la CPU.

  • Utiliza las Consultas activas más antiguas para buscar las consultas que se leen de la tabla. que contenga la división en caliente y que tengan una latencia más alta de la esperada.

Estos son algunos casos especiales a los que debes prestar atención:

  • Verifica si el tiempo de actividad (TTL) se habilitó recientemente. Si hay muchas divisiones de datos antiguos, el TTL puede aumentar los niveles de CPU_USAGE_SCORE durante las eliminaciones masivas. En este caso, el problema debería resolverse por cuenta propia una vez se completen las eliminaciones iniciales.

Optimiza la carga de trabajo

¿Qué sigue?