---
title: oGRAC 页面恢复简介-官方技术文章-鲲鹏社区
description: oGRAC 页面恢复简介
keywords: GRAC,页面恢复简介,官方技术文章,鲲鹏社区,恢复集管理,页面恢复触发,恢复流程,恢复场景
url: https://www.hikunpeng.com/developer/techArticles/20260623-7?envFlag=1
section: (其他)
---

# oGRAC 页面恢复简介-官方技术文章-鲲鹏社区

URL: https://www.hikunpeng.com/developer/techArticles/20260623-7?envFlag=1
描述: oGRAC 页面恢复简介
关键词: GRAC,页面恢复简介,官方技术文章,鲲鹏社区,恢复集管理,页面恢复触发,恢复流程,恢复场景

官方技术文章 [了解详情](https://www.hikunpeng.com/zh/developer/techArticles)

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 已落盘
- 但对应的数据页还没刷盘
- 这时实例/节点故障重启或接管

本页内容
