Actualizar vista materalizada de forma incremental en PostgreSQL

33

¿Es posible actualizar una vista materializada de forma incremental en PostgreSQL, es decir, solo para los datos que son nuevos o que han cambiado?

Considere esta tabla y vista materializada:

CREATE TABLE graph (
   xaxis integer NOT NULL,
   value integer NOT NULL,
);

CREATE MATERIALIZED VIEW graph_avg AS 
SELECT xaxis, AVG(value)
FROM graph
GROUP BY xaxis

Periódicamente, se agregan nuevos valores grapho se actualiza un valor existente. Quiero actualizar la vista graph_avgcada dos horas solo para los valores que se han actualizado. Sin embargo, en PostgreSQL 9.3, toda la tabla se actualiza. Esto lleva bastante tiempo. La próxima versión 9.4 permite la CONCURRENTactualización, pero aún actualiza la vista completa. Con cientos de millones de filas, esto lleva unos minutos.

¿Cuál es una buena manera de realizar un seguimiento de los valores actualizados y nuevos y solo actualizar parcialmente la vista?

usuario4150760
fuente

Respuestas:

22

Siempre puede implementar su propia tabla que sirve como "vista materializada". Eso es lo que tenía que hacer antes de que MATERIALIZED VIEWse implementara en Postgres 9.3 de cualquier manera.

Por ejemplo, puedes crear un plano VIEW:

CREATE VIEW graph_avg_view AS 
SELECT xaxis, AVG(value) AS avg_val
FROM   graph
GROUP  BY xaxis;

Y materialice el resultado como un todo una vez o cada vez que necesite comenzar de nuevo:

CREATE TABLE graph_avg AS
SELECT * FROM graph_avg_view

(O use la SELECTdeclaración directamente, sin crear una VIEW.)
Luego, dependiendo de los detalles no revelados de su caso de uso, podría DELETE/ UPDATE/ INSERTcambiar manualmente.

Una declaración DML básica con CTE modificadores de datos para su tabla como es :

Suponiendo que nadie trate de otra persona a escribir al graph_avgmismo tiempo (la lectura no es ningún problema):

WITH del AS (
   DELETE FROM graph_avg t
   WHERE  NOT EXISTS (SELECT 1 FROM graph_avg_view v WHERE v.xaxis = v.xaxis);
   )
, upd AS (
   UPDATE graph_avg t
   FROM   graph_avg_view v
   WHERE  t.xaxis = v.xaxis
   AND    t.avg_val <> v.avg_val
   )
INSERT INTO graph_avg t
SELECT *
FROM   graph_avg_view v
LEFT   JOIN graph_avg t USING (xaxis)
WHERE  t.xaxis IS NULL;

Pero esto probablemente debería optimizarse.

Receta básica:

  • Agregue una timestampcolumna con valor predeterminado now()a su tabla base. Digamos que es ts.
    • Si tiene actualizaciones, agregue un activador para establecer la marca de tiempo actual con cada actualización que cambie xaxiso value.
  • Cree una tabla pequeña para recordar la marca de tiempo de su última instantánea. Llamémoslo mv:

    CREATE TABLE mv (
       tbl text PRIMARY KEY
     , ts timestamp NOT NULL DEFAULT '-infinity'
    ); -- possibly more details
  • Cree este índice parcial de varias columnas:

    CREATE INDEX graph_mv_latest ON graph (xaxis, value)
    WHERE  ts >= '-infinity';
  • Use la marca de tiempo de la última instantánea como predicado en sus consultas para actualizar la instantánea con un uso de índice perfecto.

  • Al final de la transacción, suelte el índice y vuelva a crearlo con la marca de tiempo de la transacción reemplazando la marca de tiempo en el predicado de índice (inicialmente '-infinity'), que también guarda en su tabla. Todo en una sola transacción.

  • Tenga en cuenta que el índice parcial es excelente para cubrir INSERTy UPDATEoperaciones, pero no DELETE. Para cubrir eso, debe considerar toda la tabla. Todo depende de los requisitos exactos.

Erwin Brandstetter
fuente
Gracias por la claridad en las opiniones materializadas y por sugerir una respuesta alternativa.
user4150760
13

Actualización concurrente (Postgres 9.4)

Si bien no es una actualización incremental como la solicitó, Postgres 9.4 proporciona una nueva función de actualización concurrente .

Para citar el documento ...

Antes de PostgreSQL 9.4, actualizar una vista materializada significaba bloquear toda la tabla y, por lo tanto, evitar cualquier cosa que la consultara, y si una actualización tardaba mucho en adquirir el bloqueo exclusivo (mientras espera que las consultas lo usen para finalizar), a su vez está retrasando consultas posteriores. Esto ahora se puede mitigar con la palabra clave CONCURRENTEMENTE:

 postgres=# REFRESH MATERIALIZED VIEW CONCURRENTLY mv_data;

Sin embargo, deberá existir un índice único en la vista materializada. En lugar de bloquear la vista materializada, crea una versión actualizada temporal de la misma, compara las dos versiones, luego aplica INSERT y DELETEs contra la vista materializada para aplicar la diferencia. Esto significa que las consultas aún pueden usar la vista materializada mientras se actualiza. A diferencia de su forma no concurrente, las tuplas no están congeladas, y necesita VACÍO debido a los DELETEs mencionados anteriormente que dejarán atrás las tuplas muertas.

Esta actualización concurrente todavía realiza una nueva consulta completa (no incremental). Por lo tanto, CONCURRENTEMENTE no ahorra en el tiempo de cálculo general, solo minimiza la cantidad de tiempo que su vista materializada no está disponible para su uso durante su actualización.

Albahaca Bourque
fuente
11
Por un momento estuve emocionado hasta que leí detenidamente. it instead creates a temporary updated version of it...compares the two versions- Esto significa que la versión actualizada temporal sigue siendo un cálculo completo, luego aplica la diferencia a la vista existente. Entonces, esencialmente, todavía estoy volviendo a hacer TODOS los cálculos, pero solo en la tabla temporal.
user4150760
55
Ah, es cierto, CONCURRENTLYno ahorra en el tiempo de cálculo general, solo minimiza la cantidad de tiempo que su vista materializada no está disponible para su uso durante su actualización.
Basil Bourque