PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Quando o autovacuum não aspira


Algumas semanas atrás, expliquei os fundamentos do ajuste de autovacuum. No final desse post, prometi analisar os problemas com a aspiração em breve. Bem, demorou um pouco mais do que eu planejei, mas vamos lá.

Para recapitular rapidamente, autovacuum é um processo em segundo plano que limpa linhas mortas, por exemplo versões de linha excluídas antigas. Você também pode realizar a limpeza manualmente executando VACUUM , mas autovacuum faz isso automaticamente dependendo da quantidade de linhas mortas na tabela, no momento certo – não com muita frequência, mas com frequência suficiente para manter a quantidade de “lixo” sob controle.



De um modo geral, autovacuum não pode estar sendo executado com muita frequência – a limpeza só é realizada após atingir algum número de linhas mortas acumuladas na tabela. Mas pode ser atrasado por vários motivos, resultando em tabelas e índices ficando maiores do que o desejável. E esse é exatamente o tema deste post. Então, quais são os culpados comuns e como identificá-los?

Limitação


Conforme explicado no básico de ajuste, autovacuum os trabalhadores são estrangulados para realizar apenas certa quantidade de trabalho por intervalo de tempo. Os limites padrão são bastante baixos – cerca de 4 MB/s de gravações, 8 MB/s de leituras. Isso é adequado para pequenas máquinas como Raspberry Pi ou pequenos servidores de 10 anos atrás, mas as máquinas atuais são muito mais poderosas (tanto em termos de CPU quanto de E/S) e lidam com muito mais dados.

Imagine que você tem algumas mesas grandes e algumas pequenas. Se todos os três autovacuum os trabalhadores começarem a limpar as mesas grandes, nenhuma das mesas pequenas será aspirada, independentemente da quantidade de linhas mortas que acumularem. Identificar isso não é particularmente difícil, supondo que você tenha monitoramento suficiente. Procure por períodos em que todos os autovacuum os trabalhadores estão ocupados enquanto as mesas não são aspiradas apesar de acumular muitas linhas mortas.

Todas as informações necessárias estão em pg_stat_activity (número de autovacuum processos de trabalho) e pg_stat_all_tables (last_autovacuum e n_dead_tup ).

Aumentando o número de autovacuum trabalhadores não é uma solução, pois a quantidade total de trabalho permanece a mesma. Você pode especificar limites de limitação por tabela, excluindo esse trabalhador do limite total, mas isso ainda não garante que haverá trabalhadores disponíveis quando necessário.

A solução certa é ajustar a limitação, usando limites razoáveis ​​em relação à configuração do hardware e aos padrões de carga de trabalho. Algumas recomendações básicas de limitação são mencionadas no post anterior. (Obviamente, se você puder reduzir a quantidade de linhas mortas geradas no banco de dados, essa seria uma solução ideal.)

A partir deste ponto, assumiremos que a limitação não é o problema, ou seja, que o autovacuum trabalhadores não ficam saturados por longos períodos de tempo e que a limpeza é acionada em todas as tabelas sem atrasos injustificados.

Transações longas


Então, se a tabela for aspirada regularmente, com certeza ela não poderá acumular muitas linhas mortas, certo? Infelizmente não. As linhas não são realmente “removíveis” imediatamente após serem excluídas, mas apenas quando não há transações que possam vê-las. O comportamento exato depende do que as outras transações estão (estavam) fazendo e do nível de serialização, mas em geral:

LEIA COMPROMETIDA
  • executar a limpeza do bloco de consultas
  • transações ociosas bloqueiam a limpeza somente se executarem uma gravação
  • transações ociosas (sem nenhuma gravação) não bloquearão a limpeza (mas não é uma boa prática mantê-las por perto)

SERIALIZÁVEL
  • executar a limpeza do bloco de consultas
  • limpeza de bloqueio de transações ociosas (mesmo que apenas tenham feito leituras)

Na prática, é mais sutil, é claro, mas explicar todos os vários bits exigiria primeiro explicar como funcionam os XIDs e os instantâneos, e esse não é o objetivo deste post. O que você realmente deve tirar disso é que transações longas são uma má ideia, principalmente se essas transações podem ter feito gravações.

Claro, existem razões perfeitamente válidas pelas quais você pode precisar manter as transações por longos períodos de tempo (por exemplo, se você precisar garantir o ACID para todas as alterações). Mas certifique-se de que isso não aconteça desnecessariamente, por exemplo. devido a um design de aplicativo ruim.

Uma consequência um tanto inesperada disso é o alto uso de CPU e E/S, devido ao autovacuum correndo sem parar, sem limpar nenhuma linha morta (ou apenas algumas delas). Por causa disso, as mesas ainda são elegíveis para limpeza na próxima rodada, causando mais mal do que bem.

Como detectar isso? Em primeiro lugar, você precisa monitorar transações de longa duração, principalmente as ociosas. Tudo o que você precisa fazer é ler os dados de pg_stat_activity . A definição da visão muda um pouco com a versão do PostgreSQL, então você pode precisar ajustar isso um pouco:
SELECT xact_start, state FROM pg_stat_activity;

-- count 'idle' transactions longer than 15 minutes (since BEGIN)
SELECT COUNT(*) FROM pg_stat_activity
 WHERE state = 'idle in transaction'
  AND (now() - xact_start) > interval '15 minutes'

-- count transactions 'idle' for more than 5 minutes
SELECT COUNT(*) FROM pg_stat_activity
 WHERE state = 'idle in transaction'
  AND (now() - state_change) > interval '5 minutes'

Você também pode simplesmente usar algum plugin de monitoramento existente, por exemplo, check_postgres.pl. Aqueles já incluem esse tipo de verificação de sanidade. Você terá que decidir o que é uma duração razoável de transação/consulta, que é específica do aplicativo.

Desde o PostgreSQL 9.6 você também pode usar idle_in_transaction_session_timeout para que as transações ociosas por muito tempo sejam encerradas automaticamente. Da mesma forma, para consultas longas, há statement_timeout .

Outra coisa útil é VACUUM VERBOSE que realmente informará quantas linhas mortas ainda não puderam ser removidas:
db=# VACUUM verbose z;
INFO:  vacuuming "public.z"
INFO:  "z": found 0 removable, 66797 nonremovable row versions in 443 out of 443 pages
DETAIL:  12308 dead row versions cannot be removed yet.
...

Ele não informará qual back-end está impedindo a limpeza, mas é um sinal bastante claro do que está acontecendo.

Observação: . Você não pode obter facilmente essas informações de autovacuum porque só é registrado com DEBUG2 por padrão (e você certamente não deseja executar com esse nível de log em produção).

Consultas longas em esperas quentes


Vamos supor que as tabelas estejam sendo limpas em tempo hábil, mas não removendo tuplas mortas, resultando em inchaço de tabela e índice. Você está monitorando pg_stat_activity e não há transações de longa duração. Qual poderia ser o problema?

Se você tiver uma réplica de streaming, é provável que o problema esteja lá. Se a réplica usar hot_standby_feedback=on , as consultas na réplica agem praticamente como transações no primário, incluindo o bloqueio de limpeza. Claro, hot_standby_feedback=on é usado exatamente ao executar consultas longas (por exemplo, cargas de trabalho de análise e BI) em réplicas, para evitar cancelamentos devido a conflitos de replicação.

Infelizmente, você terá que escolher – manter hot_standby_feedback=on e aceitar atrasos na limpeza ou lidar com consultas canceladas. Você também pode usar max_standby_streaming_delay para limitar o impacto, embora isso não impeça totalmente os cancelamentos (portanto, você ainda precisa tentar novamente as consultas).

Na verdade, há uma terceira opção agora – replicação lógica. Em vez de usar a replicação de streaming física para a réplica de BI, você pode copiar as alterações usando a nova replicação lógica, disponível no PostgreSQL 10. A replicação lógica relaxa o acoplamento entre o primário e a réplica e torna os clusters em sua maioria independentes (são limpos de forma independente, etc.).

Isso resolve os dois problemas associados à replicação de streaming físico – limpeza atrasada em consultas primárias ou canceladas na réplica de BI. No entanto, para réplicas que atendem a propósitos de DR, a replicação de streaming continua sendo a escolha certa. Mas essas réplicas não estão (ou não deveriam estar) executando consultas longas.

Observação: Embora eu tenha mencionado que a replicação lógica estará disponível no PostgreSQL 10, uma parte significativa da infraestrutura estava disponível em versões anteriores (particularmente no PostgreSQL 9.6). Portanto, você pode fazer isso mesmo em versões mais antigas (fizemos isso para alguns de nossos clientes), mas o PostgreSQL 10 o tornará muito mais conveniente e confortável.

Problema com autoanalyze


Um detalhe que você pode perder é que autovacuum trabalhadores realmente executa duas tarefas diferentes. Primeiramente a limpeza (como se estivesse executando VACUUM ), mas também coletando estatísticas (como se estivesse executando ANALYZE ). E ambos as peças são estranguladas usando autovacuum_cost_limit .

Mas há uma grande diferença no manuseio de transações. Sempre que o VACUUM parte atinge autovacuum_cost_limit , o trabalhador libera o instantâneo e dorme por um tempo. O ANALYZE no entanto, precisa ser executado em um único instantâneo/transação, o que faz limpeza do bloco.

Esta é uma maneira elegante de dar um tiro no pé, principalmente se você também fizer isso:
  • aumentar default_statistics_target para criar estatísticas mais precisas a partir de amostras maiores
  • inferior autovacuum_analyze_scale_factor para coletar estatísticas com mais frequência

A consequência não intencional, é claro, é que ANALYZE acontecerá com mais freqüência, levará muito mais tempo e (diferentemente do VACUUM parte) impedem a limpeza. A solução geralmente é bastante simples - não diminua o autovacuum_analyze_scale_factor demais. Executando ANALYZE cada vez que 10% das alterações da tabela devem ser mais do que suficientes na maioria dos casos.

n_dead_tup


Uma última coisa que gostaria de mencionar é sobre as mudanças em pg_stat_all_tables.n_dead_tup valores. Você pode pensar que o valor é um contador simples, incrementado sempre que uma nova tupla morta é criada e diminuída sempre que é limpa. Mas na verdade é apenas uma estimativa do número de tuplas mortas, atualizadas por ANALYZE . Para tabelas pequenas (menos de 240 MB) não é realmente uma grande diferença, porque ANALYZE lê a tabela inteira e por isso é bastante exato. Para tabelas grandes, no entanto, pode mudar um pouco dependendo de qual subconjunto de tabela é amostrado. E diminuindo autovacuum_vacuum_scale_factor torna mais aleatório.

Portanto, tenha cuidado ao olhar para n_dead_tup em um sistema de monitoramento. Quedas ou aumentos repentinos no valor podem ser simplesmente devido a ANALYZE recalcular uma estimativa diferente, e não devido à limpeza real e/ou novas tuplas mortas que aparecem na tabela.

Resumo


Para resumir isso em alguns pontos simples:
  • autovacuum só pode funcionar se não houver transações que possam precisar das tuplas mortas.
  • Consultas de longa duração bloqueiam a limpeza. Considere usar statement_timeout para limitar os danos.
  • A transação de longa duração pode bloquear a limpeza. O comportamento exato depende de coisas como nível de isolamento ou o que aconteceu na transação. Monitore-os e encerre-os, se possível.
  • Consultas de longa duração em réplicas com hot_standby_feedback=on também pode bloquear a limpeza.
  • autoanalyze também é estrangulado, mas ao contrário do VACUUM parte, ele mantém um único instantâneo (e, portanto, bloqueia a limpeza).
  • n_dead_tup é apenas uma estimativa mantida por ANALYZE , portanto, espere alguma flutuação (especialmente em tabelas grandes).