OceanBase源码解读:MV快照清洁任务 ob_mview_clean_snapshot_task

物化视图(MV)的 major 刷新依赖”基表快照”来保证一致性取数,这些快照记录落在内部表 __all_acquired_snapshot 中。但有四类场景会让快照变成”孤儿”:MV 被删除、MV 创建中途失败、同一基表被多个 MV 引用产生冗余记录、基表的所有 MV 都删光。这篇我们解读负责定期打扫这些孤儿快照的后台任务——ObMViewCleanSnapshotTask(源码位于 rootserver/mview 目录,约 250 行),它是 rootserver 下 MV 定时任务家族的一员,继承自共享定时器外壳 ObMViewTimerTask。

任务定位与生命周期

ObMViewCleanSnapshotTask 是一个典型的”低频兜底”定时任务:init() 极其轻量,只记录 MTL_ID()(多租户框架下每个租户各持有一份任务对象);start() 把任务注册到租户共享定时器,以 MVIEW_CLEAN_SNAPSHOT_INTERVAL 为周期 repeat 执行;stop()/destroy() 则遵循 cancel_task + wait_task 的标准收尾,保证正在执行的轮次跑完再析构。

int ObMViewCleanSnapshotTask::start()
{
  int ret = OB_SUCCESS;
  if (IS_NOT_INIT) {
    ret = OB_NOT_INIT;
  } else {
    is_stop_ = false;
    if (!in_sched_ && OB_FAIL(schedule_task(MVIEW_CLEAN_SNAPSHOT_INTERVAL, true /*repeat*/))) {
      LOG_WARN("fail to schedule ObMViewCleanSnapshotTask", KR(ret));
    } else {
      in_sched_ = true;   // 防重复注册
    }
  }
  return ret;
}

核心流程:runTimerTask 的五段式

主循环 runTimerTask 是这个文件的灵魂,一次清理轮次分五段走:

第一段,灰度门槛。先调 need_schedule_major_refresh_mv_task 判断集群数据版本是否 >= 4.3,低版本直接空转返回。这是 OceanBase 4.3 新特性代码的标准防御:混部升级场景下旧版本租户不应触碰新表结构。

第二段,事务包裹 + 锁账本。整个清理轮次放在一个 MySQL 事务里,先以 select_for_update 锁住全局统计行取出 major_refresh_mv_merge_scn,再拉取全部 SNAPSHOT_FOR_MAJOR_REFRESH_MV 类型的快照记录。加锁的动机是防止与刷新任务并发修改造成”清理依据的账本”中途变化。

第三段,防误删闸门。这是源码注释里特意点明的竞态:MV 创建过程中”快照先落库、依赖关系后补”,存在窗口期。如果此时按依赖判断,会把新建 MV 的快照误判为”无引用”删掉。所以只要检测到有 MV 处于创建中(mview_in_creation),本轮直接跳过——宁可少清一轮,不能删错一条。

} else if (OB_FAIL(ObMViewInfo::contains_major_refresh_mview_in_creation(
               trans, tenant_id_, mview_in_creation))) {
  LOG_WARN("fail to check if mv is in creation", KR(ret), K(tenant_id_));
} else if (mview_in_creation) {
  // when a mview is being created, its snapshot may be added but the dependency is not
  // added yet, which can cause cleaning the snapshot by mistake. so we just skip this round.
  LOG_INFO("mview is being created, skip clean task", KR(ret), K(tenant_id_));
}

第四段,逐条判定是否可删。利用”快照列表已按 tablet_id + snapshot_scn 排序”这一性质,算法变得非常朴素:同 tablet_id 再次出现的记录必然 scn 更大,直接删(只保留最小 scn 那条);查 __all_tablet_to_ls 反查不到 table_id 时说明表已删,快照成孤儿,吞掉 OB_ITER_END 转成功并标记删除;MV 自己的 container 表保留;普通基表则通过 ObMVDepUtils::get_referring_mv_of_base_table 查引用,无 MV 引用即删。源码自己也注释了:调度频率低,不必为性能做复杂优化,简单循环足够——这是”低频任务用简单代码换可维护性”的务实取舍。

} else if (snapshot.tablet_id_ == last_tablet_id) {
  // 列表按 tablet_id, snapshot_scn 排序,同 tablet 后出现的必然更大
  need_remove_snapshot = true;
  LOG_INFO("redundant snapshot, remove snapshot", KR(ret), K(tenant_id_), K(snapshot));
} else if (FALSE_IT(last_tablet_id = snapshot.tablet_id_)) {
} else if (OB_FAIL(get_table_id_(trans, snapshot.tablet_id_, table_id))) {
  if (OB_ITER_END == ret) {
    ret = OB_SUCCESS;              // 表已删 → 快照成孤儿,吞码转成功
    need_remove_snapshot = true;
    LOG_INFO("table is dropped, remove snapshot", KR(ret), K(tenant_id_), K(snapshot));
  }
  ...
}
if (OB_FAIL(ret) || !need_remove_snapshot) {
} else if (OB_FAIL(snapshot_proxy.remove_snapshot(trans, tenant_id_, snapshot))) {
  LOG_WARN("fail to remove snapshot", KR(ret), K(tenant_id_), K(snapshot));
}

第五段,事务收尾。trans.end(OB_SUCC(ret)) 双分支保错误码语义:成功才提交,失败回滚且不覆盖业务错误码——OceanBase 瀑布式错误处理的标准写法。

两个辅助函数

get_table_id_ 通过内部表 __all_tablet_to_ls 做 tablet_id → table_id 的反查,配合 EXTRACT_INT_FIELD_MYSQL 宏取列;查无记录时 result->next() 返回 OB_ITER_END,正是上层判定”表已删”的信号。is_mv_container_table_ 则经 SchemaGetterGuard 拿表 schema,以 mv_major_refresh() 标志判断该表是否为 MV major 刷新专用的 container 表。

小结

ob_mview_clean_snapshot_task.cpp 是一个只有 250 行的小文件,但浓缩了 OceanBase 后台任务设计的几个关键套路:MTL 多租户任务对象、init/start/stop/destroy 生命周期守卫、灰度版本门槛、事务化对账、幂等兜底清理、以及用”创建窗口期跳过”这种保守策略换正确性。它和前面解读过的 MLog 维护任务、MV 定时任务基类一起,构成了物化视图 major 刷新体系在 rootserver 侧的”环卫班底”。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注