关于pg备份的基础知识,参考 https://blog.csdn.net/Hehuyi_In/article/details/102641959
pg的备份本质是是通过直接复制磁盘数据实现的,在全页写机制的文章中我们提到过,这可能会导致数据不一致。因此,在复制数据前必须做一些准备工作。
postgresql源码学习(34)—— 事务日志⑩ - 全页写机制_Hehuyi_In的博客-CSDN博客
pg_start_backup函数进行创建基础备份的准备工作(详细参考下方源码)。
注意这里指的是pg中pg_start_backup函数的参数,而不是源码中的函数参数。
pg_start_backup(label text [,fast boolean[,exclusive boolean]])
参数含义:
非排他备份

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

排他备份

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

但发起非排他模式的可以

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

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


backup_label文件包含以下几项 :
如果是非排他模式,backup_label中的信息由每个备份进程自己维护,在执行pg_stop_backup函数时返回给用户。
在源码中,pg_start_backup实际对应的是do_pg_start_backup函数。
备份开始的WAL位置(LSN)
- /*
- * 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_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章