Tendré que invocar REFRESH MATERIALIZED VIEW
en cada cambio en las tablas involucradas, ¿verdad?
Sí, PostgreSQL por sí solo nunca lo llamará automáticamente, debe hacerlo de alguna manera.
¿Cómo debo hacer esto?
Muchas maneras de lograr esto. Antes de dar algunos ejemplos, tenga en cuenta que REFRESH MATERIALIZED VIEW
El comando bloquea la vista en el modo AccessExclusive, por lo que mientras está funcionando, ni siquiera puede hacer SELECT
sobre la mesa.
Aunque, si está en la versión 9.4 o más reciente, puede darle el CONCURRENTLY
opción:
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
Esto adquirirá un ExclusiveLock y no bloqueará SELECT
consultas, pero puede tener una sobrecarga mayor (depende de la cantidad de datos cambiados, si se han cambiado pocas filas, entonces podría ser más rápido). Aunque aún no puede ejecutar dos REFRESH
comandos al mismo tiempo.
Actualizar manualmente
Es una opción a tener en cuenta. Especialmente en casos de carga de datos o actualizaciones por lotes (por ejemplo, un sistema que solo carga toneladas de información/datos después de largos períodos de tiempo) es común tener operaciones al final para modificar o procesar los datos, por lo que puede incluir un REFRESH
operación al final de la misma.
Programación de la operación REFRESH
La primera y más utilizada opción es usar algún sistema de programación para invocar la actualización, por ejemplo, puede configurar algo similar en un trabajo cron:
*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"
Y luego su vista materializada se actualizará cada 30 minutos.
Consideraciones
Esta opción es realmente buena, especialmente con CONCURRENTLY
opción, pero solo si puede aceptar que los datos no estén 100% actualizados todo el tiempo. Tenga en cuenta que incluso con o sin CONCURRENTLY
, el REFRESH
el comando necesita ejecutar la consulta completa, por lo que debe tomarse el tiempo necesario para ejecutar la consulta interna antes de considerar el tiempo para programar REFRESH
.
Refrescante con un gatillo
Otra opción es llamar al REFRESH MATERIALIZED VIEW
en una función de activación, como esta:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
RETURN NULL;
END;
$$;
Luego, en cualquier tabla que involucre cambios en la vista, usted hace:
CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();
Consideraciones
Tiene algunos inconvenientes críticos para el rendimiento y la concurrencia:
- Cualquier operación INSERTAR/ACTUALIZAR/ELIMINAR tendrá que ejecutar la consulta (lo que puede ser lento si está considerando MV);
- Incluso con
CONCURRENTLY
, unoREFRESH
aún bloquea otro, por lo que se serializará cualquier INSERCIÓN/ACTUALIZACIÓN/ELIMINACIÓN en las tablas involucradas.
La única situación en la que puedo pensar que es una buena idea es si los cambios son realmente raros.
Actualizar usando LISTEN/NOTIFY
El problema con la opción anterior es que es síncrona e impone una gran sobrecarga en cada operación. Para mejorar eso, puede usar un disparador como antes, pero eso solo llama a NOTIFY
operación:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NOTIFY refresh_mv, 'my_mv';
RETURN NULL;
END;
$$;
Entonces puede crear una aplicación que se mantenga conectada y use LISTEN
operación para identificar la necesidad de llamar a REFRESH
. Un buen proyecto que puede usar para probar esto es pgsidekick, con este proyecto puede usar el script de shell para hacer LISTEN
, para que pueda programar el REFRESH
como:
pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"
O usa pglater
(también dentro de pgsidekick
) para asegurarse de no llamar a REFRESH
muy a menudo. Por ejemplo, puede usar el siguiente disparador para que sea REFRESH
, pero 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;
$$;
Entonces no llamará a REFRESH
en menos de 60 segundos de diferencia, y además si NOTIFY
muchas veces en menos de 60 segundos, el REFRESH
se activará una sola vez.
Consideraciones
Al igual que la opción cron, esta también es buena solo si puede soportar un poco de datos obsoletos, pero tiene la ventaja de que REFRESH
se llama solo cuando es realmente necesario, por lo que tiene menos gastos generales y también los datos se actualizan más cerca de cuando es necesario.
OBS:Todavía no he probado los códigos y los ejemplos, así que si alguien encuentra un error, un error tipográfico o lo prueba y funciona (o no), házmelo saber.