增量物理备份
openGauss提供数据库备份和恢复的工具gs_probackup,支持对openGauss实例进行定期备份,以便在数据库出现故障时能够恢复服务器。gs_probackup支持全量和增量的物理备份,如果要使用PTRACK增量备份,需在数据库配置文件postgresql.conf中手动添加参数enable_cbm_tracking = on,以开启数据库对脏页的追踪。
1 cbm (changed block map)
1.1 cbm文件
开启enable_cbm_tracking = on后,服务器会在data/pg_cbm/目录下生成pg_xlog_seqnum_startlsn_endlsn.cbm文件。cbm文件结构如图,单个文件最大128MB,cbmPage是cbm文件在系统内的最小操作单位,每个cbmPage的大小为512B。每个cbmPage的头部有一个56B的cbmPageHeader,保存当前cbmPage的相关信息,后边紧跟数据的每一个二进制位 标识当前二进制位对应的数据文件的页面 自上一次备份后的变更状态,每个cbmPage可以标识3648个其他数据页的变更状态

1.1.1 相关数据结构
/* Struct for single bitmap file information */
typedef struct BitmapFileStruct {
char name[MAXPGPATH]; /* Name with full path */
int fd; /* Handle to opened file */
uint64 size; /* Size of the file */
off_t offset; /* Offset of the next read */
} BitmapFile;
/* cbmPageHeader */
typedef struct cbmpageheader {
pg_crc32c pageCrc;
bool isLastBlock;
uint8 pageType;
XLogRecPtr pageStartLsn;
XLogRecPtr pageEndLsn;
RelFileNode rNode;
ForkNumber forkNum;
BlockNumber firstBlkNo; //当前cbmPage记录的文件起始页
BlockNumber truncBlkNo;
} CbmPageHeader;
说明:
rNode和forknum标识当前cbmPage和其他数据文件之间的对应关系(cm文件,vm文件,df文件等)- cbmPage的某一位与对应数据文件页的对应关系:
firstBlkNo + offset, firstBlkNo = (x) - ((x) % CBM_BLOCKS_PER_PAGE) CBM_BLOCKS_PER_PAGE = 3648,每个cbmPage可以表示的数据页数目
1.1.2 主要函数
static void CBMPageSetBitmap(char *page, BlockNumber blkNo, uint8 pageType, BlockNumber truncBlkNo); // 置位cbm标志位
static void CreateNewCBMPageAndInsert(CbmHashEntry *cbmPageEntry, BlockNumber blkNo, uint8 pageType, BlockNumber truncBlkNo); // 新增一个cbm页面
static void FlushOneCBMPage(const char *page, XlogBitmap *xlogCbmSys); // 将单个cbm页面刷盘
static void FlushCBMPagesToDisk(XlogBitmap *xlogCbmSys, bool isCBMWriter);
1.1.3 数据读写
cbm页面数据读取
- cbm页面数据读取主要在增量备份时进行,增量备份工具
gs_probackup向数据库服务器发送SELECT pg_cbm_get_changed_block(startLsn, endLsn);,查询startLsn和endLsn之间发生变化的数据页面信息。在数据库服务器内部,通过读取cbm页面数据来判断哪些数据页面发生了变更
cbm页面置位
- cbm页面数据写主要由cbmWriter线程进行。cbmWriter线程会解析XLog,将当前XLog对应的数据页修改信息记录到对应的cbm页面的相应位置
1.2 cbmWriter线程
1.2.1 主要数据结构
/* cbmWriter线程级数据结构 */
typedef struct knl_t_cbm_context {
/* The xlog parsing and bitmap output struct instance */
struct XlogBitmapStruct* XlogCbmSys;
/* cbmwriter.cpp */
/* Flags set by interrupt handlers for later service in the main loop. */
volatile sig_atomic_t got_SIGHUP;
volatile sig_atomic_t shutdown_requested;
MemoryContext cbmwriter_context;
MemoryContext cbmwriter_page_context;
} knl_t_cbm_context;
typedef struct XlogBitmapStruct{
char cbmFileHome[MAXPGPATH]; /* directory for bitmap files */
BitmapFile out; /* current bitmap file */
uint64 outSeqNum; /* the bitmap file sequence number */
XLogRecPtr startLSN; /* 下一个未解析的XLog */
XLogRecPtr endLSN; /* 解析时下一个检查点的LSN */
HTAB* cbmPageHash;
Dllist pageFreeList;
uint64 totalPageNum;
CBMXlogRead xlogRead; /* file information of xlog files to be parsed */
bool xlogParseFailed; /* true if failed during last xlog parse */
bool needReset; /* true if failed during last CBMFollowXlog */
bool firstCPCreated;
}
typedef struct BitmapFileStruct {
char name[MAXPGPATH]; /* Name with full path */
int fd; /* Handle to opened file */
uint64 size; /* Size of the file */
off_t offset; /* Offset of the next read */
} BitmapFile;
1.2.2 主要函数
/* cbmWriter线程主函数 */
void CBMWriterMain()
{
/* 线程初始化、信号量设置 */
for( ; ; ) {
ResetLatch(&t_thrd.proc->procLatch);
pgstat_report_activity(STATE_RUNNING, NULL);
if (t_thrd.cbm_cxt.got_SIGHUP) {
/* 重新加载配置文件 */
}
if (t_thrd.cbm_cxt.shutdown_requested) {
/* 退出 */
}
CBMFollowXlog();
pgstat_report_activity(STATE_IDLE, NULL);
/* WaitLatch() */
}
}
/* 解析XLog并将发生变更的页面记录到cbmPage中 */
void CBMFollowXlog()
{
XLogRecPtr tmpEndLSN = t_thrd.shemem_ptr_cxt.ControlFile->checkPointCopy.redo;
timeLineID timeLine = t_thrd.shemem_ptr_cxt.ControlFile->checkPointCopy.ThisTimeLineID;
···
if (tmpEndLSN < t_thrd.cbm_cxt.XlogCbmSys->startLSN) {
if (checkUserRequstAndRotateCbm()) {
RotateCBMFile();
···
return;
} else if (!t_thrd.cbm_cxt.XlogCbmSys->firstCPCreated) {
···
return;
} else {
RemoveAllCBMFiles(PANIC);
} else if (tmpEndLSN == t_thrd.cbm_cxt.XlogCbmSys->startLSN) {
if (checkUserRequstAndRotateCbm()) {
RotateCBMFile();
}
···
return;
}
if (ParseXlogIntoCBMPages(timeLine, isRecEnd)) {
···
return;
}
if (t_thrd.cbm_cxt.XlogCbmSys->totalPageNum == 0)
CreateDummyCBMEtyPageAndInsert();
FlushCBMPagesToDisk(t_thrd.cbm_cxt.XlogCbmSys, true);
SetCBMTrackedLSN(t_thrd.cbm_cxt.XlogCbmSys->startLSN);
···
}
bool ParseXlogIntoCBMPages(TimeLineID timeLine, bool isRecEnd)
{
XLogPageReadPrivateCBM readprivate;
readprivate.datadir = t_thrd.proc_cxt.DataDir;
readprivate.tli = timeLine;
XLogReaderState* xlogreader = XLogReaderAllocate(&CBMXLogPageRead, &readprivate);
do{
record = XLogReadRecord();
if (record == NULL) {
····
}
TrackChangeBlock(xlogreader);
advanceXlogPtrToNextPageIfNeeded(&(xlogreader->EndRecPtr));
···
} while(true);
···
}
说明:
TrackChangeBlock()通过调用TrackRelPageModification()完成对XLog的解析和记录,其他类型的XLog调用相应的函数进行解析记录
1.2.3 hashTab的维护
cbmWriter线程维护一个线程级的哈希表,将cbmPage缓存到线程中。

相关数据结构
typedef struct cbmpagetag {
RelFileNode rNode;
ForkNumber forkNum;
} CBMPageTag;
typedef struct cbmhashentry {
CBMPageTag cbmTag;
Dllist cbmSegPageList;
int pageNum;
} CbmHashEntry;
typedef struct cbmsegpagelist {
int segIndex;
Dllist pageDllist;
} CbmSegPageList;
说明:
- 通过由
relFileNode+forkNum组成的key值,可将表示 不同表的不同数据文件 的cbmPage放到一个hashEntry中,每个hashEntry下挂载一个双链表,链表的每个元素是一个cbmPage段,每个段最多有131072个cbmPage。段索引与数据页blk之间的关系为:segIndex = firstBlkNo / BLOCK_NUM_PER_CBMLIST
, BLOCK_NUM_PER_CBMLIST = 131072 - 每个cbmPage的头部记录当前cbmPage记录的数据页的起始blkNo,即
firstBlkNo,与数据页blkNo之间的对应关系为firstBlkNo = (blk) - ((blk) % CBM_BLOCKS_PER_PAGE)
查找一个cbmPage的流程
- 由
relFileNode+forkNum组成的key值到hash表中查找对应的cbmhashentry - 由数据页blk计算cbmPage的
firstBlkNo - 由数据页blk计算
cbmpagesegliist的segIndex - 由
segIndex查找cbmpagesegliist - 由
firstBlkNo查找cbmPage
2 物理备份工具gs_probackup
gs_probackup是一个用于管理openGauss数据库备份和恢复的工具,可对openGauss实例进行定期备份,以便在数据库出现故障时能够恢复服务器
2.1 简单使用
- 初始化一个备份目录
gs_probackup init -B /path/to/backup/
- 在备份路径下添加一个数据库实例
gs_probackup add-instance -B /path/to/backup/ -D /path/to/pg-data/ --instance instancename
- 进行一次全量备份
gs_probackup backup -B /path/to/backup/ --instance instancename -d database -b FULL
- 进行一次增量备份
gs_probackup backup -B /path/to/backup/ --instance instancename -d database -b PTRACK
- 查看备份信息
gs_probackup show -B /path/to/backup/ --instance instancename
- 合并备份信息
gs_probackup merge -B /path/to/backup/ --instance instancename -i BACKUPID
- 恢复备份数据
gs_probackup restore -B /path/to/backup/ --instance instancename -D pgdata-path -i BACKUPID
说明:
- 更多信息请参考gs_probackup文档
- 进行增量备份之前至少进行一次全量备份
2.2 实现原理
gs_probackup备份通过直接读取数据库data目录下的文件实现物理备份,对于增量备份,通过PTRACK引擎获取发生变更的blocks,实现数据的增量备份
2.2.1 主要函数
int main(int argc char* argv[])
{
pgBackupInit();
init_config();
···
if (argc > 1) {
parse_options(argv[1], argc, argv);
}
···
command = make_command_string(argc, argv);
parse_cmdline_args(argc, argv, command_name);
init_logger();
···
parse_backup_option_to_params();
compress_init();
return do_actual_operate();
}
int do_actual_operate()
{
switch(backup_subcmd){
case SUB_CMD:
return function();
}
return 0;
}
说明:
- 在
do_actual_operate()中,根据不同的子命令调用相应的处理函数
2.2.2 备份
备份子命令backup的处理函数为do_backup(),主要处理流程如下:

- 初始化相关信息
- 创建备份目录和备份控制文件
- 写备份控制文件
- 连接数据库服务器
- 系统标识符验证
- 设置会话超时时间
- 备份数据
- 更新备份控制文件内容
- 根据保留策略删除过期备份
说明
- 设置开始备份向数据库服务器发送的主要SQL:
SELECT pg_catalog.pg_start_backup($1, $2); - 在停止备份
pg_stop_backup()中,如果备份来自主库,则先创建恢复点信息create_restore_point(),主要SQL为*SELECT pg_catalog.pg_create_restore_point($1)
,然后停止备份,主要SQL为SELECT pg_catalog.pg_stop_backup() - WAL流复制和数据复制在不同的线程中完成
- 增量备份模式下,通过
make_pagemap_from_ptrack()获取自上一次备份后发生变更的blocks。具体实现上,首先获取最新的cbm追踪位置SELECT pg_cbm_tracked_location(),然后根据cbm信息查询自上次备份后发生变更的blocksSELECT path, changed_block_number, changed_block_list FROM pg_cbm_get_changed_block($1, $1)
WAL复制子线程
- 线程入口:
StreamLog() - 日志接收函数:
ReceiveXlogStream()
数据复制子线程
- 线程入口:
backup_files() - 数据接收主要函数:
backup_data_file()
gs_probackup和server交互流程

说明:
- 在获取发生变更的blocks一步,会首先通过
SELECT pg_cbm_tracked_location()获取cbm最新追踪的位置,然后通过SELECT * FROM pg_cbm_get_changed_block($1, $2)获取指定lsn范围内发生变更的数据块 - 步骤1:通过
SELECT pg_create_restore_point($1)创建恢复点 - 步骤2:通过
SELECT pg_stop_backup()停止备份,数据库服务器接收到SELECT pg_stop_backup()后会插入一条XLog,记录本次增量备份的起始位置XLogRegisterData((char *)(&startpoint), sizeof(startpoint));,这条XLog的位置就是本次备份的结束位置,然后将其发送到gs_probackup,用于结束XLog的接收线程 - 步骤3:写备份控制文件等信息
- 步骤4:等待XLog传输线程结束
- 步骤5:读取恢复信息
read_recovery_info()
Q&A
Q1:XLog和数据的备份是不同的线程处理的,如何保证备份结束时数据和XLog的一致性?
- 与
gs_basebackup不同,gs_basebackup的数据和XLog都是通过数据库服务器向备份工具发送的,数据发送停止后会立即插入一条XLog作为备份结束的位置标记,Xlog接收进程收到结束标记后停止XLog接收 gs_probackup的XLog来自数据库服务器,但是数据文件是自己从磁盘上读取的。数据文件备份完之后会立即向数据库服务器发送停止备份指令,数据库根据停止备份指令停止XLog的传输,因此XLog能够覆盖整个备份的数据文件
附
SELECT * FROM pg_cbm_get_changed_block($1, $2)查询结果





