Autovacuum agressivo no PostgreSQL

42

Estou tentando fazer com que o PostgreSQL aspire automaticamente meu banco de dados de forma agressiva. No momento, configurei o aspirador automático da seguinte maneira:

  • autovacuum_vacuum_cost_delay = 0 #Desativar vácuo baseado em custo
  • autovacuum_vacuum_cost_limit = 10000 # Valor máximo
  • autovacuum_vacuum_threshold = 50 # Valor padrão
  • autovacuum_vacuum_scale_factor = 0.2 # Valor padrão

Percebo que o aspirador automático só entra em ação quando o banco de dados não está carregado, então entro em situações em que existem muito mais tuplas mortas do que tuplas vivas. Veja a captura de tela em anexo para um exemplo. Uma das tabelas possui 23 tuplas ao vivo, mas 16845 tuplas mortas aguardando vácuo. Isso é insano!

Muitas tuplas mortas

O vácuo automático entra em ação quando a execução do teste termina e o servidor de banco de dados está ocioso, o que não é o que eu quero, pois eu gostaria que o vácuo automático fosse ativado sempre que o número de tuplas mortas exceder 20% de tuplas ativas + 50, pois o banco de dados foi desativado. configurado. O aspirador automático quando o servidor está ocioso é inútil para mim, pois o servidor de produção deve atingir milhares de atualizações / s por um período prolongado e é por isso que preciso que o aspirador automático seja executado mesmo quando o servidor estiver sob carga.

Falta alguma coisa? Como forço a execução do vácuo automático enquanto o servidor está sob carga pesada?

Atualizar

Isso pode ser um problema de bloqueio? As tabelas em questão são tabelas de resumo que são preenchidas por meio de um gatilho após inserção. Essas tabelas estão bloqueadas no modo SHARE ROW EXCLUSIVE para impedir gravações simultâneas na mesma linha.

CadentOrange
fonte

Respostas:

40

Eelke está quase certo de que seu bloqueio está bloqueando o vácuo automático. O Autovacuum foi projetado para dar lugar à atividade do usuário, deliberadamente. Se essas tabelas estiverem bloqueadas, o vácuo automático não poderá ser aspirado.

Para a posteridade, no entanto, eu queria dar um exemplo de conjunto de configurações para autovacuum hiper-agressivo, já que as configurações que você deu não o fazem. Observe que tornar o vácuo automático mais agressivo provavelmente não resolverá o seu problema. Observe também que as configurações padrão de vácuo automático se baseiam na execução de mais de 200 execuções de teste usando o DBT2, buscando uma combinação ideal de configurações; portanto, os padrões devem ser considerados bons, a menos que você tenha um motivo sólido para pensar o contrário, ou que seu banco de dados esteja significativamente fora o mainstream para bancos de dados OLTP (por exemplo, um pequeno banco de dados que recebe 10 mil atualizações por segundo ou um data warehouse de 3 TB).

Primeiro, ative o log para poder verificar se o autovacuum está fazendo o que você pensa que é:

log_autovacuum_min_duration = 0

Então vamos criar mais trabalhadores de autovac e fazer com que eles verifiquem as tabelas com mais frequência:

autovacuum_max_workers = 6
autovacuum_naptime = 15s

Vamos abaixar os limites para o vácuo automático e a análise automática acionados mais cedo:

autovacuum_vacuum_threshold = 25
autovacuum_vacuum_scale_factor = 0.1

autovacuum_analyze_threshold = 10
autovacuum_analyze_scale_factor = 0.05 

Então, tornemos o vácuo automático menos interruptível, para que seja concluído mais rapidamente, mas com o custo de ter um impacto maior na atividade simultânea do usuário:

autovacuum_vacuum_cost_delay = 10ms
autovacuum_vacuum_cost_limit = 1000

Existe um programa completo para o vácuo automático genericamente agressivo, que pode ser apropriado para um pequeno banco de dados que recebe uma taxa muito alta de atualizações, mas pode ter um impacto muito grande na atividade simultânea do usuário.

Além disso, observe que os parâmetros do autovacuum podem ser ajustados por tabela , o que quase sempre é uma resposta melhor para a necessidade de ajustar o comportamento do autovacuum.

Novamente, porém, é improvável que você resolva seu problema real.

Josh Berkus
fonte
36

Apenas para ver quais tabelas se qualificam para o vácuo automático, a seguinte consulta pode ser usada (com base em http://www.postgresql.org/docs/current/static/routine-vacuuming.html ). Observe, no entanto, que a consulta não procura configurações específicas da tabela:

 SELECT psut.relname,
     to_char(psut.last_vacuum, 'YYYY-MM-DD HH24:MI') as last_vacuum,
     to_char(psut.last_autovacuum, 'YYYY-MM-DD HH24:MI') as last_autovacuum,
     to_char(pg_class.reltuples, '9G999G999G999') AS n_tup,
     to_char(psut.n_dead_tup, '9G999G999G999') AS dead_tup,
     to_char(CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
         + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
            * pg_class.reltuples), '9G999G999G999') AS av_threshold,
     CASE
         WHEN CAST(current_setting('autovacuum_vacuum_threshold') AS bigint)
             + (CAST(current_setting('autovacuum_vacuum_scale_factor') AS numeric)
                * pg_class.reltuples) < psut.n_dead_tup
         THEN '*'
         ELSE ''
     END AS expect_av
 FROM pg_stat_user_tables psut
     JOIN pg_class on psut.relid = pg_class.oid
 ORDER BY 1;
pygrac
fonte
11

Sim, é um problema de bloqueio. De acordo com esta página (não cheia), o VACUUM precisa de acesso SHARE UPDATE EXCLUSIVE, bloqueado pelo nível de bloqueio que você está usando.

Tem certeza de que precisa desse bloqueio? O PostgreSQL é compatível com ACID, portanto, gravações simultâneas não são um problema, pois o PostgreSQL abortará uma das transações se ocorrer uma violação de serialização.

Além disso, você pode bloquear linhas usando SELECT FOR UPDATE para bloquear linhas em vez de toda a tabela.

Outra alternativa sem bloqueio seria usar o nível de isolamento de transação serializável. No entanto, isso pode afetar o desempenho de outras transações e você deve estar preparado para mais falhas de serialização.

Eelke
fonte
Isso ocorre devido ao bloqueio das tabelas de resumo, pois elas são bloqueadas usando o SHARE ROW EXCLUSIVE MODE. As gravações simultâneas sem o bloqueio podem ser bem-sucedidas, mas certamente acabarão com valores incorretos. Imagine que eu estou mantendo uma contagem N de linhas do tipo X. Se eu inserir simultaneamente 2 linhas do tipo X, sem travar, terminarei com N + 1 na minha tabela de resumo em vez de N + 2. A solução que eu adotei é ter um trabalho cron que aspire manualmente as tabelas de resumo no meu banco de dados. Funciona bem e parece ser a abordagem recomendada, mas parece muito um hack para mim.
CadentOrange
6

Aumentar o número de processos de autovacuum e reduzir o tempo de soneca provavelmente ajudará. Aqui está a configuração para um PostgreSQL 9.1 que eu uso em um servidor que armazena informações de backup e, como resultado, obtém muita atividade de inserção.

http://www.postgresql.org/docs/current/static/runtime-config-autovacuum.html

autovacuum_max_workers = 6              # max number of autovacuum subprocesses
autovacuum_naptime = 10         # time between autovacuum runs
autovacuum_vacuum_cost_delay = 20ms     # default vacuum cost delay for

Também tentarei abaixar o cost_delaypara tornar a aspiração mais agressiva.

Também posso testar o vácuo automático usando o pgbench.

http://wiki.postgresql.org/wiki/Pgbenchtesting

Exemplo de alta contenção:

Criar banco de dados bench_replication

pgbench -i -p 5433 bench_replication

Executar pgbench

pgbench -U postgres -p 5432 -c 64 -j 4 -T 600 bench_replication

Verificar o status de aspiração automática

psql
>\connect bench_replicaiton
bench_replication=# select schemaname, relname, last_autovacuum from pg_stat_user_tables;
 schemaname |     relname      |        last_autovacuum        
------------+------------------+-------------------------------
 public     | pgbench_branches | 2012-07-18 18:15:34.494932+02
 public     | pgbench_history  | 
 public     | pgbench_tellers  | 2012-07-18 18:14:06.526437+02
 public     | pgbench_accounts | 
Craig Efrein
fonte
6

O script "qualificar para autovacuum" existente é muito útil, mas (conforme afirmado corretamente) estavam faltando opções específicas da tabela. Aqui está a versão modificada que leva em consideração essas opções:

WITH rel_set AS
(
    SELECT
        oid,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_threshold=', 2), ',', 1)::BIGINT
        END AS rel_av_vac_threshold,
        CASE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)
            WHEN '' THEN NULL
        ELSE split_part(split_part(array_to_string(reloptions, ','), 'autovacuum_vacuum_scale_factor=', 2), ',', 1)::NUMERIC
        END AS rel_av_vac_scale_factor
    FROM pg_class
) 
SELECT
    PSUT.relname,
    to_char(PSUT.last_vacuum, 'YYYY-MM-DD HH24:MI')     AS last_vacuum,
    to_char(PSUT.last_autovacuum, 'YYYY-MM-DD HH24:MI') AS last_autovacuum,
    to_char(C.reltuples, '9G999G999G999')               AS n_tup,
    to_char(PSUT.n_dead_tup, '9G999G999G999')           AS dead_tup,
    to_char(coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples, '9G999G999G999') AS av_threshold,
    CASE
        WHEN (coalesce(RS.rel_av_vac_threshold, current_setting('autovacuum_vacuum_threshold')::BIGINT) + coalesce(RS.rel_av_vac_scale_factor, current_setting('autovacuum_vacuum_scale_factor')::NUMERIC) * C.reltuples) < PSUT.n_dead_tup
        THEN '*'
    ELSE ''
    END AS expect_av
FROM
    pg_stat_user_tables PSUT
    JOIN pg_class C
        ON PSUT.relid = C.oid
    JOIN rel_set RS
        ON PSUT.relid = RS.oid
ORDER BY C.reltuples DESC;
Vadim Zingertal
fonte