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 doVACUUM
parte, ele mantém um único instantâneo (e, portanto, bloqueia a limpeza).n_dead_tup
é apenas uma estimativa mantida porANALYZE
, portanto, espere alguma flutuação (especialmente em tabelas grandes).