Otimizando o desempenho da atualização em massa no PostgreSQL

37

Usando o PG 9.1 no Ubuntu 12.04.

Atualmente, leva até 24 horas para executarmos um grande conjunto de instruções UPDATE em um banco de dados, que estão no formato:

UPDATE table
SET field1 = constant1, field2 = constant2, ...
WHERE id = constid

(Estamos apenas substituindo os campos dos objetos identificados pelo ID.) Os valores vêm de uma fonte de dados externa (ainda não no banco de dados de uma tabela).

As tabelas possuem vários índices e nenhuma restrição de chave estrangeira. Nenhum COMMIT é feito até o fim.

Leva 2h para importar um pg_dumpbanco de dados inteiro. Parece uma linha de base que devemos razoavelmente atingir.

Com exceção da produção de um programa personalizado que, de alguma forma, reconstrói um conjunto de dados para a importação do PostgreSQL, existe alguma coisa que podemos fazer para trazer o desempenho UPDATE em massa mais próximo do da importação? (Essa é uma área que acreditamos que as árvores de mesclagem estruturadas em log lidam bem, mas estamos pensando se há algo que possamos fazer no PostgreSQL.)

Algumas ideias:

  • descartando todos os índices que não são de ID e reconstruindo depois?
  • aumentar os pontos de verificação, mas isso realmente ajuda a taxa de transferência sustentada a longo prazo?
  • usando as técnicas mencionadas aqui ? (Carregue novos dados como tabela e "mescle" dados antigos em que o ID não foi encontrado nos novos dados)

Basicamente, existem várias coisas para tentar e não temos certeza do que são mais eficazes ou se estamos ignorando outras coisas. Passaremos os próximos dias experimentando, mas pensamos em perguntar aqui também.

Eu tenho carga simultânea na tabela, mas é somente leitura.

Yang
fonte
Faltam informações cruciais na sua pergunta: sua versão do Postgres? De onde vêm os valores? Parece um arquivo fora do banco de dados, mas esclareça. Você tem carga simultânea na tabela de destino? Se sim, o que exatamente? Ou você pode dar ao luxo de largar e recriar? Sem chaves estrangeiras, ok - mas existem outros objetos dependentes, como visualizações? Edite sua pergunta com as informações ausentes. Não aperte em um comentário.
Erwin Brandstetter
@ErwinBrandstetter Obrigado, atualizou minha pergunta.
Yang
Suponho que você tenha verificado através de explain analyzeque está usando um índice para a pesquisa?
Rogerdpack #

Respostas:

45

Suposições

Como faltam informações no Q, assumirei:

  • Seus dados vêm de um arquivo no servidor de banco de dados.
  • Os dados são formatados como COPYsaída, com um único id por linha para corresponder à tabela de destino.
    Caso contrário, formate-o corretamente primeiro ou use as COPYopções para lidar com o formato.
  • Você está atualizando todas as linhas da tabela de destino ou a maioria delas.
  • Você pode deixar de lado e recriar a tabela de destino.
    Isso significa que não há acesso simultâneo. Caso contrário, considere esta resposta relacionada:
  • Não há objetos dependentes, exceto os índices.

Solução

Sugiro que você siga uma abordagem semelhante, conforme descrito no link da sua terceira bala . Com grandes otimizações.

Para criar a tabela temporária, existe uma maneira mais simples e rápida:

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

Um único grande UPDATEde uma tabela temporária dentro do banco de dados será mais rápido que as atualizações individuais de fora do banco de dados por várias ordens de magnitude.

No modelo MVCC do PostgreSQL , um UPDATEmeio para criar uma nova versão de linha e marcar a antiga como excluída. Isso é tão caro quanto um INSERTe um DELETEcombinado. Além disso, deixa você com muitas tuplas mortas. Como você está atualizando a tabela inteira de qualquer maneira, seria mais rápido, em geral, criar uma nova tabela e excluir a antiga.

Se você tiver RAM suficiente disponível, defina temp_buffers(apenas para esta sessão!) Alto o suficiente para manter a tabela temporária na RAM - antes de fazer qualquer outra coisa.

Para obter uma estimativa da quantidade de RAM necessária, execute um teste com uma pequena amostra e use as funções de tamanho de objeto db :

SELECT pg_size_pretty(pg_relation_size('tmp_tbl'));  -- complete size of table
SELECT pg_column_size(t) FROM tmp_tbl t LIMIT 10;  -- size of sample rows

Script completo

SET temp_buffers = '1GB';        -- example value

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

COPY tmp_tbl FROM '/absolute/path/to/file';

CREATE TABLE tbl_new AS
SELECT t.col1, t.col2, u.field1, u.field2
FROM   tbl     t
JOIN   tmp_tbl u USING (id);

-- Create indexes like in original table
ALTER TABLE tbl_new ADD PRIMARY KEY ...;
CREATE INDEX ... ON tbl_new (...);
CREATE INDEX ... ON tbl_new (...);

-- exclusive lock on tbl for a very brief time window!
DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;

DROP TABLE tmp_tbl; -- will also be dropped at end of session automatically

Carga simultânea

As operações simultâneas na tabela (que eu descartei nas suposições no início) aguardarão, quando a tabela estiver bloqueada no final e falharão assim que a transação for confirmada, porque o nome da tabela é resolvido imediatamente para seu OID, mas a nova tabela possui um OID diferente. A tabela permanece consistente, mas operações simultâneas podem receber uma exceção e precisam ser repetidas. Detalhes nesta resposta relacionada:

Rota UPDATE

Se você (tiver que) seguir o UPDATEcaminho, descarte qualquer índice que não seja necessário durante a atualização e recrie-o posteriormente. É muito mais barato criar um índice em uma peça do que atualizá-lo para cada linha individual. Isso também pode permitir atualizações QUENTES .

I descrito um procedimento semelhante utilizando UPDATEa esta resposta estreitamente relacionada no SO .

 

Erwin Brandstetter
fonte
11
Na verdade, estou apenas atualizando 20% das linhas na tabela de destino - não todas, mas uma parte grande o suficiente para que uma mesclagem seja provavelmente melhor do que a atualização aleatória procura.
Yang
11
@AryehLeibTaurog: Isso não deveria estar acontecendo, pois DROP TABLEtira um Access Exclusive Lock. De qualquer maneira, eu já listei o pré-requisito na parte superior da minha resposta: You can afford to drop and recreate the target table.pode ajudar a bloquear a tabela no início da transação. Sugiro que você inicie uma nova pergunta com todos os detalhes relevantes da sua situação, para que possamos chegar ao fundo disso.
Erwin Brandstetter
11
@ErwinBrandstetter Interessante. Parece depender da versão do servidor. Reproduzi o erro no 8.4 e 9.1 usando o adaptador psycopg2 e usando o cliente psql . Na 9.3, não há erro. Veja meus comentários no primeiro script. Não tenho certeza se há uma pergunta para postar aqui, mas pode valer a pena solicitar algumas informações em uma das listas do postgresql.
Aryeh Leib Taurog
11
Eu escrevi uma classe auxiliar simples em python para automatizar o processo.
Aryeh Leib Taurog
3
Resposta muito útil. Como uma pequena variação, pode-se criar a tabela temporária apenas com as colunas a serem atualizadas e as colunas de referência, excluir as colunas a serem atualizadas da tabela original e mesclar as tabelas usando CREATE TABLE tbl_new AS SELECT t.*, u.field1, u.field2 from tbl t NATURAL LEFT JOIN tmp_tbl u;, LEFT JOINpermitindo manter linhas para as quais não há atualização. Claro que NATURALpode ser alterado para qualquer válido USING()ou ON.
Skippy le Grand Gourou
2

Se os dados puderem ser disponibilizados em um arquivo estruturado, você poderá lê-los com um invólucro de dados externo e executar uma mesclagem na tabela de destino.

David Aldridge
fonte
3
O que você quer dizer com "mesclar na tabela de destino"? Por que usar o FDW é melhor que COPIAR em uma tabela temporária (conforme sugerido no terceiro marcador da pergunta original)?
Yang
"Mesclar" como na instrução sql MERGE. O uso do FDW permite fazer isso sem a etapa adicional de copiar os dados em uma tabela temporária. Suponho que você não esteja substituindo todo o conjunto de dados e que haja uma certa quantidade de dados no arquivo que não representaria uma alteração do conjunto de dados atual - se uma quantidade significativa for alterada, uma conclusão completa a substituição da mesa pode valer a pena.
David Aldridge
11
@DavidAldridge: Embora definido no padrão SQL: 2003, MERGEainda não está implementado no PostgreSQL . As implementações em outros RDBMS variam bastante. Considere as informações da tag para MERGEe UPSERT.
Erwin Brandstetter
@ ErwinBrandstetter [olhe] Ah, sim, sim. Bem, Merge é a cereja no topo do bolo, realmente, eu suponho. Acessar os dados sem a etapa de importação para tabela temporária é realmente o cerne da técnica do FDW.
David Aldridge