Actualiser progressivement la vue matérialisée dans PostgreSQL

33

Est-il possible d'actualiser une vue matérialisée de manière incrémentielle dans PostgreSQL, c'est-à-dire uniquement pour les données nouvelles ou modifiées?

Considérez ce tableau et vue matérialisée:

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

Périodiquement, de nouvelles valeurs sont ajoutées graphou une valeur existante est mise à jour. Je souhaite actualiser la vue graph_avgtoutes les deux heures uniquement pour les valeurs mises à jour. Cependant, dans PostgreSQL 9.3, la table entière est actualisée. Ceci prend beaucoup de temps. La prochaine version 9.4 autorise la CONCURRENTmise à jour mais actualise toujours la vue entière. Avec des centaines de millions de lignes, cela prend quelques minutes.

Quel est un bon moyen de garder trace des nouvelles valeurs et de ne mettre à jour que partiellement la vue?

utilisateur4150760
la source

Réponses:

22

Vous pouvez toujours implémenter votre propre table servant de "vue matérialisée". C'est ce que vous deviez faire avant MATERIALIZED VIEWétait implémenté dans Postgres 9.3 de toute façon.

Par exemple, vous pouvez créer une plaine VIEW:

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

Et matérialisez le résultat dans son ensemble une fois ou chaque fois que vous devez recommencer:

CREATE TABLE graph_avg AS
SELECT * FROM graph_avg_view

(Ou utilisez SELECTdirectement l' instruction, sans créer de VIEW.)
Ensuite, en fonction des détails non divulgués de votre cas d'utilisation, vous pouvez DELETE/ UPDATE/ INSERTmodifier les modifications manuellement.

Une instruction DML de base avec des CTE modifiant les données pour votre table en l'état :

En supposant que personne d' autre ne tente d' écrire à en graph_avgmême temps (lecture est pas un problème):

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;

Mais cela devrait probablement être optimisé.

Recette de base:

  • Ajouter une timestampcolonne avec défaut now()à votre table de base. Appelons ça ts.
    • Si vous avez des mises à jour, ajoutez un déclencheur pour définir l'horodatage actuel avec chaque mise à jour modifiant l'un xaxisou l' autre value.
  • Créez un petit tableau pour vous rappeler l'horodatage de votre dernier instantané. Appelons ça mv:

    CREATE TABLE mv (
       tbl text PRIMARY KEY
     , ts timestamp NOT NULL DEFAULT '-infinity'
    ); -- possibly more details
    
  • Créez cet index partiel multicolonne:

    CREATE INDEX graph_mv_latest ON graph (xaxis, value)
    WHERE  ts >= '-infinity';
    
  • Utilisez l'horodatage du dernier instantané en tant que prédicat dans vos requêtes pour actualiser celui-ci avec une utilisation d'index parfaite.

  • À la fin de la transaction, supprimez l'index et recréez-le avec l'horodatage de la transaction en remplaçant l'horodatage dans le prédicat d'index (initialement '-infinity'), que vous avez également enregistré dans votre table. Tout en une transaction.

  • Notez que l'index partiel est génial pour couvrir INSERTet les UPDATEopérations, mais pas DELETE. Pour couvrir cela, vous devez considérer l'ensemble du tableau. Tout dépend des exigences exactes.

Erwin Brandstetter
la source
Merci pour la clarté des points de vue matérialisés et pour avoir suggéré une autre réponse.
user4150760
13

Mise à jour simultanée (Postgres 9.4)

Bien qu'il ne s'agisse pas d'une mise à jour incrémentielle comme vous l'avez demandé, Postgres 9.4 fournit une nouvelle fonctionnalité de mise à jour simultanée .

Pour citer le doc…

Avant PostgreSQL 9.4, actualiser une vue matérialisée signifiait verrouiller l’ensemble de la table, empêchant ainsi toute interrogation de celle-ci. bloque les requêtes suivantes. Cela peut maintenant être atténué avec le mot clé CONCURRENTLY:

 postgres=# REFRESH MATERIALIZED VIEW CONCURRENTLY mv_data;

Un index unique devra cependant exister sur la vue matérialisée. Au lieu de verrouiller la vue matérialisée, il en crée une version mise à jour temporaire, compare les deux versions, puis applique les actions INSERT et DELETE à la vue matérialisée pour appliquer la différence. Cela signifie que les requêtes peuvent toujours utiliser la vue matérialisée pendant sa mise à jour. Contrairement à sa forme non concurrente, les n-uplets ne sont pas figés, et il faut VACUUMing en raison de la suppression de SUPPLÉ mentionnée ci-dessus qui laissera des nuplets morts.

Cette mise à jour simultanée effectue toujours une nouvelle requête complète (non incrémentielle). Donc, de manière concourante, vous ne gagnez pas du temps de calcul global, cela minimise simplement le temps pendant lequel votre vue matérialisée n'est pas disponible pour être utilisée lors de sa mise à jour.

Basil Bourque
la source
11
Pendant un moment, j'étais excité jusqu'à ce que je lise attentivement. it instead creates a temporary updated version of it...compares the two versions- Cela signifie que la version mise à jour temporaire est toujours un calcul complet, puis applique la différence à la vue existante. Donc, essentiellement, je suis encore en train de refaire TOUS les calculs, mais juste dans la table temporaire.
user4150760
5
Ah, vrai, CONCURRENTLYn’économise pas sur le temps de calcul global, il minimise simplement le temps pendant lequel votre vue matérialisée n’est pas disponible pendant sa mise à jour.
Basil Bourque