sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo puedo asegurarme de que una vista materializada esté siempre actualizada?

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:

  1. Cualquier operación INSERTAR/ACTUALIZAR/ELIMINAR tendrá que ejecutar la consulta (lo que puede ser lento si está considerando MV);
  2. Incluso con CONCURRENTLY , uno REFRESH 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.