Adicionar coluna anulável à tabela custa mais de 10 minutos

11

Tenho problemas para adicionar uma nova coluna em uma tabela.
Tentei executá-lo algumas vezes, mas depois de mais de 10 minutos em execução, decidi cancelar a consulta devido ao tempo de bloqueio.

ALTER TABLE mytable ADD mycolumn VARCHAR(50);

Informação útil:

  • Versão do PostgreSQL: 9.1
  • Número de linhas: ~ 250K
  • Número de colunas: 38
  • Número de colunas anuláveis: 32
  • Número de restrições: 5 (1 PK, 3 FK, 1 ÚNICO)
  • Número de índices: 1
  • Tipo de SO: Debian Squeeze 64

Encontrei informações interessantes sobre a maneira como o PostgreSQL gerencia colunas anuláveis ​​(via HeapTupleHeader).

Meu primeiro palpite é que, como essa tabela já possui 32 colunas anuláveis ​​com 8 bits MAXALIGN, o HeapTupleHeader tem 4 bytes de comprimento (não verificado e não sei como fazê-lo).

Portanto, a adição de uma nova coluna anulável pode precisar de uma atualização do HeapTupleHeader em cada linha para adicionar um novo 8 bits MAXALIGN, o que pode causar problemas de desempenho.

Então, tentei alterar uma das colunas anuláveis ​​(que não é realmente anulável na realidade) para diminuir para 31 o número de colunas anuláveis, para verificar se meu palpite poderia ser verdadeiro.

ALTER TABLE mytable ALTER myothercolumn SET NOT NULL;

Infelizmente, essa alteração também leva muito tempo, mais de 5 minutos, então eu também a abortei.

Você tem uma idéia do que poderia causar esse custo de desempenho?

Matthieu Verrecchia
fonte
11
Bem, posso lhe dizer parte disso: alterar um tipo de coluna para outro que não é compatível com binário na verdade cria uma nova coluna, copia os dados e define a coluna antiga como descartada. No entanto, SET NOT NULLnão altera o tipo, apenas adiciona uma restrição - mas a restrição deve ser verificada na tabela e isso exige uma verificação completa da tabela. A versão 9.4 melhora alguns desses casos com bloqueios mais fracos, mas ainda é bastante pesado.
Craig Ringer
11
Antes de suspeitar que o desempenho esteja lento, você precisa garantir que o ALTER TABLE não esteja apenas aguardando um bloqueio. Mencione-o na pergunta se você tiver verificado.
Daniel Vérité 28/08
Obrigado craig e daniel. Quando executo o comando alter, ele aparece em pg_stat_activity com a espera "true", suponho que isso significa que aguarda um bloqueio !? É o bom caminho para verificar? By the way, antes de executar este alter, tudo vai bem, mas poucos segundos depois do início, o número de bloqueios cresce
Tente a consulta em wiki.postgresql.org/wiki/Lock_dependency_information para uma melhor visualização. Você tem transações remanescentes que esquecem de confirmar ou atividade pesada com esta tabela que a mantém sempre ocupada.
Daniel Vérité 28/08
Pode ser um ajuste melhor no dba.SE.
Erwin Brandstetter

Respostas:

8

Existem alguns mal-entendidos aqui:

O mapa de bits nulo é não parte do cabeçalho montão tuplo. Por documentação:

Há um cabeçalho de tamanho fixo (ocupando 23 bytes na maioria das máquinas), seguido por um bitmap nulo opcional ...

Suas 32 colunas anuláveis ​​não são suspeitas por dois motivos:

  • O bitmap nulo é adicionado por linha e somente se houver pelo menos um NULLvalor real na linha. As colunas anuláveis não têm impacto direto, apenas os NULLvalores reais . Se o bitmap nulo estiver alocado, ele sempre será alocado completamente (tudo ou nada). O tamanho real do bitmap nulo é de 1 bit por coluna, arredondado para o próximo byte . Por código de fonte atual:

    #define BITMAPLEN(NATTS) (((int)(NATTS) + 7) / 8)
  • O bitmap nulo é alocado após o cabeçalho da tupla de heap e seguido por um OID opcional e dados da linha. O início de um OID ou dados de linha é indicado por t_hoffno cabeçalho. Por código fonte do comentário :

    Observe que t_hoff deve ser um múltiplo de MAXALIGN.

  • Há um byte livre após o cabeçalho da tupla de heap, que ocupa 23 bytes. Portanto, o bitmap nulo para linhas de até 8 colunas efetivamente não tem custo adicional. Com a 9ª coluna da tabela, t_hoffsão avançados outros MAXALIGNbytes (normalmente 8) para fornecer outras 64 colunas. Portanto, a próxima borda teria 72 colunas.

Para exibir informações de controle de um cluster de banco de dados PostgreSQL (incl. MAXALIGN), Exemplo para uma instalação típica do Postgres 9.3 em uma máquina Debian:

    sudo /usr/lib/postgresql/9.3/bin/pg_controldata /var/lib/postgresql/9.3/main

Atualizei as instruções na resposta relacionada que você citou .

Tudo isso à parte, mesmo que sua ALTER TABLEinstrução desencadeie uma reescrita de uma tabela inteira (o que provavelmente ocorre, alterando um tipo de dados), 250K não são muito e seria uma questão de segundos em qualquer máquina decente (a menos que as linhas sejam extraordinariamente grandes) . 10 minutos ou mais indicam um problema completamente diferente. O seu extrato está aguardando um bloqueio na mesa, provavelmente.

O crescente número de entradas pg_stat_activitysignifica mais transações abertas - indica acesso simultâneo à tabela (provavelmente) que precisa aguardar a conclusão da operação.

Alguns tiros no escuro

Verifique se há um inchaço na tabela, tente um método VACUUM mytablemais suave ou mais agressivo VACUUM FULL mytable- que pode encontrar os mesmos problemas de simultaneidade, pois esse formulário também adquire um bloqueio exclusivo. Você pode tentar o pg_repack ...

Começaria por inspecionar possíveis problemas com índices, gatilhos, chave estrangeira ou outras restrições, especialmente as que envolvem a coluna. Especialmente um índice corrompido pode estar envolvido? Tente REINDEX TABLE mytable;ou DROPtodos eles e adicione-os novamente ALTER TABLE na mesma transação .

Tente executar o comando à noite ou sempre que não houver muita carga.

Um método de força bruta seria interromper o acesso ao servidor e tente novamente:

Sem poder identificá-lo, a atualização para a versão atual ou a 9.4 futura em particular pode ajudar. Houve várias melhorias para tabelas grandes e para detalhes de bloqueio. Mas se houver algo quebrado no seu banco de dados, você provavelmente deve descobrir isso primeiro.

Erwin Brandstetter
fonte
2
É quase certamente fechaduras. Mas, como teste, você sempre pode criar uma cópia da tabela e tentar alterar isso. Se isso não demorar muito, você sabe que não é a modificação real que é o problema.
Obrigado pelas explicações Erwin. Acho que você está certo, parece ser um problema de bloqueio. Quando verifico pg_stat_activity, posso ver que meu ALTER tem uma "espera" verdadeira. O que não consigo entender é por que o ALTER não consegue bloquear a tabela, porque mesmo quando não consigo encontrar nenhuma consulta em execução, parece que não consegue. Mas assim que meu ALTER começa a ser executado, todas as outras consultas aguardam o término. Portanto, a atividade parece indicar que o ALTER bloqueia todas as outras consultas, mas também indica que o ALTER não conseguiu o bloqueio. Eu acho que há algo que eu não entendo bem !?
@MatthieuVerrecchia: Você tentou o teste que Richard sugeriu?
Erwin Brandstetter
11
Acabei de clonar minha tabela para uma nova (com pg_dump -> pg_sql). A nova coluna foi adicionada corretamente em 50ms, o que confirma o problema de bloqueio. A propósito, ainda não entendo por que o ALTER não pode travar com uma atividade de banco de dados realmente padrão.
11
@ErwinBrandstetter Segui suas sugestões e tentei um VACUUM, depois um REINDEX. O REINDEX também estava bloqueando, porque também não foi possível obter o bloqueio. Após algumas investigações, o problema foi mais simples do que imaginamos. Havia um <IDLE> restante de uma semana com uma transação aberta O problema foi resolvido, obrigado para tudo, as informações foram muito úteis.