Dovrò invocare REFRESH MATERIALIZED VIEW
su ogni modifica ai tavoli coinvolti, giusto?
Sì, PostgreSQL da solo non lo chiamerà mai automaticamente, devi farlo in qualche modo.
Come devo fare per farlo?
Molti modi per raggiungere questo obiettivo. Prima di fare alcuni esempi, tieni presente che REFRESH MATERIALIZED VIEW
il comando blocca la visualizzazione in modalità AccessExclusive, quindi mentre funziona, non puoi nemmeno fare SELECT
sul tavolo.
Tuttavia, se sei nella versione 9.4 o successiva, puoi assegnargli il CONCURRENTLY
opzione:
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
Questo acquisirà un ExclusiveLock e non bloccherà SELECT
query, ma potrebbe avere un sovraccarico maggiore (dipende dalla quantità di dati modificati, se sono state modificate poche righe, potrebbe essere più veloce). Anche se non puoi ancora eseguire due REFRESH
comandi contemporaneamente.
Aggiorna manualmente
È un'opzione da considerare. Specialmente nei casi di caricamento dati o aggiornamenti batch (ad es. un sistema che carica solo tonnellate di informazioni/dati dopo lunghi periodi di tempo) è comune avere operazioni alla fine per modificare o elaborare i dati, quindi puoi semplicemente includere un REFRESH
operazione alla fine.
Programmazione dell'operazione di AGGIORNAMENTO
La prima e ampiamente utilizzata opzione è utilizzare un sistema di pianificazione per invocare l'aggiornamento, ad esempio, puoi configurare simili in un lavoro cron:
*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"
E poi la tua vista materializzata verrà aggiornata ogni 30 minuti.
Considerazioni
Questa opzione è davvero buona, specialmente con CONCURRENTLY
opzione, ma solo se puoi accettare che i dati non siano sempre aggiornati al 100%. Tieni presente che anche con o senza CONCURRENTLY
, il REFRESH
il comando deve eseguire l'intera query, quindi devi prenderti il tempo necessario per eseguire la query interna prima di considerare il tempo per pianificare il REFRESH
.
Rinfresco con un trigger
Un'altra opzione è chiamare il REFRESH MATERIALIZED VIEW
in una funzione trigger, come questa:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
RETURN NULL;
END;
$$;
Quindi, in qualsiasi tabella che comporta modifiche alla vista, fai:
CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();
Considerazioni
Presenta alcune insidie critiche per le prestazioni e la concorrenza:
- Qualsiasi operazione INSERT/UPDATE/DELETE dovrà eseguire la query (che è possibile lenta se stai considerando MV);
- Anche con
CONCURRENTLY
, unREFRESH
ne blocca ancora un altro, quindi qualsiasi INSERT/UPDATE/DELETE sulle tabelle coinvolte verrà serializzato.
L'unica situazione che posso pensare che sia una buona idea è se i cambiamenti sono davvero rari.
Aggiorna utilizzando LISTEN/NOTIFY
Il problema con l'opzione precedente è che è sincrona e impone un grande sovraccarico ad ogni operazione. Per migliorare ciò, puoi utilizzare un trigger come prima, ma che chiama solo un NOTIFY
operazione:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NOTIFY refresh_mv, 'my_mv';
RETURN NULL;
END;
$$;
Quindi puoi creare un'applicazione che rimanga connessa e utilizzi LISTEN
operazione per identificare la necessità di chiamare REFRESH
. Un bel progetto che puoi usare per testarlo è pgsidekick, con questo progetto puoi usare lo script della shell per fare LISTEN
, così puoi programmare il REFRESH
come:
pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"
Oppure usa pglater
(anche all'interno di pgsidekick
) per assicurarti di non chiamare REFRESH
molto spesso. Ad esempio, puoi utilizzare il seguente trigger per renderlo REFRESH
, ma entro 1 minuto (60 secondi):
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;
$$;
Quindi non chiamerà REFRESH
in meno di 60 secondi di distanza, e anche se NOTIFY
molte volte in meno di 60 secondi, il REFRESH
verrà attivato solo una volta.
Considerazioni
Come l'opzione cron, anche questa va bene solo se puoi mettere a nudo un po' di dati obsoleti, ma questo ha il vantaggio che REFRESH
viene chiamato solo quando realmente necessario, quindi hai meno sovraccarico e inoltre i dati vengono aggiornati più vicino a quando necessario.
OBS:Non ho ancora provato i codici e gli esempi, quindi se qualcuno trova un errore, un errore di battitura o lo prova e funziona (o meno), per favore fatemelo sapere.