postgresql源码学习(37)—— 备份还原① - do_pg_start_backup函数

本文深入解析了PostgreSQL中pg_start_backup函数的工作原理及其内部实现细节,涵盖了备份模式、参数设置及备份过程中的关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于pg备份的基础知识,参考 https://blog.csdn.net/Hehuyi_In/article/details/102641959

一、 pg中的pg_start_backup函数

       pg的备份本质是是通过直接复制磁盘数据实现的,在全页写机制的文章中我们提到过,这可能会导致数据不一致。因此,在复制数据前必须做一些准备工作。

postgresql源码学习(34)—— 事务日志⑩ - 全页写机制_Hehuyi_In的博客-CSDN博客

1. 主要作用

pg_start_backup函数进行创建基础备份的准备工作(详细参考下方源码)。

2. 函数参数

注意这里指的是pg中pg_start_backup函数的参数,而不是源码中的函数参数。

pg_start_backup(label text [,fast boolean[,exclusive boolean]])

参数含义:

  • label:用户定义的备份标签,一般使用备份文件名加日期
  • fast:是否尽快开始备份,默认是false;若设置为true,会创建CHECKPOINT_IMMEDIATE类型的检查点
  • exclusive:指定是否为排他备份

3. 备份模式

  • 排他模式:运行期间其他会话不能再执行其他排他模式的备份(但非排他模式的可以)
  • 非排他模式:运行期间其他会话可以执行排他或非排他模式的备份

非排他备份

函数返回值是备份开始的WAL位置(LSN),可以转为对应的日志文件

select pg_walfile_name('0/2000060');

排他备份

再发起一个排他备份,会报错

但发起非排他模式的可以

4. pg_is_in_backup函数

       pg中还有一个pg_is_in_backup函数,用于检查当前是否在执行排他备份,但不能用它检查是否有非排他的备份在进行。

5. backup_label文件

排他备份会在$PGDATA目录下创建backup_label文件

backup_label文件包含以下几项 :

  • START WAL LOCATION: WAL起始位置
  • CHECKPOINT LOCATION:由该命令创建的检查点LSN位置
  • BACKUP METHOD:备份方式,值为pg_start_backup或pg_basebackup,若只是配置流复制则为streamed
  • BACKUP FROM:是在主库还是从库做的基础备份
  • LABEL:在pg_start_backup中指定的标签
  • STARTTIME:执行pg_start_backup的时间戳

       如果是非排他模式,backup_label中的信息由每个备份进程自己维护,在执行pg_stop_backup函数时返回给用户。

二、 源码中的do_pg_start_backup函数

在源码中,pg_start_backup实际对应的是do_pg_start_backup函数。

1. 主要参数

  • backupidstr:用户定义的备份标签,同前面介绍
  • fast:是否尽快开始备份,同前面介绍
  • starttli_p:起始时间线
  • labelfile:标签文件
  • tablespaces:表空间
  • tblspcmapfile:表空间映射文件

2. 返回值

备份开始的WAL位置(LSN)

3. 主要流程

  • 预检查,包括是否在恢复阶段、WAL日志等级、备份标签长度等
  • 检查WAL等级
  • 判断是否为排他备份
  • 开启强制全页写 forcePageWrites
  • 强制日志切换
  • 强制创建检查点
  • 从控制文件中获取检查点位置、redo点位置、时间线ID、全页写设置信息
  • 返回备份开始的WAL位置
/*
 * do_pg_start_backup
 *
 * Utility function called at the start of an online backup. It creates the necessary starting checkpoint and constructs the backup label file.
 * Every successfully started non-exclusive backup must be stopped by calling do_pg_stop_backup() or do_pg_abort_backup().
 */
XLogRecPtr
do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
                   StringInfo labelfile, List **tablespaces,
                   StringInfo tblspcmapfile)
{
    bool        exclusive = (labelfile == NULL);
    bool        backup_started_in_recovery = false;
    XLogRecPtr  checkpointloc;
    XLogRecPtr  startpoint;
    TimeLineID  starttli;
    pg_time_t   stamp_time;
    char        strfbuf[128];
    char        xlogfilename[MAXFNAMELEN];
    XLogSegNo   _logSegNo;
    struct stat stat_buf;
    FILE       *fp;

    /* 当前是否在恢复阶段(包括有从库) */
    backup_started_in_recovery = RecoveryInProgress();

    /*
     * Currently only non-exclusive backup can be taken during recovery.
       恢复阶段只能执行非排他备份,若执行排他备份,则直接报错
     */
    if (backup_started_in_recovery && exclusive)
        ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                 errmsg("recovery is in progress"),
                 errhint("WAL control functions cannot be executed during recovery.")));

    /*
     * During recovery, we don't need to check WAL level. Because, if WAL level is not sufficient, it's impossible to get here during recovery.
       如果在恢复阶段(包括从库),也不需要检查WAL等级,因为如果WAL等级不满足要求是不可能在恢复阶段的,直接报错
     */
    if (!backup_started_in_recovery && !XLogIsNeeded())
        ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                 errmsg("WAL level not sufficient for making an online backup"),
                 errhint("wal_level must be set to \"replica\" or \"logical\" at server start.")));

    /* 备份标签过长,报错 */
    if (strlen(backupidstr) > MAXPGPATH)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                 errmsg("backup label too long (max %d bytes)",
                        MAXPGPATH)));

    /* 后面修改forcePageWrites参数需要先获取InsertLock */
    WALInsertLockAcquireExclusive();

    /* 如果是排他备份 */
    if (exclusive)
    {
        /*
         * 首先标记我们正在执行排他备份,以确认当前没有其他会话在在执行pg_start_backup() 或者 pg_stop_backup()
         */
        if (XLogCtl->Insert.exclusiveBackupState != EXCLUSIVE_BACKUP_NONE)
        {
            WALInsertLockRelease();
            ereport(ERROR,
                    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                     errmsg("a backup is already in progress"),
                     errhint("Run pg_stop_backup() and try again.")));
        }
        /* 标记开始排他备份 */
        XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_STARTING;
    }
    /* 非排他备份 */
    else
    /* 计数器+1 */
        XLogCtl->Insert.nonExclusiveBackups++;
    /* 强制开启全页写 */
    XLogCtl->Insert.forcePageWrites = true;
    WALInsertLockRelease();

    /* Ensure we release forcePageWrites if fail below,如果下面代码运行失败,确保要取消forcePageWrites */
    PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));
    {
        bool        gotUniqueStartpoint = false;
        DIR        *tblspcdir;
        struct dirent *de;
        tablespaceinfo *ti;
        int         datadirpathlen;

        /* 创建检查点前强制执行日志切换(如果当前在恢复阶段,则不强制切换)
         */
        if (!backup_started_in_recovery)
            RequestXLogSwitch(false);

        do
        {
            bool        checkpointfpw;

            /*
             * 强制创建检查点
             * 如果在恢复阶段,尽可能创建一个restartpoint,我们用最近一次的restartpoint作为备份开始的检查点。            
             * 如果用户使用了fast参数,则以 CHECKPOINT_IMMEDIATE方式创建检查点.
             */
            RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT |
                              (fast ? CHECKPOINT_IMMEDIATE : 0));

            /* 从控制文件中获取检查点位置、redo点位置、时间线ID、全页写设置信息         */
            LWLockAcquire(ControlFileLock, LW_SHARED);
            checkpointloc = ControlFile->checkPoint;
            startpoint = ControlFile->checkPointCopy.redo;
            starttli = ControlFile->checkPointCopy.ThisTimeLineID;
            checkpointfpw = ControlFile->checkPointCopy.fullPageWrites;
            LWLockRelease(ControlFileLock);

/* 如果处于恢复阶段,要做一些设置,略 */
            …
            /*
             * 如果有两个基础备份同时在运行,需要确保它们的检查点开始位置不同,因为pg在end-of-backup WAL 记录中会用其作为基础备份的唯一标记,并写入备份历史文件
             */
            WALInsertLockAcquireExclusive();
            if (XLogCtl->Insert.lastBackupStart < startpoint)
            {
                XLogCtl->Insert.lastBackupStart = startpoint;
                gotUniqueStartpoint = true;
            }
            WALInsertLockRelease();
        } while (!gotUniqueStartpoint);

        XLByteToSeg(startpoint, _logSegNo, wal_segment_size);
        XLogFileName(xlogfilename, starttli, _logSegNo, wal_segment_size);

        /*
         * Construct tablespace_map file.  If caller isn't interested in this, we make a local StringInfo. 构建表空间映射文件
         */
        if (tblspcmapfile == NULL)
            tblspcmapfile = makeStringInfo();

        datadirpathlen = strlen(DataDir);

        /* Collect information about all tablespaces,收集所有表空间信息 */
        tblspcdir = AllocateDir("pg_tblspc");
        while ((de = ReadDir(tblspcdir, "pg_tblspc")) != NULL)
        {
            …
        }
        FreeDir(tblspcdir);

        /*
         * Construct backup label file.  If caller isn't interested in this, we make a local StringInfo. 构建backup_label文件
         */
        if (labelfile == NULL)
            labelfile = makeStringInfo();

        /* Use the log timezone here, not the session timezone,填充backup_label文件内容 */
        stamp_time = (pg_time_t) time(NULL);
        pg_strftime(strfbuf, sizeof(strfbuf),
                    "%Y-%m-%d %H:%M:%S %Z",
                    pg_localtime(&stamp_time, log_timezone));
        appendStringInfo(labelfile, "START WAL LOCATION: %X/%X (file %s)\n",
                         LSN_FORMAT_ARGS(startpoint), xlogfilename);
        appendStringInfo(labelfile, "CHECKPOINT LOCATION: %X/%X\n",
                         LSN_FORMAT_ARGS(checkpointloc));
        appendStringInfo(labelfile, "BACKUP METHOD: %s\n",
                         exclusive ? "pg_start_backup" : "streamed");
        appendStringInfo(labelfile, "BACKUP FROM: %s\n",
                         backup_started_in_recovery ? "standby" : "primary");
        appendStringInfo(labelfile, "START TIME: %s\n", strfbuf);
        appendStringInfo(labelfile, "LABEL: %s\n", backupidstr);
        appendStringInfo(labelfile, "START TIMELINE: %u\n", starttli);

        /* 写backup_label文件,略 */
       /* 写备份表空间映射文件,略 */
            …
        }
    }
    PG_END_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));

    /*
     * Mark that start phase has correctly finished for an exclusive backup. 标记排他备份的开始阶段已正确结束
     */
    if (exclusive)
    {
        WALInsertLockAcquireExclusive();
        XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS;

        /* Set session-level lock */
        sessionBackupState = SESSION_BACKUP_EXCLUSIVE;
        WALInsertLockRelease();
    }
    else
        sessionBackupState = SESSION_BACKUP_NON_EXCLUSIVE;

    /*
     * We're done.  As a convenience, return the starting WAL location.
     * 运行结束,返回起始的WAL位置
     */
    if (starttli_p)
        *starttli_p = starttli;
    return startpoint;
}

三、 pg_start_backup_callback函数

前面代码中有一段

PG_ENSURE_ERROR_CLEANUP(pg_start_backup_callback, (Datum) BoolGetDatum(exclusive));

确保如果PG_ENSURE_ERROR_CLEANUP中的代码运行失败,务必要取消掉forcePageWrites。

/* Error cleanup callback for pg_start_backup */
static void
pg_start_backup_callback(int code, Datum arg)
{
    bool        exclusive = DatumGetBool(arg);

    /* Update backup counters and forcePageWrites on failure,更新备份计数器和forcePageWrites状态 */
    WALInsertLockAcquireExclusive();
    if (exclusive)
    {
        Assert(XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_STARTING);
        XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_NONE;
    }
    else
    {
        Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
        XLogCtl->Insert.nonExclusiveBackups--;
    }

    if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
        XLogCtl->Insert.nonExclusiveBackups == 0)
    {
        XLogCtl->Insert.forcePageWrites = false;
    }
    WALInsertLockRelease();
}

参考

《PostgreSQL技术内幕:事务处理深度探索》第4章

pg 备份恢复(一)—— 热备份_Hehuyi_In的博客-CSDN博客_pg数据库热备份

postgresql源码学习(34)—— 事务日志⑩ - 全页写机制_Hehuyi_In的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值