PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como posso garantir que uma visão materializada esteja sempre atualizada?


Precisarei invocar REFRESH MATERIALIZED VIEW em cada mudança nas tabelas envolvidas, certo?

Sim, o PostgreSQL por si só nunca o chamará automaticamente, você precisa fazer isso de alguma forma.

Como devo proceder para fazer isso?

Muitas maneiras de conseguir isso. Antes de dar alguns exemplos, tenha em mente que REFRESH MATERIALIZED VIEW O comando bloqueia a visualização no modo AccessExclusive, portanto, enquanto está funcionando, você não pode nem fazer SELECT na mesa.

Embora, se você estiver na versão 9.4 ou mais recente, você pode fornecer o CONCURRENTLY opção:
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;

Isso adquirirá um bloqueio exclusivo e não bloqueará SELECT consultas, mas pode ter uma sobrecarga maior (depende da quantidade de dados alterados, se poucas linhas foram alteradas, pode ser mais rápido). Embora você ainda não possa executar dois REFRESH comandos simultaneamente.

Atualizar manualmente


É uma opção a considerar. Especialmente em casos de carregamento de dados ou atualizações em lote (por exemplo, um sistema que carrega apenas toneladas de informações/dados após longos períodos de tempo) é comum ter operações no final para modificar ou processar os dados, então você pode simplesmente incluir um REFRESH operação no final dela.

Programando a operação REFRESH


A primeira e amplamente utilizada opção é usar algum sistema de agendamento para invocar a atualização, por exemplo, você pode configurar o mesmo em um cron job:
*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"

E então sua visão materializada será atualizada a cada 30 minutos.

Considerações


Esta opção é muito boa, especialmente com CONCURRENTLY opção, mas somente se você puder aceitar que os dados não estejam 100% atualizados o tempo todo. Tenha em mente que mesmo com ou sem CONCURRENTLY , o REFRESH O comando precisa executar a consulta inteira, portanto, você deve levar o tempo necessário para executar a consulta interna antes de considerar o tempo para agendar o REFRESH .

Atualizando com um gatilho


Outra opção é chamar a REFRESH MATERIALIZED VIEW em uma função de gatilho, assim:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
    RETURN NULL;
END;
$$;

Então, em qualquer tabela que envolva mudanças na visão, você faz:
CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();

Considerações


Ele tem algumas armadilhas críticas para desempenho e simultaneidade:
  1. Qualquer operação INSERT/UPDATE/DELETE terá que executar a consulta (o que pode ser lento se você estiver considerando MV);
  2. Mesmo com CONCURRENTLY , um REFRESH ainda bloqueia outro, então qualquer INSERT/UPDATE/DELETE nas tabelas envolvidas será serializado.

A única situação que posso pensar que é uma boa ideia é se as mudanças forem realmente raras.

Atualize usando LISTEN/NOTIFY


O problema com a opção anterior é que ela é síncrona e impõe uma grande sobrecarga em cada operação. Para melhorar isso, você pode usar um gatilho como antes, mas que só chama um NOTIFY Operação:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    NOTIFY refresh_mv, 'my_mv';
    RETURN NULL;
END;
$$;

Então você pode construir um aplicativo que se mantém conectado e usa LISTEN operação para identificar a necessidade de chamar REFRESH . Um bom projeto que você pode usar para testar isso é o pgsidekick, com este projeto você pode usar o shell script para fazer LISTEN , para que você possa agendar a REFRESH como:
pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"

Ou use pglater (também dentro do pgsidekick ) para garantir que você não chame REFRESH muitas vezes. Por exemplo, você pode usar o seguinte gatilho para torná-lo REFRESH , mas dentro de 1 minuto (60 segundos):
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    NOTIFY refresh_mv, '60 REFRESH MATERIALIZED VIEW CONCURRENLTY my_mv';
    RETURN NULL;
END;
$$;

Portanto, não chamará REFRESH em menos de 60 segundos de intervalo, e também se você NOTIFY muitas vezes em menos de 60 segundos, o REFRESH será acionado apenas uma vez.

Considerações


Como a opção cron, esta também é boa apenas se você puder usar um pouco de dados obsoletos, mas isso tem a vantagem de que o REFRESH é chamado apenas quando realmente necessário, para que você tenha menos sobrecarga e também os dados sejam atualizados mais perto de quando necessário.

OBS:Eu realmente não testei os códigos e exemplos ainda, então se alguém encontrar algum erro, erro de digitação ou tentar e funcionar (ou não), por favor me avise.