Depurar pontos de acesso no banco de dados

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

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 medida é fornecida como uma porcentagem. Se mais de 50% da carga em uma divisão for restringida pelo recursos disponíveis, a divisão é considerada morna. 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 de uso excessivo do Spanner ajudam a identificar as divisões em que os pontos de acesso ocorrem. Em seguida, é possível fazer alterações no aplicativo ou esquema conforme necessário. Você pode recuperar essas estatísticas no SPANNER_SYS.SPLIT_STATS_TOP_MINUTE tabelas do sistema usando instruções SQL.

Disponibilidade das estatísticas de divisão quente

O Spanner fornece as estatísticas de divisão quente na SPANNER_SYS. Os dados de SPANNER_SYS estão disponíveis somente por meio de Interfaces GoogleSQL e 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 acompanhar as divisões mais frequentes:

  • 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 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 quentes 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 quente
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 Tabelas cujas linhas podem estar na divisão

O que são as 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 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 o espaço da chave tenha esta aparência:

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

Veja a seguir alguns exemplos de divisões para ajudar você a entender a aparência de divisões

SPLIT_START e SPLIT_LIMIT podem indicar a linha de uma tabela ou índice. ou podem ser <begin> e <end>, representando os limites da chave 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 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 final do espaço de chave possível 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 mais importantes

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 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 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 sobrecarregados 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 uso excessivo do ponto de acesso persistente

À medida que o Spanner equilibra sua carga com a divisão baseada em carga, recomendamos investigar 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 em um intervalo de tempo 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 CPU_USAGE_SCORE mais alto

Para este exemplo, executamos o seguinte SQL para encontrar os intervalos de linhas com o nível mais alto do 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 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 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 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 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 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. Pode haver casos em que O Spanner não consegue mais balancear a 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.

Uma única linha com acesso frequente

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 e se há uma carga permanente em uma linha conhecida.

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 a seguir, a carga se move por divisões contíguas ao longo do tempo, movendo-se para uma nova divisão ao longo de 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 em ordem crescente monotonicamente. 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 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 16/05/2024T17:40:00Z foi dividida ainda mais em uma 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ê tiver observado uma divisão quente persistente por mais de 10 minutos, consulte práticas para mitigar 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 a opção 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 o Query Insights para procurar consultas lidas na tabela que contém a divisão quente e recentemente aumentou a latência ou 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 mais usada e que têm latência maior do que o esperado.

Alguns casos especiais a serem observados:

  • Verifique se o time to live (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