Atualizar a visualização materializada incrementalmente no PostgreSQL

33

É possível atualizar uma visão materializada incrementalmente no PostgreSQL, ou seja, apenas para os dados novos ou que foram alterados?

Considere esta tabela e visão materializada:

CREATE TABLE graph (
   xaxis integer NOT NULL,
   value integer NOT NULL,
);

CREATE MATERIALIZED VIEW graph_avg AS 
SELECT xaxis, AVG(value)
FROM graph
GROUP BY xaxis

Periodicamente, novos valores são adicionados graphou um valor existente é atualizado. Desejo atualizar a exibição a graph_avgcada duas horas apenas para os valores que foram atualizados. No entanto, no PostgreSQL 9.3, a tabela inteira é atualizada. Isso consome bastante tempo. A próxima versão 9.4 permite CONCURRENTatualização, mas ainda atualiza a exibição inteira. Com centenas de milhões de linhas, isso leva alguns minutos.

Qual é uma boa maneira de acompanhar os valores atualizados e novos e atualizar a exibição apenas parcialmente?

user4150760
fonte

Respostas:

22

Você sempre pode implementar sua própria tabela, servindo como "visualização materializada". Isso é o que você tinha que fazer antes de MATERIALIZED VIEWser implementado no Postgres 9.3 de qualquer maneira.

Por exemplo, você pode criar uma planície VIEW:

CREATE VIEW graph_avg_view AS 
SELECT xaxis, AVG(value) AS avg_val
FROM   graph
GROUP  BY xaxis;

E materialize o resultado como um todo uma vez ou sempre que precisar começar de novo:

CREATE TABLE graph_avg AS
SELECT * FROM graph_avg_view

(Ou use a SELECTinstrução diretamente, sem criar a VIEW.)
Então, dependendo dos detalhes não revelados do seu caso de uso, você pode DELETE/ UPDATE/ INSERTalterar manualmente.

Uma instrução DML básica com CTEs modificadores de dados para sua tabela, como é :

Assumindo Ninguém tenta outra pessoa para escrever a graph_avgconcorrentemente (leitura não é problema):

WITH del AS (
   DELETE FROM graph_avg t
   WHERE  NOT EXISTS (SELECT 1 FROM graph_avg_view v WHERE v.xaxis = v.xaxis);
   )
, upd AS (
   UPDATE graph_avg t
   FROM   graph_avg_view v
   WHERE  t.xaxis = v.xaxis
   AND    t.avg_val <> v.avg_val
   )
INSERT INTO graph_avg t
SELECT *
FROM   graph_avg_view v
LEFT   JOIN graph_avg t USING (xaxis)
WHERE  t.xaxis IS NULL;

Mas isso provavelmente deve ser otimizado.

Receita básica:

  • Adicione uma timestampcoluna com padrão now()à sua tabela base. Vamos chamá-lo ts.
    • Se você tiver atualizações, adicione um gatilho para definir o carimbo de data / hora atual a cada atualização que seja alterada xaxisou value.
  • Crie uma pequena tabela para lembrar o carimbo de data e hora do seu instantâneo mais recente. Vamos chamá-lo mv:

    CREATE TABLE mv (
       tbl text PRIMARY KEY
     , ts timestamp NOT NULL DEFAULT '-infinity'
    ); -- possibly more details
  • Crie este índice parcial, com várias colunas:

    CREATE INDEX graph_mv_latest ON graph (xaxis, value)
    WHERE  ts >= '-infinity';
  • Use o registro de data e hora do último instantâneo como predicado em suas consultas para atualizar o instantâneo com o uso perfeito do índice.

  • No final da transação, descarte o índice e recrie-o com o registro de data e hora da transação, substituindo o registro de data e hora no predicado do índice (inicialmente '-infinity'), que você também salva na sua tabela. Tudo em uma transação.

  • Observe que o índice parcial é ótimo para cobrir INSERTe UPDATEoperações, mas não DELETE. Para cobrir isso, você precisa considerar a tabela inteira. Tudo depende dos requisitos exatos.

Erwin Brandstetter
fonte
Obrigado pela clareza das visualizações materializadas e por sugerir uma resposta alternativa.
user4150760
13

Atualização simultânea (Postgres 9.4)

Embora não seja uma atualização incremental conforme solicitado, o Postgres 9.4 fornece um novo recurso de atualização simultânea .

Para citar o documento…

Antes do PostgreSQL 9.4, atualizar uma exibição materializada significava bloquear toda a tabela e, portanto, impedir que qualquer consulta fosse realizada; se uma atualização levasse muito tempo para adquirir o bloqueio exclusivo (enquanto aguarda as consultas para finalizá-lo), por sua vez está mantendo consultas subsequentes. Agora isso pode ser mitigado com a palavra-chave CONCURRENTLY:

 postgres=# REFRESH MATERIALIZED VIEW CONCURRENTLY mv_data;

Um índice exclusivo precisará existir na exibição materializada. Em vez de bloquear a visualização materializada, ela cria uma versão atualizada temporária, compara as duas versões e aplica INSERTs e DELETEs na visualização materializada para aplicar a diferença. Isso significa que as consultas ainda podem usar a visualização materializada enquanto estão sendo atualizadas. Diferentemente de sua forma não simultânea, as tuplas não são congeladas e precisam de VACUUM devido aos DELETEs mencionados acima que deixarão as tuplas mortas para trás.

Esta atualização simultânea ainda está executando uma nova consulta completa (não incremental). Portanto, CONCURRENTLY não economiza no tempo total de computação, apenas minimiza o tempo que sua visualização materializada fica indisponível para uso durante sua atualização.

Basil Bourque
fonte
11
Por um momento, fiquei empolgado até ler atentamente. it instead creates a temporary updated version of it...compares the two versions- Isso significa que a versão atualizada temporária ainda é uma computação completa e aplica a diferença à visualização existente. Então, essencialmente, ainda estou refazendo TODOS os cálculos, mas apenas na tabela temporária.
user4150760
5
Ah, é verdade, CONCURRENTLYnão economiza no tempo total de computação, apenas minimiza o tempo que sua visualização materializada fica indisponível para uso durante sua atualização.
Basil Bourque