web-dev-qa-db-fra.com

Rendre la base de données Postgres temporairement en lecture seule (pour effectuer des instantanés de volume)

Le mécanisme de sauvegarde intégré de PostgreSQL n'est pas toujours très approprié. Parfois, vous souhaitez mettre l'application dans un état au repos , car elle contient des données externes avec lesquelles vous souhaitez sauvegarder en même temps que vous sauvegardez les données PG. Mais la seule façon de mettre l'application dans un état de repos est de "verrouiller" également la base de données. PG ne dispose pas d'un mécanisme de verrouillage à l'échelle de la base de données ou à l'échelle du cluster. Mettre PG en lecture seule serait un élément de la solution suivante:

  1. Mettre en veille les données d'application (désactiver les connexions)
  2. Mettre la base de données au repos (en la rendant en lecture seule)
  3. Effectuer un point de contrôle PG ou pg_xlog_switch ()
  4. Créer un instantané des volumes d'application et de données
  5. Reprenez la base de données (faites-la à nouveau RW)
  6. Reprendre l'application
  7. Sauvegardez les instantanés
7
Otheus

Après avoir récupéré des réponses ailleurs sur Internet, j'ai imaginé une solution. Les autres réponses étaient en elles-mêmes incomplètes. Je présente donc une réponse ici dans l'espoir qu'elle profite aux autres.

La stratégie

  1. Désactivez les connexions à la base de données (pas au cluster).
  2. Définissez le paramètre default_transaction_read_only De la base de données sur true.
  3. Mettez fin aux connexions existantes à cette base de données.
  4. Réactivez les connexions (en lecture seule).

Une fois cela fait, vous (dans ma solution):

  1. Effectuez le CHECKPOINT (je pense que c'est le plus sûr, mais une pg_xlog_switch() serait appropriée pour les serveurs à très forte charge)
  2. Prenez l'instantané du volume
  3. Inversez les étapes précédentes. (Mais c'est délicat!)

Pièges

  1. Mettre fin aux connexions alors qu'elles sont en cours de transaction est probablement une mauvaise idée. Mieux vaut tuer les connexions inactives, attendre quelques secondes, puis tuer les connexions inactives, attendre encore quelques secondes, répéter jusqu'à ce qu'elles soient toutes disparues.
  2. À un moment donné, vous devrez supprimer les requêtes ouvertes/bloquées ou abandonner la sauvegarde.
  3. Au début d'une transaction de session, Postgresql prend une sorte de snapshot de la table de processus. Vous devez réinitialiser cet instantané chaque fois que vous allez vérifier si les processus indésirables sont toujours là. Voir pg_stat_clear_snapshot()
  4. La restauration de l'état de lecture-écriture est pas si simple. Si des connexions en lecture seule existent maintenant, vous devez les interrompre pour que le nouveau statut de lecture-écriture prenne effet. Mais de nouvelles connexions pourraient arriver tout en tuant les existantes. Encore une fois, vous devez

    1. Désactiver les connexions à la base de données
    2. changer le statut default_transaction_read_only en false
    3. tuer les connexions existantes
    4. Réactiver (r/w) les connexions à la base de données

Stratégie alternative

Une autre stratégie consiste à modifier les autorisations sur le rôle utilisé par l'application. Cela peut être assez compliqué et moins général.

Par exemple, vous devez révoquer/réaccorder non seulement des tables, mais des séquences, des objets volumineux et probablement le schéma lui-même. De plus, quel est exactement le comportement des connexions existantes lorsque vous modifiez l'accès? Probablement aucun impact, ce qui signifie que vous devez également tuer ces backends. Enfin, supposons que l'application dispose d'un accès en lecture-écriture à la plupart des tables, mais pas à d'autres dans le schéma. Vous devez vous assurer que votre ré-octroi n'inclut pas également ces objets.

Une autre possibilité consiste à VERROUILLER toutes les tables, en interrogeant le catalogue et en effectuant une requête dynamique. Cela semblait périlleux à mes goûts.

La mise en oeuvre

Pause_service

Le nom de l'instance de base de données est 'gitlabhq' et le nom d'utilisateur de l'application est 'gitlab'. Remplacez-le par le vôtre:

  psql -Upostgres  <<'PAUSE_DB'
    -- 1. disable new connections
    alter database gitlabhq_production with allow_connections = off;
    -- 2. Make DB read-only
    alter database gitlabhq set default_transaction_read_only = true;
    -- 3. Inobtrusively but safely terminate current connections
    DO $X$ BEGIN
        -- kill open idle connections, try up to 9x. Last time, kill regardless
        FOR i IN 1..10 LOOP
          PERFORM pg_terminate_backend(pid) from pg_stat_activity where usename = 'gitlab'
            and (i >= 10 OR state in ('idle', 'disabled' ));
          PERFORM pg_stat_clear_snapshot();
          EXIT WHEN NOT EXISTS ( select pid from pg_stat_activity where usename = 'gitlab' );
          RAISE NOTICE 'pg backends still open: sleeping 2 seconds';
          PERFORM pg_sleep(2);
          PERFORM pg_stat_clear_snapshot();
        END LOOP;
        -- send notice if still open connections
        IF EXISTS ( select pid from pg_stat_activity where usename = 'gitlab' ) THEN
            RAISE NOTICE 'Hung backends. Backup might not be 100%% consistent';
        END IF;
    END;$X$;
    -- 4. Allow read-only connections while checkpointing/snapshotting
    alter database gitlabhq with allow_connections = on;
    CHECKPOINT;

CV

    alter database gitlabhq_production with allow_connections = off;
    alter database gitlabhq set default_transaction_read_only = false;
    SELECT pg_stat_clear_snapshot();
    SELECT pg_terminate_backend(pid) from pg_stat_activity where usename = 'gitlab';
    alter database gitlabhq with allow_connections = on;

Il est possible que dans cette dernière étape, vous tuiez les requêtes en lecture seule/SELECT de longue durée, mais d'après mon expérience, ces requêtes de longue durée peuvent durer des minutes, voire des heures, et il est acceptable de les supprimer afin de garantir la disponibilité pour tous les autres.

9
Otheus

Je pense qu'il serait souhaitable d'avoir cette fonctionnalité en tant que fonctionnalité officielle de PostgreSQL, personnellement.

Le faire simplement

Si vous ne voulez pas vous salir les mains avec le codage C pour une extension PostgreSQL, vous pouvez simplement mettre un pool de connexions devant PostgreSQL. Comme pgBouncer.

pgBouncer a la capacité de suspendre l'activité de l'application intégrée . Cependant, pour le rendre très utile, vous devez vous connecter directement (pas via pgbouncer) et annuler les connexions actives une fois que vous en avez interrompu l'entrée. Juste select pg_terminate_backend(pid) from pg_stat_activity where pid <> pg_backend_pid().

Bien faire les choses

Si vous êtes prêt à vous salir les mains, vous pouvez le faire avec une extension C. L'extension doit:

  • Être chargé dans shared_preload_libraries Afin qu'il puisse enregistrer un petit segment de mémoire partagée statique avec un indicateur booléen comme db_is_locked .

  • Enregistrez un ProcessUtility_hook Et ExecutorStart_hook Qui teste l'indicateur est verrouillé dans shmem et, s'il est défini, dort dans une boucle WaitLatch jusqu'à ce qu'il voit que l'indicateur a été effacé à nouveau. (Vous pouvez éventuellement utiliser un crochet analyseur à la place).

  • Écrivez deux fonctions appelables SQL en C. L'une définit l'indicateur. Un autre efface le drapeau et itère via PGPROC en définissant le verrou de tous les processus utilisateur, afin qu'ils sachent se réveiller immédiatement.

  • Vous pouvez éventuellement écrire une troisième fonction qui, si l'indicateur est défini, itère via PGXACT pour trouver les transactions d'écriture ouvertes et leur signale de se terminer.

Tout cela a déjà été implémenté dans le cadre de l'extension BDR , mais cela fait partie d'un système beaucoup plus vaste. Vous pourriez très probablement extraire les parties pertinentes dans votre propre extension. Voir bdr_locks.c, bdr_commandfilter.c, bdr_executor.c, bdr.c, Etc.

Notez que cela ne rendra pas PostgreSQL en lecture seule sur le disque - le pointeur de contrôle continuera à fonctionner, le bgwriter continuera à fonctionner, l'archiveur continuera exécuter, etc. Il ne suffit donc pas de vous permettre de faire une sauvegarde de base de données sans un instantané du système de fichiers atomique ou pg_start_backup()/pg_stop_backup(). Mais cela convient à votre cas d'utilisation, interrompant l'activité de l'application au niveau de la base de données.

4
Craig Ringer