Dividir estatísticas

Esta página descreve como detectar e depurar pontos de acesso no seu banco de dados. Você pode acessar estatísticas sobre pontos de acesso em divisões com o GoogleSQL e PostgreSQL.

O Spanner armazena os dados como um espaço de chave contíguo, ordenado por as chaves primárias das tabelas e dos índices. Uma divisão é um intervalo de linhas de uma 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 da divisão, mas não o limite da divisão.

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

A estatística de ponto de acesso 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 quente. Se 100% da carga uma divisão for restrita, ela será considerada quente.

O Spanner usa a divisão baseada em carga para distribuir uniformemente os o carregamento de dados nos servidores da instância. As divisões morna e quente podem ser movidas entre servidores para balanceamento de carga ou podem ser divididas em partes menores. No entanto, o Spanner pode não conseguir equilibrar a carga, mesmo após várias tentativas de divisão, devido a antipadrões no aplicativo. Portanto, os pontos de acesso persistentes que duram pelo menos 10 minutos podem precisar de mais a solução de problemas e possíveis mudanças nos aplicativos.

As estatísticas de divisão quente do Spanner ajudam a identificar as divisões onde ocorrem os pontos de acesso. Depois, você pode fazer mudanças no aplicativo ou esquema, conforme necessário. É possível recuperar essas estatísticas das tabelas do sistema SPANNER_SYS.SPLIT_STATS_TOP_MINUTE usando instruções SQL.

Disponibilidade das estatísticas de divisão quente

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

As APIs de leitura única do Spanner não são compatíveis com SPANNER_SYS.

Estatísticas de divisão quente

Use a tabela a seguir para rastrear divisões:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE: mostra divisões principais em intervalos de 1 minuto.

Essas tabelas têm as seguintes propriedades:

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

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

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

    • 1 minuto: 11:58:00-11:58:59
  • O Spanner agrupa as estatísticas por divisões.

  • Cada linha contém uma porcentagem que indica quão quente ou quente é uma divisão, por cada divisão para a qual o Spanner captura estatísticas durante a o intervalo especificado.

  • Se menos de 50% da carga em uma divisão for restringida pelo valor recursos, o Spanner não vai capturar a estatística. Se o Spanner não conseguir armazenar todas as divisões quentes durante o intervalo, o sistema vai priorizar as divisões com a maior porcentagem de CPU_USAGE_SCORE durante o intervalo especificado. Se houver nenhuma divisão retornada, é uma indicação da ausência de pontos de acesso.

Esquema de tabela

A tabela a seguir mostra o esquema das tabelas a seguir:

  • 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 para o intervalo de linhas na divisão. O limite: a tecla também pode ser <end>, indicando o fim do keyspace|
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 quentes |
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 e é definida pelo campo start e limit. Uma divisão pode ser uma única linha, um intervalo de linhas estreita ou uma ampla em um intervalo de linhas específico, e a divisão pode incluir várias tabelas ou índices.

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

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 é assim:

CHAVE PRINCIPAL
<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 entender como elas funcionam.

O SPLIT_START e o 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. SPLIT_START e SPLIT_LIMIT também podem conter chaves truncadas, que são aquelas 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 antes da 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 as chaves 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 de tabela de nível superior, a divisão contém as linhas de tabelas intercaladas entre o início e o limite. Consulte Visão geral dos esquemas para entender como as tabelas intercaladas são colocalizadas.
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 chave do banco de dados. Todas as linhas das tabelas após split_start, como Users(4), sã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 à Users com UserId=9. Portanto, 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 na 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. Portanto, as tabelas afetadas abrangem a tabela Users, seus índices e tabelas intercalados e todas as tabelas que podem aparecer após os 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 principais

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

O Spanner mantém dados para cada tabela, no mínimo, pelo período 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 quente

Nesta seção, descrevemos como detectar e resolver problemas de pontos de acesso.

Selecione um período para investigar

Verifique as métricas de latência das suas no banco de dados do Spanner para encontrar o período em que o aplicativo tiveram alta latência e uso de CPU. Por exemplo, isso pode mostrar 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 ponto de acesso continuou por mais de 10 minutos. É possível fazer isso consultando na tabela SPANNER_SYS.SPLIT_STATS_TOP_MINUTE, conforme mostrado exemplo a seguir:

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

Substitua interval_end_date_time pela data e hora do em um intervalo de tempo usando o 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;

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, significa que o banco de dados está exibindo um ponto de acesso que pode precisar de mais depuração.

Encontrar as divisões com o nível mais alto de CPU_USAGE_SCORE

Para este exemplo, executamos o SQL a seguir para encontrar os intervalos de linha com o nível CPU_USAGE_SCORE mais alto:

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 em um intervalo de tempo 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 em um intervalo de tempo 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

Nesta tabela de resultados, podemos ver que os pontos de acesso ocorreram em duas divisões. A divisão baseada em carga do Spanner pode tentar resolver pontos de acesso nessas divisões. No entanto, talvez não seja possível fazer isso se houver e padrões problemáticos no esquema ou na carga de trabalho. Para detectar se há divisões que precisem da intervenção, recomendamos rastrear as divisões por pelo menos 10 minutos. Por exemplo, o SQL a seguir rastreia a primeira divisão nos últimos dez 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 em um intervalo de tempo 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 em um intervalo de tempo 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 aquecido nos últimos minutos. Você pode observar a por mais tempo para determinar se o Spanner baseado em carga a divisão mitiga o ponto de acesso. Talvez haja casos em que o Spanner não consiga fazer o balanceamento de carga.

Por exemplo, consulte a SPANNER_SYS.SPLIT_STATS_TOP_MINUTE tabela. 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 em um intervalo de tempo 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 abaixo, 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 equilibrar a carga para essa chave única, porque ela não pode ser dividida.

Ponto de acesso em movimento

No exemplo abaixo, a carga passa 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 crescente. O Spanner não consegue balancear a carga para mitigar os efeitos desse comportamento de aplicativo.

Balanceamento de carga normal

O Spanner tenta equilibrar a carga adicionando mais divisões ou movendo divisões. Confira o exemplo abaixo.

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 2024-05-16T17:40:00Z foi dividida em uma divisão menor e, como resultado, a estatística CPU_USAGE_SCORE diminuiu. Talvez o Spanner não crie divisões em linhas individuais. As divisões refletem a carga de trabalho que causa a estatística CPU_USAGE_SCORE alta.

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

Práticas recomendadas para mitigar pontos de acesso

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

Identifique a causa

  • Use Bloquear e Insights sobre transações para identificar transações com alto o tempo de espera de bloqueio em que a chave de início do intervalo de linhas está na divisão a quente.

  • Use os Insights de consulta para procurar consultas que leem da tabela que contém a divisão ativa e 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 lidas da tabela que contém a divisão quente e que têm latência maior do que a esperada.

Alguns casos especiais a serem observados:

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

Otimizar a carga de trabalho

A seguir