我们如何将延迟复制用于PostgreSQL的灾难恢复


复制不是备份。 还是不行 这是我们通过意外删除快捷方式将延迟复制用于恢复的方式。


GitLab的基础架构专家负责运行GitLab.com ,这是GitLab本质上最大的实例。 有300万用户和将近700万个项目,这是具有专用架构的最大的开源SaaS站点之一。 如果没有PostgreSQL数据库系统,GitLab.com的基础架构将步履维艰,如果数据丢失会导致任何故障,我们只是出于容错的目的而这么做。 这样的灾难不太可能发生,但是我们已经做好了充分的准备,并准备了各种备份和复制机制。


复制不是适合您的数据库备份工具( 请参见下文 )。 但是现在,我们将看到如何使用延迟复制来快速恢复意外删除的数据:在GitLab.com上,用户删除gitlab-ce项目的快捷方式 ,并失去了与合并请求和任务的联系。


使用延迟的副本,我们仅需1.5小时即可恢复数据。 看看情况如何。


PostgreSQL的时间点恢复


PostgreSQL具有内置功能,可以在特定时间点恢复数据库状态。 它被称为时间点恢复 (PITR),并使用相同的机制来维护副本的相关性:从整个数据库群集的可靠快照(基本备份)开始,我们应用许多状态更改,直到某个时间点为止。


要将此功能用于冷备份,我们会定期进行基本的数据库备份,并将其存储在归档文件中(GitLab归档文件实时保存在Google云存储中 )。 我们还通过归档预写日志 (WAL) 日志来监视数据库状态的变化。 有了所有这些,我们就可以执行PITR进行灾难恢复:我们从发生错误之前拍摄的图片开始,并应用WAL存档中的更改直到失败。


什么是延迟复制?


延迟复制是延迟WAL更改的应用。 也就是说,交易发生在X小时,但它将在副本中出现,在X + d小时X + dd的延迟。


PostgreSQL有两种方法来配置数据库的物理副本:从存档还原和流复制。 实际上, 从存档中恢复就像PITR一样工作,但是持续不断:我们不断从WAL存档中提取更改并将其应用于副本。 流复制直接从上游数据库主机检索WAL流。 我们更喜欢从存档中恢复-它更易于管理且性能正常,并且不会落后于工作集群。


如何从存档设置延迟恢复


恢复选项recovery.conf文件描述。 一个例子:


 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' 

使用这些参数,我们设置了一个懒惰的副本,并从存档中进行了恢复。 这里wal-e用于从存档中提取WAL段( restore_command ),更改将在八个小时后应用( recovery_min_apply_delay )。 例如,副本将监视归档文件中时间线的更改,例如,由于集群中的故障转移( recovery_target_timeline )。


使用recovery_min_apply_delay您可以配置延迟的流复制,但是有一些技巧与复制插槽,热备用反馈等相关联。 WAL存档使您可以避免它们。


recovery_min_apply_delay参数仅在PostgreSQL 9.3中出现。 在以前的版本中,对于延迟复制,您需要配置恢复管理功能的组合( pg_xlog_replay_pause(), pg_xlog_replay_resume() )或将WAL段保留在归档中以保持时间延迟。


PostgreSQL如何做到这一点?


很想知道PostgreSQL如何实现延迟恢复。 让我们看一下recoveryApplyDelay(XlogReaderState) 。 从主循环中为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, &microsecs); if (secs <= 0 && microsecs <= 0) return false; while (true) { // Shortened: // Use WaitLatch until we reached recoveryDelayUntilTime // and then break; } return true; } 

最重要的是,延迟是基于事务提交时间戳( xtime )中记录的物理时间。 如您所见,该延迟仅适用于提交,并且不涉及其他记录-所有更改都直接应用,并且提交被延迟,因此我们只有在配置了延迟之后才能看到更改。


如何使用惰性副本恢复数据


假设我们有一个生产中的数据库集群和一个延迟了八小时的副本。 让我们看看如何使用意外删除快捷方式的示例恢复数据。


当发现问题时,我们暂停了从存档中恢复懒惰副本的操作:


 SELECT pg_xlog_replay_pause(); 

稍作停顿,我们没有风险副本会重复DELETE请求。 有用的东西,如果您需要时间弄清楚。


最重要的是,延迟的副本必须到达DELETE请求之前的位置。 我们大致知道移除的物理时间。 我们删除了recovery_min_apply_delay ,并将recovery_target_time添加到了recovery.conf 。 因此副本立即到达正确的时刻:


 recovery_target_time = '2018-10-12 09:25:00+00' 

使用时间戳记,最好减少多余的部分,以免丢失。 是的,减少幅度越大,我们丢失的数据就越多。 同样,如果我们跳过DELETE请求,所有内容将再次被删除,您将不得不重新开始(甚至为PITR进行冷备份)。


我们重新启动了Postgres的延迟实例,并重复WAL段直到指定的时间为止。 您可以根据要求跟踪此阶段的进度:


 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; 

如果时间戳不再更改,则恢复已完成。 您可以将recovery_target_action操作配置为在重播后关闭,推进或暂停实例(默认情况下它会暂停)。


在该请求失败之前,数据库进入了状态。 现在,您可以例如导出数据。 我们导出了有关快捷方式以及与任务的所有连接和合并请求的已删除数据,并将它们传输到工作数据库中。 如果损失很大,则可以简单地推广副本并将其用作主要副本。 但是,在我们恢复到这一刻之后,所有的变化都将丢失。


代替时间戳,最好使用事务ID。 使用log_statements = 'ddl'为DDL语句(例如DROP TABLE )编写这些ID很有用。 如果我们有一个事务ID,我们将使用recovery_target_xid并将所有内容运行到DELETE请求之前的事务中。


恢复工作非常简单:从recovery.conf删除所有更改,然后重新启动Postgres。 很快,复制品将再次出现8个小时的延迟,我们已为以后的麻烦做好了准备。


恢复收益


使用延迟的副本而不是冷备份,您无需花费数小时从存档中还原整个映像。 例如,我们需要五个小时来获得2 TB的整个基本备份。 然后,您仍然必须应用整个每日WAL以恢复到所需状态(在最坏的情况下)。


延迟副本比冷备份在两个方面要好:


  1. 无需从存档中获取整个基础备份。
  2. 有一个固定的八小时WAL时段窗口,需要重复。

我们还将不断检查是否有可能从WAL中制作PITR,并且我们会迅速注意到WAL归档文件的损坏或其他问题,并监控延迟副本的延迟。


在此示例中,我们花了50分钟才能恢复,也就是说,速度为每小时110 GB WAL数据( 那时存档仍在AWS S3上 )。 总共,我们解决了问题,并在1.5小时内恢复了数据。


底线:延迟的副本在哪里派上用场(哪里不方便)


如果您意外丢失数据并在配置的延迟内注意到此灾难,请使用延迟复制作为急救。


但请记住:复制不是备份。

备份和复制具有不同的目标。 如果不小心创建了DELETEDROP TABLE则冷备份很有用。 我们从冷存储器进行备份,并还原表或整个数据库的先前状态。 但是同时, DROP TABLE查询几乎立即在工作群集上的所有副本中播放,因此常规复制不会在这里保存您。 当租用单独的服务器并分配负载时,复制本身使数据库可访问。


即使副本延迟,有时如果数据中心崩溃,隐藏损坏或您没有立即注意到的其他事件,我们有时仍然确实需要在安全的地方进行冷备份。 一次复制没有任何意义。


注意事项 现在GitLab.com,我们仅在系统级别上防止数据丢失,而在用户级别上不恢复数据。

Source: https://habr.com/ru/post/zh-CN445446/


All Articles