暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

openGauss增量备份原理及备份工具

原创 scmysxb 2023-02-08
2097

增量物理备份

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个其他数据页的变更状态

cbmpage.png

cbm文件结构示意图

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;

说明:

  • rNodeforknum标识当前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);,查询startLsnendLsn之间发生变化的数据页面信息。在数据库服务器内部,通过读取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缓存到线程中。
cbmpagehash.png

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的流程

  1. relFileNode+forkNum组成的key值到hash表中查找对应的cbmhashentry
  2. 由数据页blk计算cbmPage的firstBlkNo
  3. 由数据页blk计算cbmpagesegliistsegIndex
  4. segIndex查找cbmpagesegliist
  5. firstBlkNo查找cbmPage

 
 



2 物理备份工具gs_probackup

gs_probackup是一个用于管理openGauss数据库备份和恢复的工具,可对openGauss实例进行定期备份,以便在数据库出现故障时能够恢复服务器

2.1 简单使用

  1. 初始化一个备份目录
gs_probackup init -B /path/to/backup/
  1. 在备份路径下添加一个数据库实例
gs_probackup add-instance -B /path/to/backup/ -D /path/to/pg-data/ --instance instancename
  1. 进行一次全量备份
gs_probackup backup -B /path/to/backup/ --instance instancename -d database -b FULL
  1. 进行一次增量备份
gs_probackup backup -B /path/to/backup/ --instance instancename -d database -b PTRACK
  1. 查看备份信息
gs_probackup show -B /path/to/backup/ --instance instancename
  1. 合并备份信息
gs_probackup merge -B /path/to/backup/ --instance instancename -i BACKUPID
  1. 恢复备份数据
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(),主要处理流程如下:
backup.png

do_backup主要流程
  1. 初始化相关信息
  2. 创建备份目录和备份控制文件
  3. 写备份控制文件
  4. 连接数据库服务器
  5. 系统标识符验证
  6. 设置会话超时时间
  7. 备份数据
  8. 更新备份控制文件内容
  9. 根据保留策略删除过期备份

说明

  • 设置开始备份向数据库服务器发送的主要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_probackupserver交互流程
server.png

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)查询结果

cbm.png

最后修改时间:2023-05-23 14:57:26
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论