Dividir estatísticas

Nesta página, descrevemos como detectar e depurar hotspots no seu banco de dados. É possível acessar estatísticas sobre hotspots em divisões com GoogleSQL e PostgreSQL.

O Spanner armazena seus dados como um espaço de chaves contíguo, ordenado pelas chaves primárias das tabelas e índices. Uma divisão é um intervalo de linhas de um conjunto de tabelas ou um índice. O início da divisão é chamado de início da divisão. O limite de divisão define o fim da divisão. A divisão inclui o início, mas não o limite.

No Spanner, pontos de acesso são situações em que muitas solicitações são enviadas para o mesmo servidor, o que satura os recursos dele e pode causar latências altas. As divisões afetadas por pontos de acesso são conhecidas como divisões ativas ou quentes.

A estatística de hotspot de uma divisão (identificada no sistema como CPU_USAGE_SCORE) é uma medição da carga em uma divisão que é limitada pelos recursos disponíveis no servidor. Essa medição é fornecida como uma porcentagem. Se mais de 50% da carga em uma divisão for limitada pelos recursos disponíveis, ela será considerada ativa. Se 100% da carga em uma divisão for restrita, ela será considerada ativa.

O Spanner usa a divisão baseada em carga para distribuir uniformemente a carga de dados entre os servidores da instância. As divisões quentes e mornas podem ser movidas entre servidores para balanceamento de carga ou divididas em partes menores. No entanto, o Spanner pode não conseguir balancear a carga, mesmo depois de várias tentativas de divisão, devido a antipatterns no aplicativo. Portanto, hotspots persistentes que duram pelo menos 10 minutos podem precisar de mais solução de problemas e possíveis mudanças no aplicativo.

As estatísticas de divisão ativa do Spanner ajudam a identificar as divisões em que ocorrem pontos de acesso. Depois, faça as mudanças necessárias no aplicativo ou esquema. É possível recuperar essas estatísticas das tabelas do sistema SPANNER_SYS.SPLIT_STATS_TOP_MINUTE usando instruções SQL.

Acessar estatísticas de divisão frequente

O Spanner fornece as estatísticas de divisão ativa no esquema SPANNER_SYS. Os dados do SPANNER_SYS estão disponíveis somente por meio das interfaces do GoogleSQL e do PostgreSQL. Você pode acessar esses dados das seguintes maneiras:

Os seguintes métodos de leitura única fornecidos pelo Spanner não são compatíveis com SPANNER_SYS:

  • realizar uma leitura forte de uma única linha ou de várias linhas em uma tabela;
  • realizar uma leitura desatualizada de uma única linha ou várias linhas em uma tabela;
  • ler uma única linha ou várias linhas em um índice secundário.

Estatísticas de divisões ativas

Use a tabela a seguir para rastrear divisões ativas:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE: mostra divisões que estão ativas durante intervalos de um minuto.

Essas tabelas têm as seguintes propriedades:

  • Cada uma contém dados para intervalos de tempo não sobrepostos da duração que o nome da tabela especifica.
  • Os intervalos são baseados em horários:

    • Os intervalos de um minuto terminam no minuto.
  • Após cada intervalo, o Spanner coleta dados de todos os servidores e disponibiliza os dados nas tabelas SPANNER_SYS pouco tempo depois.

    Por exemplo, às 11:59:30, os intervalos mais recentes disponíveis para as consultas SQL são:

    • 1 minuto: das 11h58min00s às 11h58min59s
  • O Spanner agrupa as estatísticas por divisões.

  • Cada linha contém uma porcentagem que indica o nível de atividade de uma divisão, para cada divisão em que o Spanner captura estatísticas durante o intervalo especificado.

  • Se menos de 50% da carga em uma divisão for limitada pelos recursos disponíveis, o Spanner não vai capturar a estatística. Se o Spanner não puder armazenar todas as divisões ativas durante o intervalo, o sistema vai priorizar as divisões com a maior porcentagem de CPU_USAGE_SCORE durante o intervalo especificado. Se não houver divisões retornadas, isso indica a ausência de pontos de acesso.

Esquema de tabela

A tabela a seguir mostra o esquema para estas estatísticas:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE
Nome da coluna Tipo Descrição
INTERVAL_END TIMESTAMP Fim do intervalo de tempo em que a divisão estava ativa.
SPLIT_START STRING A chave inicial do intervalo de linhas na divisão. O início da divisão também pode ser <begin>, indicando o início do espaço de chaves.
SPLIT_LIMIT STRING A chave de limite do intervalo de linhas na divisão. A chave de limite também pode ser <end>, indicando o fim do espaço de chaves.
CPU_USAGE_SCORE INT64 A porcentagem de CPU_USAGE_SCORE das divisões. Uma porcentagem de CPU_USAGE_SCORE de 50% indica a presença de divisões | quentes ou ativas |
AFFECTED_TABLES STRING ARRAY As tabelas com linhas que podem estar na divisão

Chaves de início e limite de divisão

Uma divisão é um intervalo de linhas contíguas de um banco de dados, definido pelas chaves de início e limite. Uma divisão pode ser uma única linha, um intervalo de linhas estreito ou amplo, e pode incluir várias tabelas ou índices.

As colunas SPLIT_START e SPLIT_LIMIT identificam as chaves primárias de uma divisão quente ou fria.

Esquema de exemplo

O esquema a seguir é uma tabela de exemplo para os tópicos desta 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);

Imagine que seu espaço de chaves seja assim:

PRIMARY KEY
<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>

Exemplo de divisões

Confira a seguir alguns exemplos de divisões para ajudar você a entender como elas são.

Os valores SPLIT_START e SPLIT_LIMIT podem indicar a linha de uma tabela ou índice, ou podem ser <begin> e <end>, representando os limites do espaço de chaves do banco de dados. Os SPLIT_START e SPLIT_LIMIT também podem conter chaves truncadas, que são chaves que precedem qualquer chave completa na tabela. Por exemplo, Threads(10) é um prefixo para qualquer linha Threads intercalada em Users(10).

SPLIT_START SPLIT_LIMIT AFFECTED_TABLES EXPLICAÇÃO
Users(3) Users(10) UsersByFirstName, Users, Threads, Messages, MessagesIdx A divisão começa na linha com UserId=3 e termina na linha anterior à linha com UserId = 10. A divisão contém as linhas da tabela Users e todas as linhas das tabelas intercaladas de UserId=3 a 10.
Messages(3,"a",1) Threads(3,"aa") Threads, Messages, MessagesIdx A divisão começa na linha com UserId=3, ThreadId="a" e MessageId=1 e termina na linha anterior à linha com a chave de UserId=3 e ThreadsId = "aa". A divisão contém todas as tabelas entre Messages(3,"a",1) e Threads(3,"aa"). Como split_start e split_limit são intercalados na mesma linha da tabela de nível superior, a divisão contém as linhas das tabelas intercaladas entre o início e o limite. Consulte schemas-overview para entender como as tabelas intercaladas são colocadas em conjunto.
Messages(3,"a",1) <end> UsersByFirstName, Users, Threads, Messages, MessagesIdx A divisão começa na tabela de mensagens na linha com a chave UserId=3, ThreadId="a" e MessageId=1. A divisão hospeda todas as linhas de split_start a <end>, o fim do espaço de chaves do banco de dados. Todas as linhas das tabelas após o split_start, como Users(4), estão incluídas na divisão.
<begin> Users(9) UsersByFirstName, Users, Threads, Messages, MessagesIdx A divisão começa em <begin>, o início do espaço de chaves do banco de dados, e termina na linha anterior à linha Users com UserId=9. Assim, a divisão tem todas as linhas da tabela anteriores a Users e todas as linhas da tabela Users anteriores a UserId=9 e as linhas das tabelas intercaladas.
Messages(3,"a",1) Threads(10) UsersByFirstName, Users, Threads, Messages, MessagesIdx A divisão começa em Messages(3,"a", 1) intercalada em Users(3) e termina na linha anterior a Threads(10). Threads(10) é uma chave de divisão truncada que é um prefixo de qualquer chave da tabela "Threads" intercalada em Users(10).
Users() <end> UsersByFirstName, Users, Threads, Messages, MessagesIdx A divisão começa na chave de divisão truncada de Users(), que precede qualquer chave completa da tabela Users. A divisão se estende até o fim do possível espaço de chaves no banco de dados. As affected_tables, portanto, abrangem a tabela Users, as tabelas e os índices intercalados e todas as tabelas que podem aparecer depois dos usuários.
Threads(10) UsersByFirstName("abc") UsersByFirstName, Users, Threads, Messages, MessagesIdx A divisão começa na linha Threads com UserId = 10 e termina no índice UsersByFirstName na chave anterior a "abc".

Exemplos de consultas para encontrar divisões importantes

O exemplo a seguir mostra uma instrução SQL que pode ser usada para recuperar as estatísticas de divisão dinâmica. É possível executar essas instruções SQL usando as bibliotecas de cliente, a gcloud ou o console 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;

A saída da consulta é semelhante a esta:

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

Retenção de dados para as estatísticas de divisão frequente

O Spanner mantém dados para cada tabela, no mínimo, pelos períodos a seguir:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE: intervalos que abrangem as seis horas anteriores.

Resolver problemas de pontos de acesso usando estatísticas de divisão ativa

Esta seção descreve como detectar e solucionar problemas de hotspots.

Selecionar um período para investigar

Verifique as métricas de latência do banco de dados do Spanner para encontrar o período em que o aplicativo apresentou alta latência e uso da CPU. Por exemplo, ele pode mostrar que um problema começou por volta das 22h50 de 18 de maio de 2024.

Encontrar pontos de acesso persistentes

Como o Spanner equilibra sua carga com a divisão baseada em carga, recomendamos que você investigue se o hotspotting continua por mais de 10 minutos. Para isso, consulte a tabela SPANNER_SYS.SPLIT_STATS_TOP_MINUTE, conforme mostrado no exemplo a seguir:

GoogleSQL

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

Substitua interval_end_date_time pela data e hora do intervalo, usando o formato 2024-05-18T17:40:00Z.

PostgreSQL

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

Substitua interval_end_date_time pela data e hora do intervalo, usando o formato 2024-05-18T17:40:00Z.

Se o resultado da consulta anterior for igual a 10, isso significa que seu banco de dados está passando por hotspotting, o que pode exigir mais depuração.

Encontre as divisões com o maior nível de CPU_USAGE_SCORE

Neste exemplo, executamos o seguinte SQL para encontrar os intervalos de linhas com o nível mais 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";

Substitua interval_end_date_time pela data e hora do intervalo, usando o 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;

Substitua interval_end_date_time pela data e hora do intervalo, usando o formato 2024-05-18T17:40:00Z.

O SQL anterior gera o seguinte:

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

Nessa tabela de resultados, podemos ver que os pontos de concentração ocorreram em duas divisões. A divisão baseada em carga do Spanner pode tentar resolver pontos de acesso nessas divisões. No entanto, isso não será possível se houver padrões problemáticos no esquema ou na carga de trabalho. Para detectar se há divisões que precisam da sua intervenção, recomendamos acompanhar as divisões por pelo menos 10 minutos. Por exemplo, o SQL a seguir rastreia a primeira divisão nos últimos 10 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";

Substitua interval_end_date_time pela data e hora do intervalo, usando o 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;

Substitua interval_end_date_time pela data e hora do intervalo, usando o formato 2024-05-18T17:40:00Z.

O SQL anterior gera o seguinte:

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

A divisão parece ter sido disputada nos últimos minutos. Você pode observar a divisão por mais tempo para determinar que a divisão baseada em carga do Spanner reduz o hotspot. Em alguns casos, o Spanner não consegue fazer mais balanceamento de carga.

Por exemplo, consulte a tabela SPANNER_SYS.SPLIT_STATS_TOP_MINUTE. Confira os exemplos de cenários a seguir.

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";

Substitua interval_end_date_time pela data e hora do intervalo, usando o 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;

Substitua interval_end_date_time pela data e hora do intervalo, usando o formato 2024-05-18T17:40:00Z.

Linha quente única

No exemplo a seguir, parece que Threads(10,"spanner") está em uma divisão de linha única que permaneceu ativa por mais de 10 minutos. Isso pode acontecer quando há uma carga persistente em uma linha 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

O Spanner não pode balancear a carga dessa chave única porque ela não pode ser dividida mais.

Ponto de acesso móvel

No exemplo a seguir, a carga se move por divisões contíguas ao longo do tempo, passando para uma nova divisão em intervalos de tempo.

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

Isso pode ocorrer, por exemplo, devido a uma carga de trabalho que lê ou grava chaves em ordem monotonicamente crescente. O Spanner não consegue balancear a carga para reduzir os efeitos desse comportamento do aplicativo.

Balanceamento de carga normal

O Spanner tenta equilibrar a carga adicionando mais divisões ou movendo-as. O exemplo a seguir mostra como isso pode ser feito.

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

Aqui, a divisão maior em 16/05/2024 às 17h40 (UTC) foi dividida ainda mais em uma divisão menor e, como resultado, a estatística CPU_USAGE_SCORE diminuiu. O Spanner pode não criar divisões em linhas individuais. As divisões refletem a carga de trabalho que causa a estatística CPU_USAGE_SCORE alta.

Se você observar uma divisão quente persistente por mais de 10 minutos, consulte Práticas recomendadas para reduzir pontos de acesso.

Práticas recomendadas para reduzir pontos de acesso

Se o balanceamento de carga não diminuir a latência, a próxima etapa será identificar a causa dos hotspots. Depois disso, as opções são reduzir a carga de trabalho de hotspotting ou otimizar o esquema e a lógica do aplicativo para evitar hotspots.

Identificar a causa

  • Use os insights de bloqueio e transação para procurar transações com alto tempo de espera de bloqueio em que a chave inicial do intervalo de linhas está dentro da divisão ativa.

  • Use os Insights de consulta para procurar consultas que leem da tabela que contém a divisão ativa e que aumentaram a latência recentemente ou têm uma proporção maior de latência para CPU.

  • Use Consultas ativas mais antigas para procurar consultas que leem da tabela que contém a divisão dinâmica e que têm latência maior do que o esperado.

Alguns casos especiais a serem observados:

  • Verifique se o tempo de vida útil (TTL) foi ativado recentemente. Se houver muitas divisões de dados antigos, o TTL poderá aumentar os níveis de CPU_USAGE_SCORE durante exclusões em massa. Nesse caso, o problema será resolvido automaticamente quando as exclusões iniciais forem concluídas.

Otimizar a carga de trabalho

A seguir