Às vezes, os bancos de dados PostgreSQL precisam importar grandes quantidades de dados em uma única ou em um número mínimo de etapas. Isso é comumente conhecido como importação de dados em massa, em que a fonte de dados geralmente é um ou mais arquivos grandes. Esse processo às vezes pode ser inaceitavelmente lento.
Há muitos motivos para esse desempenho ruim:índices, gatilhos, chaves estrangeiras, chaves primárias GUID ou até mesmo o WAL (Wallet Write Ahead) podem causar atrasos.
Neste artigo, abordaremos algumas dicas de práticas recomendadas para importação em massa de dados em bancos de dados PostgreSQL. No entanto, pode haver situações em que nenhuma dessas dicas será uma solução eficiente. Recomendamos que os leitores considerem os prós e os contras de qualquer método antes de aplicá-lo.
Dica 1:altere a tabela de destino para o modo não registrado
Para o PostgreSQL 9.5 e superior, a tabela de destino pode ser alterada primeiro para UNLOGGED e, em seguida, alterada de volta para LOGGED assim que os dados forem carregados:
ALTER TABLE <target table> SET UNLOGGED
<bulk data insert operations…>
ALTER TABLE <target table> LOGGED
O modo UNLOGGED garante que o PostgreSQL não envie operações de gravação de tabela para o Write Ahead Log (WAL). Isso pode tornar o processo de carregamento significativamente rápido. No entanto, como as operações não são registradas, os dados não podem ser recuperados se houver um travamento ou desligamento impuro do servidor durante o carregamento. O PostgreSQL truncará automaticamente qualquer tabela não logada assim que for reiniciado.
Além disso, as tabelas não registradas não são replicadas para servidores em espera. Nesses casos, as replicações existentes devem ser removidas antes do carregamento e recriadas após o carregamento. Dependendo do volume de dados no nó primário e do número de esperas, o tempo para recriar a replicação pode ser bastante longo e inaceitável pelos requisitos de alta disponibilidade.
Recomendamos as seguintes práticas recomendadas para inserção de dados em massa em tabelas não registradas:
- Fazer backup da tabela e dos dados antes de alterá-los para um modo não registrado
- Recriar qualquer replicação para servidores em espera após a conclusão do carregamento de dados
- Usar inserções em massa não registradas para tabelas que podem ser facilmente repovoadas (por exemplo, grandes tabelas de pesquisa ou tabelas de dimensão)
Dica 2:descarte e recrie índices
Os índices existentes podem causar atrasos significativos durante as inserções de dados em massa. Isso ocorre porque, à medida que cada linha é adicionada, a entrada de índice correspondente também deve ser atualizada.
Recomendamos descartar índices na tabela de destino sempre que possível antes de iniciar a inserção em massa e recriar os índices assim que o carregamento for concluído. Novamente, criar índices em tabelas grandes pode ser demorado, mas geralmente será mais rápido do que atualizar os índices durante o carregamento.
DROP INDEX <index_name1>, <index_name2> … <index_name_n>
<bulk data insert operations…>
CREATE INDEX <index_name> ON <target_table>(column1, …,column n)
Pode valer a pena aumentar temporariamente o maintenance_work_mem parâmetro de configuração antes de criar os índices. O aumento da memória de trabalho pode ajudar a criar os índices mais rapidamente.
Outra opção para jogar pelo seguro é fazer uma cópia da tabela de destino no mesmo banco de dados com dados e índices existentes. Essa tabela recém-copiada pode ser testada com inserção em massa para ambos os cenários:soltar e recriar índices ou atualizá-los dinamicamente. O método que produz melhor desempenho pode ser seguido para a tabela ao vivo.
Dica 3:descarte e recrie chaves estrangeiras
Assim como os índices, as restrições de chave estrangeira também podem afetar o desempenho do carregamento em massa. Isso ocorre porque cada chave estrangeira em cada linha inserida deve ser verificada quanto à existência de uma chave primária correspondente. Nos bastidores, o PostgreSQL usa um gatilho para realizar a verificação. Ao carregar um grande número de linhas, esse gatilho deve ser disparado para cada linha, aumentando a sobrecarga.
A menos que seja restrito por regras de negócios, recomendamos descartar todas as chaves estrangeiras da tabela de destino, carregar os dados em uma única transação e, em seguida, recriar as chaves estrangeiras após confirmar a transação.
ALTER TABLE <target_table>
DROP CONSTRAINT <foreign_key_constraint>
BEGIN TRANSACTION
<bulk data insert operations…>
COMMIT
ALTER TABLE <target_table>
ADD CONSTRAINT <foreign key constraint>
FOREIGN KEY (<foreign_key_field>)
REFERENCES <parent_table>(<primary key field>)...
Mais uma vez, aumentando o maintenance_work_mem O parâmetro de configuração pode melhorar o desempenho da recriação de restrições de chave estrangeira.
Dica 4:desative acionadores
Os gatilhos INSERT ou DELETE (se o processo de carregamento também envolver a exclusão de registros da tabela de destino) podem causar atrasos no carregamento de dados em massa. Isso ocorre porque cada gatilho terá lógica que precisa ser verificada e operações que precisam ser concluídas logo após cada linha ser INSERT ou DELETE.
Recomendamos desabilitar todos os gatilhos na tabela de destino antes do carregamento em massa de dados e ativá-los após a conclusão do carregamento. A desativação de TODOS os gatilhos também inclui gatilhos do sistema que impõem verificações de restrição de chave estrangeira.
ALTER TABLE <target table> DISABLE TRIGGER ALL
<bulk data insert operations…>
ALTER TABLE <target table> ENABLE TRIGGER ALL
Dica 5:use o comando COPY
Recomendamos usar o PostgreSQL COPIAR comando para carregar dados de um ou mais arquivos. COPY é otimizado para cargas de dados em massa. É mais eficiente do que executar um grande número de instruções INSERT ou mesmo INSERTS de vários valores.
COPY <target table> [( column1>, … , <column_n>)]
FROM '<file_name_and_path>'
WITH (<option1>, <option2>, … , <option_n>)
Outros benefícios de usar COPY incluem:
- Suporta importação de texto e arquivo binário
- É de natureza transacional
- Permite especificar a estrutura dos arquivos de entrada
- Ele pode carregar dados condicionalmente usando uma cláusula WHERE
Dica 6:use INSERT multivalorado
A execução de vários milhares ou centenas de milhares de instruções INSERT pode ser uma escolha ruim para carregamento de dados em massa. Isso porque cada comando INSERT individual deve ser analisado e preparado pelo otimizador de consulta, passar por toda a verificação de restrição, executado como uma transação separada e registrado no WAL. O uso de uma instrução INSERT única com vários valores pode economizar essa sobrecarga.
INSERT INTO <target_table> (<column1>, <column2>, …, <column_n>)
VALUES
(<value a>, <value b>, …, <value x>),
(<value 1>, <value 2>, …, <value n>),
(<value A>, <value B>, …, <value Z>),
(<value i>, <value ii>, …, <value L>),
...
O desempenho do INSERT multivalorado é afetado pelos índices existentes. Recomendamos descartar os índices antes de executar o comando e recriá-los posteriormente.
Outra área a ser observada é a quantidade de memória disponível para o PostgreSQL para executar INSERTs multivalorados. Quando um INSERT de vários valores é executado, um grande número de valores de entrada deve caber na RAM e, a menos que haja memória suficiente disponível, o processo pode falhar.
Recomendamos definir o effective_cache_size parâmetro para 50% e shared_buffer parâmetro para 25% da RAM total da máquina. Além disso, para ser seguro, ele executa uma série de INSERTs de vários valores com cada instrução com valores para 1.000 linhas.
Dica 7:execute ANALISAR
Isso não está relacionado à melhoria do desempenho da importação de dados em massa, mas é altamente recomendável executar o ANALISAR comando na tabela de destino imediatamente depois a importação em massa. Um grande número de novas linhas distorcerá significativamente a distribuição de dados em colunas e fará com que todas as estatísticas existentes na tabela fiquem desatualizadas. Quando o otimizador de consulta usa estatísticas obsoletas, o desempenho da consulta pode ser inaceitavelmente ruim. A execução do comando ANALYZE garantirá que todas as estatísticas existentes sejam atualizadas.
Considerações finais
A importação de dados em massa pode não acontecer todos os dias para um aplicativo de banco de dados, mas há um impacto no desempenho das consultas quando ele é executado. É por isso que é necessário minimizar o tempo de carregamento da melhor forma possível. Uma coisa que os DBAs podem fazer para minimizar qualquer surpresa é testar as otimizações de carga em um ambiente de desenvolvimento ou teste com especificações de servidor semelhantes e configurações do PostgreSQL. Cada cenário de carregamento de dados é diferente e é melhor experimentar cada método e encontrar aquele que funciona.