Precisarei invocarREFRESH 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:
- Qualquer operação INSERT/UPDATE/DELETE terá que executar a consulta (o que pode ser lento se você estiver considerando MV);
- Mesmo com
CONCURRENTLY
, umREFRESH
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.