postgresql源码学习(38)—— 备份还原② - do_pg_stop_backup函数

67 篇文章 52 订阅
31 篇文章 4 订阅

一、 pg中的pg_stop_backup函数

       在执行pg_start_backup函数开启备份模式后,务必要执行pg_stop_backup函数结束备份 (详细参考下方源码)。

结束排他备份

postgres=# SELECT pg_start_backup('backup02',false,true);      
 pg_start_backup 
-----------------
 0/3000028
(1 row)

postgres=# SELECT pg_stop_backup(true);
NOTICE:  WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup
 pg_stop_backup 
----------------
 (0/4000138,,)
(1 row)

自动删除backup_label文件

结束非排他备份

postgres=# SELECT pg_start_backup('pgdata backup',false,false);
 pg_start_backup 
-----------------
 0/2000060
(1 row)

postgres=# SELECT pg_stop_backup(false);
NOTICE:  WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup
                              pg_stop_backup                               
---------------------------------------------------------------------------
 (0/5000088,"START WAL LOCATION: 0/2000060 (file 000000010000000000000002)+
 CHECKPOINT LOCATION: 0/2000098                                           +
 BACKUP METHOD: streamed                                                  +
 BACKUP FROM: primary                                                     +
 START TIME: 2022-07-14 18:39:47 CST                                      +
 LABEL: pgdata backup                                                     +
 START TIMELINE: 1                                                        +
 ","")
(1 row)

二、 do_pg_stop_backup函数

在源码中,pg_stop_backup实际对应的是do_pg_stop_backup函数。

1. 主要参数

  • labelfile:若为空,停止排他备份;若不为空,停止该标签名对应的非排他备份
  • waitforarchive:是否等待WAL日志被归档
  • stoptli_p:时间线ID

2. 返回值

从该备份还原时必须存在的最后一个WAL位置,以及*.stoptli_p中相应的时间线ID

3. 主要流程

  • 预检查,与start函数类似
  • 如果是排他模式,删除backup_label文件。(对于非排他模式,backup_label中的信息由每个备份进程自己维护,在执行pg_stop_backup函数时返回给用户)
  • 修改备份状态和计数器
  • 如果当前已没有其他备份,关闭强制全页写 forcePageWrites
  • 非排他模式,解析备份进程中的backup_label信息
  • 强制日志切换
  • 写一个备份结束的XLOG记录
  • 创建备份历史文件,该文件包含 backup_label文件的内容及执行pg_stop_backup的时间信息
  • 等待所需WAL文件归档完成(可选)
  • 返回备份结束的WAL位置
/*
 * do_pg_stop_backup
 *
 * Utility function called at the end of an online backup. It cleans up the backup state and can optionally wait for WAL segments to be archived.
 *
 * Returns the last WAL location that must be present to restore from this backup, and the corresponding timeline ID in *stoptli_p.
 */
XLogRecPtr
do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
{
    bool        exclusive = (labelfile == NULL);
    bool        backup_started_in_recovery = false;
    XLogRecPtr  startpoint;
    XLogRecPtr  stoppoint;
    TimeLineID  stoptli;
    pg_time_t   stamp_time;
    char        strfbuf[128];
    char        histfilepath[MAXPGPATH];
    char        startxlogfilename[MAXFNAMELEN];
    char        stopxlogfilename[MAXFNAMELEN];
    char        lastxlogfilename[MAXFNAMELEN];
    char        histfilename[MAXFNAMELEN];
    char        backupfrom[20];
    XLogSegNo   _logSegNo;
    FILE       *lfp;
    FILE       *fp;
    char        ch;
    int         seconds_before_warning;
    int         waits = 0;
    bool        reported_waiting = false;
    char       *remaining;
    char       *ptr;
    uint32      hi,
                lo;

    /* 开头的预检查跟start函数类似,这里不再重复介绍 */
    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.
     */
    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 (exclusive)
    {
        /*
         * At first, mark that we're now stopping an exclusive backup, to
         * ensure that there are no other sessions currently running
         * pg_start_backup() or pg_stop_backup().
         */
        WALInsertLockAcquireExclusive();
        if (XLogCtl->Insert.exclusiveBackupState != EXCLUSIVE_BACKUP_IN_PROGRESS)
        {
            WALInsertLockRelease();
            ereport(ERROR,
                    (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                     errmsg("exclusive backup not in progress")));
        }
        XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_STOPPING;
        WALInsertLockRelease();

        /*
         * Remove backup_label. In case of failure, the state for an exclusive backup is switched back to in-progress.
         * 同样下面代码如果运行失败,会执行回调函数pg_stop_backup_callback,将排他备份状态改回运行中
         */
        PG_ENSURE_ERROR_CLEANUP(pg_stop_backup_callback, (Datum) BoolGetDatum(exclusive));
        {
            /*
             * Read the existing label file into memory.将标签文件读入内存
             */
            struct stat statbuf;
            int         r;

            if (stat(BACKUP_LABEL_FILE, &statbuf))
            {
                /* should not happen per the upper checks */
                if (errno != ENOENT)
                    ereport(ERROR,
                            (errcode_for_file_access(),
                             errmsg("could not stat file \"%s\": %m",
                                    BACKUP_LABEL_FILE)));
                ereport(ERROR,
                        (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                         errmsg("a backup is not in progress")));
            }

            lfp = AllocateFile(BACKUP_LABEL_FILE, "r");
            if (!lfp)
            {
                ereport(ERROR,
                        (errcode_for_file_access(),
                         errmsg("could not read file \"%s\": %m",
                                BACKUP_LABEL_FILE)));
            }
            labelfile = palloc(statbuf.st_size + 1);
            r = fread(labelfile, statbuf.st_size, 1, lfp);
            labelfile[statbuf.st_size] = '\0';

            /*
             * Close and remove the backup label file,关闭并删除标签文件
             */
            if (r != 1 || ferror(lfp) || FreeFile(lfp))
                ereport(ERROR,
                        (errcode_for_file_access(),
                         errmsg("could not read file \"%s\": %m",
                                BACKUP_LABEL_FILE)));
            durable_unlink(BACKUP_LABEL_FILE, ERROR);

            /*
             * Remove tablespace_map file if present, it is created only if there are tablespaces. 如果表空间映射文件存在,也将其删除
             */
            durable_unlink(TABLESPACE_MAP, DEBUG1);
        }
        PG_END_ENSURE_ERROR_CLEANUP(pg_stop_backup_callback, (Datum) BoolGetDatum(exclusive));
    }

    /*
     * OK to update backup counters, forcePageWrites and session-level lock.
     *
     * Note that CHECK_FOR_INTERRUPTS() must not occur while updating them. Otherwise they can be updated inconsistently, and which might cause do_pg_abort_backup() to fail.
     */
    WALInsertLockAcquireExclusive();
    if (exclusive)
    {
       /* 排他模式,修改备份状态为NONE */
        XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_NONE;
    }
    else
    {
        /* 非排他模式,计数器-1 */
        Assert(XLogCtl->Insert.nonExclusiveBackups > 0);
        XLogCtl->Insert.nonExclusiveBackups--;
    }

        /* 若当前已无备份(包括排他模式和非排他模式),关闭强制全页写 */
    if (XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_NONE &&
        XLogCtl->Insert.nonExclusiveBackups == 0)
    {
        XLogCtl->Insert.forcePageWrites = false;
    }

    /*
     * Clean up session-level lock.
     * 更新会话级备份状态,并释放插入锁
     */
    sessionBackupState = SESSION_BACKUP_NONE;

    WALInsertLockRelease();

    /*
     * Read and parse the START WAL LOCATION line,读取并解析labelfile中的START WAL LOCATION行。注意labelfile是非排他模式备份传入的标签文件名,跟前面删除的排他文件生成的backup_label文件不一样。
     */
    if (sscanf(labelfile, "START WAL LOCATION: %X/%X (file %24s)%c",
               &hi, &lo, startxlogfilename,
               &ch) != 4 || ch != '\n')
        ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                 errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
    startpoint = ((uint64) hi) << 32 | lo;
    remaining = strchr(labelfile, '\n') + 1;    /* %n is not portable enough */

    /*
     * 在剩余内容中解析 BACKUP FROM 行. 
     */
    ptr = strstr(remaining, "BACKUP FROM:");
    if (!ptr || sscanf(ptr, "BACKUP FROM: %19s\n", backupfrom) != 1)
        ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                 errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
    if (strcmp(backupfrom, "standby") == 0 && !backup_started_in_recovery)
        ereport(ERROR,
                (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                 errmsg("the standby was promoted during online backup"),
                 errhint("This means that the backup being taken is corrupt "
                         "and should not be used. "
                         "Try taking another online backup.")));

/* 如果处于恢复阶段,要做一些设置,略 */
    if (backup_started_in_recovery)
    {
    …
    }
    else
    {
        /*
         * Write the backup-end xlog record,写backup-end的XLOG记录
         */
        XLogBeginInsert();
        XLogRegisterData((char *) (&startpoint), sizeof(startpoint));
        stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END);
        stoptli = ThisTimeLineID;

        /*
         * Force a switch to a new xlog segment file, 强制日志切换
         */
        RequestXLogSwitch(false);

        XLByteToPrevSeg(stoppoint, _logSegNo, wal_segment_size);
        XLogFileName(stopxlogfilename, stoptli, _logSegNo, wal_segment_size);

        /* Use the log timezone here, not the session timezone */
        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));

        /*
         * Write the backup history file,写备份历史文件
         */
        XLByteToSeg(startpoint, _logSegNo, wal_segment_size);
        BackupHistoryFilePath(histfilepath, stoptli, _logSegNo,
                              startpoint, wal_segment_size);
        fp = AllocateFile(histfilepath, "w");
        if (!fp)
            ereport(ERROR,
                    (errcode_for_file_access(),
                     errmsg("could not create file \"%s\": %m",
                            histfilepath)));

        fprintf(fp, "START WAL LOCATION: %X/%X (file %s)\n",
                LSN_FORMAT_ARGS(startpoint), startxlogfilename);
        fprintf(fp, "STOP WAL LOCATION: %X/%X (file %s)\n",
                LSN_FORMAT_ARGS(stoppoint), stopxlogfilename);

        /*
         * Transfer remaining lines including label and start timeline to history file.
         */
        fprintf(fp, "%s", remaining);
        fprintf(fp, "STOP TIME: %s\n", strfbuf);
        fprintf(fp, "STOP TIMELINE: %u\n", stoptli);
        if (fflush(fp) || ferror(fp) || FreeFile(fp))
            ereport(ERROR,
                    (errcode_for_file_access(),
                     errmsg("could not write file \"%s\": %m",
                            histfilepath)));

        /*
         * Clean out any no-longer-needed history files.  As a side effect, this will post a .ready file for the newly created history file, notifying the archiver that history file may be archived immediately. 清理备份历史信息
         */
        CleanupBackupHistory();
    }

    /*
     * 如果是归档模式,且设置了waitforarchive,则等待所需日志归档完成
*/

    if (waitforarchive &&
        ((!backup_started_in_recovery && XLogArchivingActive()) ||
         (backup_started_in_recovery && XLogArchivingAlways())))
    {
 //等待所需日志归档完成,略
    }
    /*
     * 如果不是归档模式,但又设置了waitforarchive,发送提醒给用户,需要自己保证备份期间的WAL文件存在
*/
    else if (waitforarchive)
        ereport(NOTICE,
                (errmsg("WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup")));

    /*
     * We're done.  As a convenience, return the ending WAL location.
     * 函数执行结束,返回WAL结束位置
     */
    if (stoptli_p)
        *stoptli_p = stoptli;
    return stoppoint;
}

三、 pg_stop_backup_callback函数

前面代码中有一段

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

确保如果PG_ENSURE_ERROR_CLEANUP中的代码运行失败,则将排他备份状态改回运行中。

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

    /* Update backup status on failure */
    WALInsertLockAcquireExclusive();
    if (exclusive)
    {
        Assert(XLogCtl->Insert.exclusiveBackupState == EXCLUSIVE_BACKUP_STOPPING);
        XLogCtl->Insert.exclusiveBackupState = EXCLUSIVE_BACKUP_IN_PROGRESS;
    }
    WALInsertLockRelease();
}

参考

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
PostgreSQL是一种开的关系型数据库,它提供了多种管理工具来操作数据库,其中包括psql和pg_dump命令。 psql是一种命令行工具,可以用来与PostgreSQL数据库进行交互。它不仅能够执行SQL语句,还可以支持交互式命令。在使用psql命令时,有时需要在命令中包含密码信息。可以使用以下的方式来在命令行中带密码执行SQL语句: 1. 在命令行中输入psql命令,启动psql工具。 2. 输入连接数据库的命令,例如:psql -U username -d dbname -h hostname -p port。 3. 输入密码,此时必须在命令行中输入密码。 4. 执行SQL语句,例如:SELECT * FROM tablename; pg_dump是PostgreSQL数据库备份工具,可以用来将数据库中的数据导出到一个文件中。使用pg_dump命令时,也需要在命令行中包含密码信息。可以使用以下的方式在命令行中带密码执行pg_dump命令: 1. 在命令行中输入pg_dump命令,启动pg_dump工具。 2. 输入数据库连接信息和密码,例如:pg_dump -U username -d dbname -h hostname -p port -W。 3. 执行备份操作,例如:pg_dump -U username -d dbname -h hostname -p port -W > backup.sql。 总的来说,在使用psql和pg_dump命令时,为了在命令中包含密码信息,需要使用“-W”参数将密码输入到命令行中。当然,这种方式存在一定的安全隐患,因为密码可以被其他人看到。因此,最好使用其他方式来进行密码管理,例如使用配置文件、环境变量或者其他安全的方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Hehuyi_In

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

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

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

打赏作者

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

抵扣说明:

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

余额充值