我们如何使用 PostgreSQL 的延迟复制进行灾难恢复

我们如何使用 PostgreSQL 的延迟复制进行灾难恢复
复制不是备份。 或不? 以下是我们如何使用延迟复制来从意外删除的快捷方式中恢复。

基础设施专家 GitLab 负责工作 亚搏体育appcom.com - 自然界中最大的 GitLab 实例。 它拥有 3 万用户和近 7 万个项目,是最大的具有专用架构的开源 SaaS 网站之一。 如果没有 PostgreSQL 数据库系统,GitLab.com 基础设施就不会走得太远,我们如何确保在发生故障时数据可能丢失时的容错能力。 这样的灾难不太可能发生,但我们已经做好了充分准备,并储备了各种备份和复制机制。

复制不是备份数据库的一种方式(见下文)。 但现在我们将看到如何使用惰性复制快速恢复意外删除的数据: 亚搏体育appcom.com 用户 删除了快捷方式 对于该项目 gitlab-ce 并失去与合并请求和任务的连接。

通过延迟副本,我们仅用了 1,5 小时就恢复了数据。 看看它是怎么发生的。

使用 PostgreSQL 进行时间点恢复

PostgreSQL 有一个内置函数可以将数据库的状态恢复到特定时间点。 它被称为 时间点恢复 (PITR) 并使用使副本保持最新的相同机制:从整个数据库集群的可靠快照(基础备份)开始,我们应用一系列状态更改直到某个时间点。

为了使用此功能进行冷备份,我们定期进行基本的数据库备份并将其存储在存档中(GitLab 存档位于 谷歌云存储)。 我们还通过归档预写日志来监视数据库状态的变化(预写日志,沃尔)。 完成所有这些后,我们可以为灾难恢复执行 PITR:从故障前拍摄的快照开始,应用 WAL 存档中的更改直至故障发生。

什么是延迟复制?

延迟复制是延迟应用 WAL 更改。 即交易在一小时内发生 X,但它会延迟出现在副本中 d 一点钟 X + d.

PostgreSQL 有两种设置物理数据库副本的方法:备份恢复和流复制。 从存档恢复,本质上像 PITR 一样工作,但是是连续的:我们不断地从 WAL 存档中检索更改并将它们应用到副本。 A 流式复制 直接从上游数据库主机检索WAL流。 我们更喜欢归档恢复 - 它更易于管理并且具有与生产集群保持同步的正常性能。

如何设置从存档延迟恢复

恢复选项 文件中描述的 recovery.conf。 例如:

standby_mode = 'on'
restore_command = '/usr/bin/envdir /etc/wal-e.d/env /opt/wal-e/bin/wal-e wal-fetch -p 4 "%f" "%p"'
recovery_min_apply_delay = '8h'
recovery_target_timeline = 'latest'

使用这些参数,我们配置了具有备份恢复功能的延迟副本。 这里用到的是 瓦勒 提取 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,而不是时间戳。 记录这些 ID 很有用,例如对于 DDL 语句(例如 DROP TABLE), 通过使用 log_statements = 'ddl'。 如果我们有交易 ID,我们会采取 recovery_target_xid 并在请求之前运行所有事务 DELETE.

恢复工作非常简单:删除所有更改 recovery.conf 并重新启动 Postgres。 副本很快就会再次出现八小时的延迟,我们已经做好了应对未来麻烦的准备。

恢复福利

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

延迟副本在两个方面优于冷备份:

  1. 无需从存档中删除整个基本备份。
  2. WAL 段有一个固定的八小时窗口,必须重复。

我们还不断检查是否可以从 WAL 生成 PITR,并且通过监视延迟副本的滞后,我们很快就会发现 WAL 存档的损坏或其他问题。

在这个例子中,我们花了 50 分钟来恢复,这意味着速度是每小时 110 GB 的 WAL 数据(存档仍然在 AWS S3)。 总共我们在1,5小时内解决了问题并恢复了数据。

结果:延迟副本在哪里有用(以及在哪里没有用)

如果您意外丢失数据并在配置的延迟内发现此问题,请使用延迟复制作为急救。

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

备份和复制有不同的目的。 如果您不小心创建了冷备份,那么冷备份会派上用场 DELETE или DROP TABLE。 我们从冷存储中进行备份并恢复表或整个数据库的先前状态。 但同时要求 DROP TABLE 几乎立即在工作集群上的所有副本中复制,因此普通复制在这里没有帮助。 当租用单个服务器并分配负载时,复制本身可以保持数据库可用。

即使使用延迟副本,如果数据中心发生故障、隐藏损坏或其他无法立即注意到的事件发生,我们有时也确实需要在安全的地方进行冷备份。 单独的复制在这里是没有用的。

注意。 上 亚搏体育appcom.com 我们目前仅在系统级别防止数据丢失,不会在用户级别恢复数据。

来源: habr.com

添加评论