开发者
资源
oGRAC 页面恢复简介

oGRAC 页面恢复简介

oGRAC

发表于 2026/06/23

0

oGRAC 页面恢复简介

1. 恢复集管理

恢复集(dtc_rcy_set)构建 :

  • 故障节点重启时,收集需要恢复的页面
  • 集群重构时,主节点向其他节点发送恢复集
  • 通过 dtc_rcy_push_page_id 将页面添加到恢复集

2. 页面恢复触发

  • 节点故障恢复 :节点重启后需要恢复数据
  • 集群重构 :节点加入或离开集群时
  • 页面访问 :访问页面时发现页面不一致
  • 检查点 :检查点过程中发现需要恢复的页面

3. 恢复流程

dtc_rcy_partial_recovery 页面局部恢复

<font style="color:rgb(15, 17, 21);">dtc_rcy_analyze_batches_paral:分析redo日志哪些页面需要恢复(在故障发生后的修改),构建恢复集</font>

    <font style="color:rgb(15, 17, 21);">dtc_rcy_analyze_paral_proc:</font>

        <font style="color:rgb(15, 17, 21);">dtc_rcy_analyze_group:获取日志组的redo记录</font>

            <font style="color:rgb(15, 17, 21);">dtc_rcy_analyze_entry:分析该redo记录要不要加到恢复集</font>

                <font style="color:rgb(15, 17, 21);">dtc_rcy_reset_page_pcn:在</font>**<font style="color:rgb(15, 17, 21);">还原恢复模式,</font>**<font style="color:rgb(15, 17, 21);">根据非“进入页面”类型的 redo 日志条目,更新恢复集中对应页面的元数据(主要是 PCN 字段)</font>

dtc_send_rcy_set:分发恢复集到各节点

<font style="color:rgb(15, 17, 21);">dtc_rcy_replay_batches_paral:并行恢复</font>

<font style="color:rgb(15, 17, 21);">dtc_rcy_process_batches:串行恢复</font>

dtc_rcy_analyze_entry的恢复模式:

  • 正常恢复模式recover_for_restore 为假)(不是命令触发的恢复,实例恢复或崩溃恢复):

(1)只关心页面是否被修改,因此只处理进入页面日志。

(2)如果日志类型不是页面进入标记(RD_ENTER_PAGE 或RD_ENTER_TXN_PAGE),直接返回成功,忽略该条日志,仅将“进入页面”日志对应的页面加入恢复集。这是因为在恢复集构建阶段,只需要知道哪些页面被修改过,而页面修改总是以“进入页面”日志开始,后续的插入、更新等操作日志不会引入新页面,因此无需重复记录。

  • 恢复还原模式(recover_for_restore为真)(从备份恢复):

(1)需要精确重放每一条日志以重建页面,因此对所有日志都处理,并可能更新 PCN。

(2)对于非进入页面日志,调用dtc_rcy_reset_page_pcn。这通常用于还原恢复(如从备份恢复后重放日志),需要处理每一条日志以更新页面的 PCN,确保页面版本正确。该函数可能根据日志内容调整页面的 PCN 值,而不直接记录页面到恢复集。

  • log->type只有enter要做恢复

(1)RD_ENTER_PAGE:普通数据页的“enter page”日志(进入并准备修改页面)

(2)RD_ENTER_TXN_PAGE:事务页(txn page)的“enter page”日志

(3)恢复逻辑会利用 enter/leave 配对来判断页修改边界;对未闭合(仅 enter)的情况也要兜底处理

static status_t dtc_rcy_analyze_entry(knl_session_t *session, log_entry_t *log, uint64 lsn, bool32 is_create_df)
{
    knl_panic(log->type >= RD_ENTER_PAGE);
    if (!(log->type == RD_ENTER_PAGE || log->type == RD_ENTER_TXN_PAGE)) {
        if (!session->kernel->db.recover_for_restore) {
            // 不是命令触发的恢复,如果不是enter类型,不需要恢复
            // 设置need_flush?
            return OG_SUCCESS;
        }
        // 更新页面pcn
        return dtc_rcy_reset_page_pcn(session, log);
    }
    // 是enter,需要恢复
    rd_enter_page_t *redo = (rd_enter_page_t *)log->data;
    page_id_t page_id = MAKE_PAGID(redo->file, redo->page);
    if (session->kernel->db.recover_for_restore) {
        // 从备份恢复,重放每一条redo日志,重建页面
        return dtc_rcy_record_page(session, page_id, lsn, redo->pcn);
    }

    datafile_t *df = DATAFILE_GET(session, redo->file);
    if (!is_create_df && (!DATAFILE_IS_ONLINE(df) || !df->ctrl->used || df->file_no == OG_INVALID_ID32)) {
        OG_LOG_RUN_ERR("[DTC RCY] failed to verify df");
        knl_panic(0);
        return OG_ERROR;
    }

    space_t *space = SPACE_GET(session, df->space_id);
    if (!is_create_df && (!SPACE_IS_ONLINE(space) || !space->ctrl->used)) {
        OG_LOG_RUN_ERR("[DTC RCY] failed to verify space cfg");
        knl_panic(0);
        return OG_ERROR;
    }
    // dtc record space_id
    dtc_record_space_id(df->space_id);

    if (dtc_rcy_record_page(session, page_id, lsn, redo->pcn) != OG_SUCCESS) {
        OG_LOG_RUN_ERR("[DTC RCY] failed to record page [%u-%u] in recovery_set", page_id.file, page_id.page);
        return OG_ERROR;
    }
    return OG_SUCCESS;
}

enter:

  • 有 enter 且有 leave:边界完整,能利用 leave 的 changed 信息判断这次会话是否真正改页,恢复判断更准确。离开页面时吐过有修改会变更pcn(pcn+1,恢复和初始化是0)
  • 有 enter 但没有 leave(崩溃中断常见):边界不完整,恢复会走保守策略,不会因为缺少leave就忽略该页。
  • 没有 enter 但有些 redo 能推导页号:在 recover_for_restore 场景下也会做辅助处理(比如 reset_page_pcn 路径)。
  • enter是业务线程准备以 X 模式进入页并可能修改页时打出来的日志--->进入页面写路径(不一定真的写了)-->数据页加写锁

dtc_process_rcy_set:主节点用于处理来自重构节点(Reformer) 的rcy_set恢复集消息的入口

<font style="color:rgb(15, 17, 21);">dtc_rcy_page_need_recover:对收到的页面id,判断</font>**<font style="color:rgb(15, 17, 21);">当前节点(主节点)</font>**<font style="color:rgb(15, 17, 21);"> 是否需要对该页面执行恢复</font>

    drc_page_need_recover:如果当前owner id无效,则需要恢复

        drc_get_page_owner_id_for_rcy:获取页面当前owner

    <font style="color:#DF2A3F;">dtc_send_page_back_to_node</font>:<font style="color:rgb(15, 17, 21);">将收集到的“不需要恢复的页面列表”发送给源节点</font>

源节点:

dtc_process_rcy_set_ack:处理各节点对恢复集的应答消息。在其他节点上已经不需要恢复的页面从全局恢复集中移除

<font style="color:rgb(15, 17, 21);">dtc_rcy_inc_rcy_set_ref_num</font>

dtc_rcy_set_update_no_need_replay_batch:在恢复集找到不需要恢复的页面,标记

4. 恢复场景

恢复判定

是否恢复由“日志变更是否尚未落到 B 的目标页版本”决定:

  • 节点 B 恢复 A 的日志,核心判断不是“B 本地有没有这页”本身,而是 该日志对应的页变更是否需要在 B 重放(基于 LSN/PCN、页状态、恢复集合判定)。
  • A 已提交但变更尚未体现在 B 页副本上:通常需要重放。
  • A 未提交事务的变更:redo 可能仍会重放到页(保证物理一致),事务可见性由 undo/txn 状态在后续恢复阶段处理,不等同于“未提交就不恢复页”。
  • B 本地页已新于或等于该变更(比如之前已同步/已回放):会被判定为无需重复重放。
  • B 本地原本没有该页缓存也不代表不恢复:可以从数据文件读入/建立后再按日志重放,缓存是否命中不是必要条件。

几乎必然会触发恢复回放的场景:

  • 用户执行了写操作(INSERT/UPDATE/DELETE 等)并提交成功
  • 提交对应的 redo 已落盘
  • 但对应的数据页还没刷盘
  • 这时实例/节点故障重启或接管

本页内容