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


