
La réplication n'est pas une sauvegarde. Ou pas? Voici comment nous avons utilisé la réplication différée pour la récupération en supprimant accidentellement des raccourcis.
Les spécialistes de l'infrastructure de GitLab sont responsables de l'exécution de GitLab.com , la plus grande instance de GitLab dans la nature. Il y a 3 millions d'utilisateurs et près de 7 millions de projets, et c'est l'un des plus grands sites SaaS open source avec une architecture dédiée. Sans le système de base de données PostgreSQL, l'infrastructure GitLab.com n'ira pas loin, et nous ne le faisons tout simplement pas pour la tolérance aux pannes en cas d'échecs lorsque des données peuvent être perdues. Il est peu probable qu'une telle catastrophe se produise, mais nous sommes bien préparés et dotés de différents mécanismes de sauvegarde et de réplication.
La réplication n'est pas un outil de sauvegarde de base de données pour vous ( voir ci-dessous ). Mais maintenant, nous allons voir comment récupérer rapidement des données supprimées accidentellement en utilisant une réplication retardée: sur GitLab.com, l' utilisateur a supprimé le raccourci pour le projet gitlab-ce
et a perdu le contact avec les demandes et tâches de fusion.
Avec une réplique retardée, nous avons récupéré les données en seulement 1,5 heure. Voyez comment c'était.
Récupération ponctuelle avec PostgreSQL
PostgreSQL a une fonction intégrée qui restaure l'état de la base de données à un moment précis. Il s'appelle Point-in-Time Recovery (PITR) et utilise les mêmes mécanismes qui maintiennent la pertinence de la réplique: à partir d'un instantané fiable de l'ensemble du cluster de base de données (sauvegarde de base), nous appliquons un certain nombre de changements d'état jusqu'à un certain point dans le temps.
Pour utiliser cette fonction pour une sauvegarde à froid, nous effectuons régulièrement une sauvegarde de base de la base de données et la stockons dans l'archive (archives GitLab en direct dans le stockage cloud de Google ). Nous surveillons également les modifications de l'état de la base de données en archivant le journal WAL ( write-ahead log ). Et avec tout cela, nous pouvons effectuer PITR pour la reprise après sinistre: nous commençons avec la photo prise avant l'erreur et appliquons les modifications de l'archive WAL jusqu'à l'échec.
Qu'est-ce que la réplication différée?
La réplication différée est l'application de modifications WAL retardées. Autrement dit, la transaction s'est produite à l'heure X
, mais elle apparaîtra dans la réplique avec un retard de d
Ă l'heure X + d
.
PostgreSQL a 2 façons de configurer la réplique physique de la base de données: restauration à partir de l'archive et réplication en streaming. La restauration à partir de l'archive , en fait, fonctionne comme PITR, mais en continu: nous extrayons constamment les modifications de l'archive WAL et les appliquons à la réplique. Et la réplication en streaming récupère directement le flux WAL de l'hôte de la base de données en amont. Nous préférons la récupération à partir de l'archive - elle est plus facile à gérer et a des performances normales, ce qui n'est pas en retard sur le cluster de travail.
Comment configurer la récupération différée de l'archive
Les options de récupération sont décrites dans le fichier recovery.conf
. Un exemple:
standby_mode = 'on' restore_command = '/usr/bin/envdir /etc/wal-ed/env /opt/wal-e/bin/wal-e wal-fetch -p 4 "%f" "%p"' recovery_min_apply_delay = '8h' recovery_target_timeline = 'latest'
Avec ces paramètres, nous avons mis en place une réplique paresseuse avec récupération à partir de l'archive. Ici, wal-e est utilisé pour extraire les segments WAL ( restore_command
) de l'archive, et les modifications seront appliquées après huit heures ( recovery_min_apply_delay
). La réplique surveillera les modifications de la chronologie dans l'archive, par exemple, en raison d'un basculement dans le cluster ( recovery_target_timeline
).
Avec recovery_min_apply_delay
vous pouvez configurer la réplication en streaming différé, mais il existe quelques astuces associées aux emplacements de réplication, aux commentaires de secours, etc. L'archive WAL vous permet de les éviter.
Le paramètre recovery_min_apply_delay
n'apparaissait que dans PostgreSQL 9.3. Dans les versions précédentes, pour la réplication différée, vous devez configurer une combinaison de fonctions de gestion de récupération ( pg_xlog_replay_pause(), pg_xlog_replay_resume()
) ou conserver les segments WAL dans l'archive pendant le délai.
Comment PostgreSQL fait-il cela?
Curieux de voir comment PostgreSQL implémente la récupération différée. Regardons recoveryApplyDelay(XlogReaderState)
. Il est appelé depuis la boucle principale pour chaque entrée du WAL.
static bool recoveryApplyDelay(XLogReaderState *record) { uint8 xact_info; TimestampTz xtime; long secs; int microsecs; /* nothing to do if no delay configured */ if (recovery_min_apply_delay <= 0) return false; /* no delay is applied on a database not yet consistent */ if (!reachedConsistency) return false; /* * Is it a COMMIT record? * * We deliberately choose not to delay aborts since they have no effect on * MVCC. We already allow replay of records that don't have a timestamp, * so there is already opportunity for issues caused by early conflicts on * standbys. */ if (XLogRecGetRmid(record) != RM_XACT_ID) return false; xact_info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK; if (xact_info != XLOG_XACT_COMMIT && xact_info != XLOG_XACT_COMMIT_PREPARED) return false; if (!getRecordTimestamp(record, &xtime)) return false; recoveryDelayUntilTime = TimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay); /* * Exit without arming the latch if it's already past time to apply this * record */ TimestampDifference(GetCurrentTimestamp(), recoveryDelayUntilTime, &secs, µsecs); if (secs <= 0 && microsecs <= 0) return false; while (true) { // Shortened: // Use WaitLatch until we reached recoveryDelayUntilTime // and then break; } return true; }
L'essentiel est que le retard est basé sur l'heure physique enregistrée dans l'horodatage de validation de la transaction ( xtime
). Comme vous pouvez le voir, le délai ne s'applique qu'aux validations et ne touche pas aux autres enregistrements - toutes les modifications sont appliquées directement et la validation est retardée, de sorte que nous ne verrons les modifications qu'une fois le délai configuré.
Comment utiliser la réplique paresseuse pour récupérer des données
Disons que nous avons un cluster de base de données en production et une réplique avec un retard de huit heures. Voyons comment récupérer des données en utilisant l'exemple de suppression accidentelle de raccourcis .
Lorsque nous avons découvert le problème, nous avons suspendu la récupération à partir de l'archive pour la réplique paresseuse:
SELECT pg_xlog_replay_pause();
Avec une pause, nous n'avions aucun risque que la réplique répète la demande DELETE
. Chose utile si vous avez besoin de temps pour le comprendre.
L'essentiel est que la réplique différée doit atteindre le point avant la demande DELETE
. Nous connaissions à peu près l'heure physique de l'enlèvement. Nous avons supprimé recovery_min_apply_delay
et ajouté recovery_target_time
Ă recovery.conf
. Ainsi, la réplique atteint le bon moment sans délai:
recovery_target_time = '2018-10-12 09:25:00+00'
Avec les horodatages, il vaut mieux réduire les excès pour ne pas rater. Certes, plus la diminution est importante, plus nous perdons de données. Encore une fois, si nous glissons dans la demande DELETE
, tout sera à nouveau supprimé et vous devrez tout recommencer (ou même prendre une sauvegarde à froid pour PITR).
Nous avons redémarré l'instance différée de Postgres et les segments WAL ont été répétés jusqu'à l'heure spécifiée. Vous pouvez suivre les progrès à ce stade sur demande:
SELECT -- current location in WAL pg_last_xlog_replay_location(), -- current transaction timestamp (state of the replica) pg_last_xact_replay_timestamp(), -- current physical time now(), -- the amount of time still to be applied until recovery_target_time has been reached '2018-10-12 09:25:00+00'::timestamptz - pg_last_xact_replay_timestamp() as delay;
Si l'horodatage ne change plus, la récupération est terminée. Vous pouvez configurer l'action recovery_target_action
pour fermer, avancer ou suspendre une instance après une relecture (par défaut, elle se met en pause).
La base de données a vu le jour avant cette demande malheureuse. Vous pouvez maintenant, par exemple, exporter des données. Nous avons exporté les données supprimées sur le raccourci et toutes les connexions avec les tâches et les demandes de fusion et les avons transférées vers la base de données de travail. Si les pertes sont à grande échelle, vous pouvez simplement promouvoir la réplique et l'utiliser comme principale. Mais alors tous les changements seront perdus après le moment où nous nous sommes rétablis.
Au lieu d'horodatages, il est préférable d'utiliser des ID de transaction. Il est utile d'écrire ces ID, par exemple, pour les instructions DDL (telles que DROP TABLE
), en utilisant log_statements = 'ddl'
. Si nous avions un ID de transaction, nous prendrions recovery_target_xid
et tout exécuter jusqu'à la transaction avant la demande DELETE
.
Le retour au travail est très simple: supprimez toutes les modifications de recovery.conf
et redémarrez Postgres. Bientôt, un retard de huit heures réapparaîtra dans la réplique et nous sommes prêts pour de futurs problèmes.
Avantages de récupération
Avec une réplique retardée, au lieu d'une sauvegarde à froid, vous n'avez pas à restaurer l'image entière de l'archive pendant des heures. Par exemple, nous avons besoin de cinq heures pour obtenir la sauvegarde de base complète de 2 To. Et puis vous devez toujours appliquer la WAL quotidienne entière pour récupérer à l'état souhaité (dans le pire des cas).
Une réplique différée est préférable à une sauvegarde à froid de deux manières:
- Pas besoin d'obtenir la sauvegarde de base complète de l'archive.
- Il existe une fenêtre fixe de huit heures de segments WAL qui doivent être répétés.
Nous vérifions également en permanence s'il est possible de créer PITR à partir de WAL, et nous remarquerions rapidement des dommages ou d'autres problèmes avec l'archive WAL, en surveillant le retard de la réplique retardée.
Dans cet exemple, il nous a fallu 50 minutes pour récupérer, c'est-à -dire que la vitesse était de 110 Go de données WAL par heure (l'archive était alors encore sur AWS S3 ). Au total, nous avons résolu le problème et restauré les données en 1,5 heure.
Bottom line: où la réplique retardée est utile (et où pas)
Utilisez la réplication paresseuse comme premiers secours si vous perdez accidentellement des données et remarquez ce désastre dans le délai configuré.
Mais gardez à l'esprit: la réplication n'est pas une sauvegarde.
La sauvegarde et la réplication ont des objectifs différents. Une sauvegarde à froid est utile si vous avez accidentellement créé une DELETE
ou DROP TABLE
. Nous effectuons une sauvegarde du stockage à froid et restaurons l'état précédent de la table ou de la base de données entière. Mais en même temps, la requête DROP TABLE
est lue presque instantanément dans toutes les répliques du cluster de travail, donc la réplication régulière ne vous sauvera pas ici. La réplication elle-même maintient la base de données accessible lorsque des serveurs distincts sont loués et répartit la charge.
Même avec une réplique retardée, nous avons parfois vraiment besoin d'une sauvegarde à froid dans un endroit sûr si un centre de données se bloque, des dommages cachés ou d'autres événements que vous ne remarquez pas immédiatement. Il n'y a aucun sens d'une réplication.
Remarque Chez GitLab.com, nous protégeons désormais contre la perte de données uniquement au niveau du système et ne restaurons pas les données au niveau de l'utilisateur.