web-dev-qa-db-fra.com

Comment puis-je m'assurer qu'une vue matérialisée est toujours à jour?

J'aurai besoin d'invoquer REFRESH MATERIALIZED VIEW à chaque modification des tables impliquées, non? Je suis surpris de ne pas trouver beaucoup de discussion à ce sujet sur le Web.

Comment devrais-je m'y prendre?

Je pense que la moitié supérieure de la réponse est ce que je recherche: https://stackoverflow.com/a/23963969/168143

Y at-il des dangers à cela? Si la mise à jour de la vue échoue, la transaction sur la mise à jour, l'insertion, etc. invoquée, sera-t-elle annulée? (c'est ce que je veux ... je pense)

29
John Bachir

J'aurai besoin d'invoquer REFRESH MATERIALIZED VIEW à chaque modification des tables impliquées, non?

Oui, PostgreSQL seul ne l'appellera jamais automatiquement, vous devez le faire d'une certaine manière.

Comment devrais-je m'y prendre?

De nombreuses façons d'y parvenir. Avant de donner quelques exemples, gardez à l’esprit que REFRESH MATERIALIZED VIEW commande bloque la vue en mode AccessExclusive. Par conséquent, même s’il fonctionne, vous ne pouvez même pas faire SELECT sur la table.

Toutefois, si vous utilisez la version 9.4 ou une version plus récente, vous pouvez lui attribuer l’option CONCURRENTLY:

REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;

Cela va acquérir un ExclusiveLock, et ne bloquera pas les requêtes SELECT, mais peut avoir un temps système plus important (dépend de la quantité de données modifiées, si peu de lignes ont été modifiées, cela pourrait être plus rapide). Bien que vous ne puissiez toujours pas exécuter deux commandes REFRESH simultanément.

Actualiser manuellement

C'est une option à considérer. Particulièrement dans les cas de chargement de données ou de mises à jour par lots (par exemple, un système ne chargeant que des tonnes d'informations/données après de longues périodes de temps), il est courant de disposer d'opérations permettant de modifier ou de traiter les données afin de pouvoir inclure simplement une opération REFRESH à la fin de celui-ci.

Planification de l'opération REFRESH

La première option, largement utilisée, consiste à utiliser un système de planification pour appeler l'actualisation. Vous pouvez par exemple configurer la même chose dans un travail cron:

*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"

Et ensuite, votre vue matérialisée sera rafraîchie toutes les 30 minutes.

Considérations

Cette option est vraiment bonne, spécialement avec l'option CONCURRENTLY, mais uniquement si vous pouvez accepter que les données ne soient pas à jour à 100%. N'oubliez pas que même avec ou sans CONCURRENTLY, la commande REFRESH doit exécuter la requête entière. Vous devez donc prendre le temps nécessaire pour exécuter la requête interne avant de prendre en compte le temps de planification de la REFRESH.

Rafraîchir avec un déclencheur

Une autre option consiste à appeler le REFRESH MATERIALIZED VIEW dans une fonction de déclenchement, comme ceci:

CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
    RETURN NULL;
END;
$$;

Ensuite, dans toute table impliquant des modifications de la vue, vous effectuez les opérations suivantes:

CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();

Considérations

Il présente des pièges critiques pour les performances et la simultanéité:

  1. Toute opération INSERT/UPDATE/DELETE devra exécuter la requête (ce qui est possible lentement si vous envisagez d'utiliser MV);
  2. Même avec CONCURRENTLY, une REFRESH en bloque une autre, de sorte que tout INSERT/UPDATE/DELETE sur les tables impliquées sera sérialisé.

La seule situation à laquelle je puisse penser comme une bonne idée est de savoir si les changements sont vraiment rares.

Actualiser avec LISTEN/NOTIFY

Le problème avec l’option précédente est qu’il est synchrone et impose une surcharge importante à chaque opération. Pour améliorer cela, vous pouvez utiliser un déclencheur comme avant, mais cela n’appelle que l’opération NOTIFY :

CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    NOTIFY refresh_mv, 'my_mv';
    RETURN NULL;
END;
$$;

Vous pouvez alors créer une application qui reste connectée et utilise LISTEN opération pour identifier la nécessité d’appeler REFRESH. Un bon projet que vous pouvez utiliser pour tester ceci est pgsidekick , avec ce projet, vous pouvez utiliser un script Shell pour exécuter LISTEN, de sorte que vous puissiez planifier la REFRESH comme suit:

pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"

Ou utilisez pglater (également à l'intérieur de pgsidekick) pour vous assurer de ne pas appeler REFRESH très souvent. Par exemple, vous pouvez utiliser le déclencheur suivant pour le rendre REFRESH, mais dans un délai d'une minute (60 secondes):

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;
$$;

Ainsi, il n'appellera pas REFRESH à moins de 60 secondes d'intervalle, et si vous NOTIFY plusieurs fois en moins de 60 secondes, la REFRESH sera déclenchée une seule fois.

Considérations

En tant qu'option cron, celle-ci n'est utile que si vous pouvez utiliser quelques données obsolètes, mais cela présente l'avantage que la REFRESH est appelée uniquement lorsque cela est vraiment nécessaire, vous avez ainsi moins de temps système et les données sont mises à jour plus précisément. au besoin.

OBS: Je n'ai pas encore vraiment essayé les codes et les exemples. Si quelqu'un trouve une erreur, une faute de frappe ou l'essaie et fonctionne (ou non), faites-le-moi savoir.

68
MatheusOl

Permettez-moi de souligner trois éléments de la réponse précédente de MatheusOl - la technologie avancée.

  1. En tant que dernier élément du tableau long_options, il doit inclure l’élément "{0, 0, 0, 0}" indiqué à https://linux.die.net/man/3/getopt_long par la phrase "The last élément du tableau doit être rempli de zéros. " Donc, il faut lire -

    static struct option long_options[] =     {
          //......
          {"help", no_argument, NULL, '?'},
          {0, 0, 0, 0} 
    };
    
  2. Sur le malloc/chose gratuite - un libre (pour char écouter = malloc (...);) est manquant. Quoi qu'il en soit, malloc a provoqué un blocage du processus pglater sous CentOS (mais pas sous Ubuntu - je ne sais pas pourquoi). Donc, je recommande d'utiliser char array et d'attribuer le nom du tableau au pointeur char (à la fois char et char **). Vous devez souvent forcer la conversion de type pendant cette opération (affectation de pointeur).

    char block4[100];
    ...
    password_Prompt = block4;
    ...
    char block1[500];
    const char **keywords = (const char **)&block1;
    ...
    char block3[300];
    char *listen = block3;
    sprintf(listen, "listen %s", id);
    PQfreemem(id);
    res = PQexec(db, listen);
    
  3. Utilisez le tableau ci-dessous pour calculer le délai d'expiration où md correspond à mature_duration, qui correspond à la différence de temps entre le dernier point d'actualisation (lr) et l'heure actuelle.

    quand md> = callback_delay (cd) ==> délai d'attente: 0

    quand md + PING_INTERVAL> = cd ==> délai d'attente: cd-md [= cd- (maintenant-lr)]

    lorsque md + PING_INTERVAL <cd ==> timeout: PI

Pour implémenter cet algorithme (3ème point), vous devez initialiser 'lr' comme suit -

res = PQexec(db, command);
latest_refresh = time(0);
if (PQresultStatus(res) == PGRES_COMMAND_OK) {
0
Park JongBum