Monitoramos o banco de dados PostgreSQL - quem é o culpado e o que fazer

Eu já falei sobre como "capturamos" os problemas do PostgreSQL usando o monitoramento de logs em massa em centenas de servidores ao mesmo tempo. Mas, além dos logs, esse DBMS também nos fornece muitas ferramentas para analisar seu estado - é um pecado não usá-las.

É verdade que, se você apenas os olhar no console, poderá girar rapidamente sem nenhum benefício, porque a quantidade de dados disponíveis excede todos os limites razoáveis.


Portanto, para que a situação permaneça controlável, desenvolvemos um complemento para o Zabbix que fornece métricas, telas de formulários e estabelece regras uniformes de monitoramento para todos os servidores e bancos de dados neles.

O artigo de hoje é sobre quais conclusões podem ser tiradas observando dinamicamente as várias métricas das bases de servidores do PostgreSQL e onde o problema pode estar oculto.

Status da conexão


A primeira coisa que todas as desmontagens no tópico “o que aconteceu com o banco de dados / foi ruim” começa com o monitoramento do estado resumido de pg_stat_activity :



No gráfico à esquerda, vemos todas as conexões que estão esperando por algo, à direita - que são algo Faz. Dependendo da versão do PG, o status da conexão é determinado por pg_stat_activity.state/wait_evente / ou pelo texto da própria solicitação.

O que procurar :

  • Muito poucoidle - em algum momento, seu aplicativo pode não ter conexões suficientes já abertas no banco de dados e, quando você tenta abrir outro, acabará esperando o processo inicializar para servir uma nova conexão.
  • idle «» , max_connections.
  • idle in transaction — , - pgbouncer. .

    , , idle_in_transaction_session_timeout.
  • wait — - «» . — .

    , «» pg_terminate_backend(pid).
  • active ( max-) , «». - (, « ») , , …

    — , «» .
  • maintenance — , - :

    query ~* E'^(\\s*(--[^\\n]*\\n|/\\*.*\\*/|\\n))*(autovacuum|VACUUM|ANALYZE|REINDEX|CLUSTER|CREATE|ALTER|TRUNCATE|DROP)'

    Na maioria dos casos, haverá o número de autovacuum / autoanalyze trabalhando ao mesmo tempo, cujo dano consiste apenas no uso de recursos do servidor para casos "estranhos". Se isso é crítico para você - torça autovacuum_max_workerse autovacuum_naptime, mas desligue completamente - você não deve .

    Mas se , ao mesmo tempo, começar a crescer waitemaintenance houver uma chance de ver se alguém decidiu implementar o DBA ou o código do desenvolvedor, por exemplo, bloqueando metade da chance de aplicativos funcionais.

Como é importante remover não apenas muitas métricas, mas também fazê-las da maneira mais eficiente possível, tentamos filmar algumas delas de forma síncrona na estrutura de uma solicitação:

Status de conexão e bloqueio
WITH event_types(wait_event_type) AS(
  VALUES
    ('lwlock')
  , ('lock')
  , ('bufferpin')
  , ('client')
  , ('extension')
  , ('ipc')
  , ('timeout')
  , ('io')
)
, events(wait_event) AS(
  VALUES
    ('walwritelock')
  , ('wal_insert')
  , ('buffer_content')
  , ('buffer_io')
  , ('lock_manager')
  , ('relation')
  , ('extend')
  , ('page')
  , ('tuple')
  , ('transactionid')
  , ('virtualxid')
  , ('speculative token')
  , ('object')
  , ('userlock')
  , ('advisory')
  , ('clientread')
  , ('datafileextend')
  , ('datafileread')
  , ('datafilewrite')
  , ('slruread')
  , ('slruwrite')
)
, states(state) AS(
  VALUES
    ('running')
  , ('maintenance')
  , ('waiting')
  , ('transaction')
  , ('idle')
)
, stats AS(
  SELECT
    pid
  , datname
  , state
  , lower(wait_event_type) wait_event_type
  , lower(wait_event) wait_event
  , query
  FROM
    pg_stat_activity
  WHERE
    pid <> pg_backend_pid()
)
, dbs AS(
  SELECT
    datname
  FROM
    pg_database db
  WHERE
    NOT db.datistemplate
)
  SELECT
    date_part('epoch', now())::integer ts
  , coalesce(s.qty, 0) val
  , dbs.datname dbname
  , states.state
  , true total
  FROM
    dbs
  CROSS JOIN
    states
  NATURAL LEFT JOIN
    (
      SELECT
        datname
      , CASE
          WHEN query ~* E'^(\\s*(--[^\\n]*\\n|/\\*.*\\*/|\\n))*(autovacuum|VACUUM|ANALYZE|REINDEX|CLUSTER|CREATE|ALTER|TRUNCATE|DROP)' THEN
            'maintenance'
          WHEN wait_event IS NOT NULL AND
            wait_event <> 'clientread' AND
            state = 'active' THEN
            'waiting'
          WHEN state = 'active' THEN
            'running'
          WHEN state = 'idle' THEN
            'idle'
          WHEN state IN ('idle in transaction', 'idle in transaction (aborted)') THEN
            'transaction'
          WHEN state = 'fastpath function call' THEN
            'fastpath'
          ELSE
            'disabled'
        END state
      , count(*) qty
      FROM
        stats
      GROUP BY
        1, 2
    ) s
UNION
  SELECT
    date_part('epoch', now())::integer ts
  , coalesce(t.qty, 0) val
  , dbs.datname dbname
  , event_types.wait_event_type
  , false total
  FROM
    dbs
  CROSS JOIN
    event_types
  NATURAL LEFT JOIN
    (
      SELECT
        datname
      , wait_event_type
      , count(*) qty
      FROM
        stats
      WHERE
        wait_event_type IS NOT NULL
      GROUP BY
        1, 2
    ) t
UNION
  SELECT
    date_part('epoch', now())::integer ts
  , coalesce(e.qty, 0) val
  , dbs.datname dbname
  , events.wait_event
  , false total
  FROM
    dbs
  CROSS JOIN
    events
  NATURAL LEFT JOIN
    (
      SELECT
        datname
      , wait_event
      , count(*) qty
      FROM
        stats
      WHERE
        wait_event IS NOT NULL
      GROUP BY
        1, 2
    ) e;

Fechaduras


Como abordamos o monitoramento de bloqueios no parágrafo anterior, vale a pena notar que o PostgreSQL gosta de aplicá-los à esquerda e à direita:



Destes, estamos mais interessados ​​em dois tipos:

  • Exclusive - normalmente ocorrem ao bloquear um registro específico.
  • AccessExclusive - ao executar operações de manutenção em cima da mesa.

Mas não esqueça que o número total de bloqueios não é de borracha :
Os bloqueios consultivo e regular são armazenados na área de memória compartilhada, cujo tamanho é determinado pelos parâmetros de configuração max_locks_per_transactione max_connections. É importante que essa memória seja suficiente, pois, caso contrário, o servidor não poderá emitir nenhum bloqueio . Portanto, o número de bloqueios recomendados que um servidor pode emitir geralmente é limitado a dezenas ou centenas de milhares, dependendo da configuração do servidor.
Normalmente, essa situação surge se o aplicativo "flui" e os recursos não são liberados: conexões com o banco de dados, contextos de transação ou bloqueios de aviso . Portanto, preste atenção à dinâmica geral.

Transações por segundo (TPS)


Para obter informações sobre alterações no contexto do banco de dados atual, você pode usar a visualização do sistema pg_stat_database . Mas se houver muitos bancos de dados no servidor, é conveniente fazer isso imediatamente para todos eles, conectando-se apostgres .

TPS e tuplas
SELECT
  extract(epoch from now())::integer ts
, datname dbname
, pg_stat_get_db_tuples_returned(oid) tup_returned
, pg_stat_get_db_tuples_fetched(oid) tup_fetched
, pg_stat_get_db_tuples_inserted(oid) tup_inserted
, pg_stat_get_db_tuples_updated(oid) tup_updated
, pg_stat_get_db_tuples_deleted(oid) tup_deleted
, pg_stat_get_db_xact_commit(oid) xact_commit
, pg_stat_get_db_xact_rollback(oid) xact_rollback
FROM
  pg_database
WHERE
  NOT datistemplate;

Quero enfatizar separadamente - não negligencie a saída dos valores máximos das métricas!



Neste gráfico, podemos ver claramente a situação de um aumento repentino de pico no número de committransações realizadas ( ). Isso não corresponde individualmente à carga no servidor e as transações podem ser de complexidade variável, mas um crescimento de quatro vezes mostra claramente que o servidor deve ter uma certa reserva de desempenho para sobreviver a esse pico sem problemas.

Bem, a reversão ( rollback) da transação é uma ocasião para verificar se o aplicativo está sendo executado conscientemente ROLLBACKou se o servidor faz isso automaticamente como resultado de um erro.

Número de operações nos registros


Primeiro, preste atenção aos registros que subtraímos dos índices / tabelas:



  • tuples.returned — , «» .
  • tuples.fetched — , « » Rows Removed by Filter, «» .
  • tuples.ratio — , , 1, — . , , , .

Se você observar um pico acentuadotuples.ratio , pode ter certeza de que alguma solicitação ineficiente da categoria descrita no artigo sobre receitas para o tratamento deles será apresentada no log .

No entanto, mesmo se ratioidealmente igual a 1, mas o pico caiureturned/fetched - também não espere bom. Geralmente, isso pode significar que há algum tipo de problema no plano, como:

Hash Join
  - Hash
    - Seq Scan on BIG_TABLE
  - Index Scan ...

Merge Join
  - Index Scan on BIG_INDEX
  - Index Scan ...

Desde que começamos a verificar o que está sendo lido lá, vamos ver como isso acontece. Ou seja, quantos registros lemos por índices e quanto resultado Seq Scan:



é claro que aqui qualquer crescimento não planejado de indicadores deve causar suspeitas. Por exemplo, se por algum motivo você precisar ler uma placa inteira de 10 milhões de registros todas as noites, a aparência desse pico durante o dia é um motivo para a desmontagem.

Bem como quaisquer inserções / atualizações / exclusões anômalas em massa:



Usando cache de dados


Para entender como a revisão em massa de registros realmente piora a vida do servidor, vejamos o trabalho do servidor com páginas de dados e a proporçãoblock.read/hit . Em um mundo ideal, o servidor não deve "ler" do disco ( shared readno nó do plano) absolutamente nada, tudo já deve estar na memória ( shared hit), pois o acesso ao disco é sempre lento .

Na realidade, isso não é inteiramente verdade e é o motivo de uma análise completa das solicitações no horário de pico:



Solicitação / transação mais longa


Para o MVCC, consultas e transações de longa duração em sistemas ocupados são um desastre de desempenho. Detalhes e fotos sobre isso podem ser lidos aqui e aqui - como você ainda pode sobreviver em tais condições.



Capturar esses vilões nos ajuda pg_stat_activity.query_start/xact_start.

Como mostra nossa experiência, uma representação visual dessas métricas já é suficiente para representar aproximadamente onde "cavar" mais:

  • procure por vazamentos de recursos no aplicativo
  • otimizar solicitações com falha
  • colocar hardware mais produtivo
  • ... ou verifique se a carga está espaçada corretamente no tempo

All Articles