Dividir estatísticas

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

O Spanner armazena seus dados como um espaço de chave contíguo, ordenado pelas chaves principais 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 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 por pontos de acesso são conhecidas como ativas ou quentes.

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 ativa. Se 100% da carga em uma divisão for restrita, ela será considerada quente.

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 quentes podem ser movidas entre servidores para balanceamento de carga ou podem ser divididas em divisões 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 solução de problemas e possíveis mudanças no aplicativo.

As estatísticas de divisão de uso excessivo do Spanner ajudam a identificar as divisões em que os pontos de acesso ocorrem. Em seguida, 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.

Disponibilidade de estatísticas de divisão ativa

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. Para acessar esses dados, use uma destas maneiras:

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

Estatísticas da divisão ativa

Use a tabela a seguir para acompanhar as divisões mais frequentes:

  • SPANNER_SYS.SPLIT_STATS_TOP_MINUTE: mostra divisões que estão ativas durante 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 horas:

    • 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 o quão quente ou quente uma divisão é, para cada divisão 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 conseguir 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 da tabela para as seguintes 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 para o intervalo de linhas na divisão. A chave limit: 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 quentes |
AFFECTED_TABLES STRING ARRAY As tabelas com linhas que podem estar na divisão

Chaves de início e limite da divisão

Uma divisão é um intervalo de linhas contíguas de um banco de dados e é definida pelas chaves início e limite. Uma divisão pode ser uma única linha, um intervalo de linhas estreito ou um intervalo de linhas amplo, 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 quente.

Esquema de exemplo

O esquema a seguir é um exemplo de tabela 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. O SPLIT_START e o SPLIT_LIMIT também podem conter chaves truncadas, que são chaves anteriores a 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 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 da split_start até a <end>, o fim do espaço de chaves do banco de dados. Todas as linhas das tabelas que seguem a 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 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. Portanto, as affected_tables abrangem a tabela Users, as tabelas e índices 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 mais importantes

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

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 ativa

Esta seção descreve como detectar e resolver problemas de pontos de acesso.

Selecione um período para investigar

Verifique as métricas de latência do seu banco de dados do Spanner para encontrar o período em que o aplicativo apresentou alta latência e uso da CPU. Por exemplo, pode mostrar que um problema começou por volta das 22h50 do dia 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. Para isso, consulte a tabela SPANNER_SYS.SPLIT_STATS_TOP_MINUTE, conforme mostrado no exemplo abaixo:

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 intervalo 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.

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

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

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, isso pode 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 de 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 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 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 intensa nos últimos minutos. Você pode observar a divisão por mais tempo para determinar se a divisão baseada em carga do Spanner mitiga o ponto de acesso. Talvez haja casos em que o Spanner não consiga fazer o balanceamento de carga.

Por exemplo, consulte a tabela SPANNER_SYS.SPLIT_STATS_TOP_MINUTE. Confira os cenários de exemplo 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 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 móvel

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 pode equilibrar a carga para mitigar os efeitos desse comportamento do aplicativo.

Balanceamento de carga normal

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

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. 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ê 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 é 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.

Identificar a causa

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

  • 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 a opção Consultas ativas mais antigas para procurar consultas que leem da tabela que contém a divisão ativa 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