news 2026/9/15 2:28:34

Bytebase Plan Check Run 运行时派生重构:从冗余存储到按需计算的配置推导

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Bytebase Plan Check Run 运行时派生重构:从冗余存储到按需计算的配置推导

Bytebase Plan Check Run 运行时派生重构:从冗余存储到按需计算的配置推导

【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase

导读

本篇文章深入解析 Bytebase 开源仓库中的一份核心设计文档(docs/plans/2026-01-05-plan-check-run-runtime-derivation.md):如何将 Plan Check Run(计划检查运行)的配置从"随运行持久化一份完整副本"重构为"运行时从关联 Plan 实时推导"。读完本文,你将理解 Bytebase 数据库变更治理链路中计划检查的数据模型演进、config/payload两列的迁移清理方案,以及派生函数在调度器(Scheduler)中的实际调用流程,可直接对照仓库源码继续深入。


一、背景与动机:为什么不再存储一份自包含配置

Bytebase 的 Plan Check(计划检查)是变更审批前的自动校验环节,例如对 SQL 进行语句评审(Statement Advise)、生成摘要报告(Statement Summary Report),以及在开启 gh-ost 在线变更时执行 ghost 同步检查。

在重构之前,每次计划检查运行时,系统会通过getPlanCheckRunFromPlan()从 Plan 复制一份配置存入 Plan Check Run 记录中,形成"自包含"的持久化副本。该设计文档指出这种做法带来三个问题:

  1. 简化代码:需要移除getPlanCheckRunFromPlan()中的配置复制逻辑;
  2. 减少存储:避免存储与 Plan 重复的冗余数据;
  3. 防止过期:Plan Check Run 配置一旦与 Plan 脱钩,就可能与 Plan 的实际状态不同步(staleness),导致检查基于过时配置执行。

重构前的数据冗余

设计文档以表格清晰刻画了重构前plan_check_run表中重复存储的字段:

字段在 Plan 中的位置在 Plan Check Run 中的位置
sheet_sha256ChangeDatabaseConfigCheckTarget
enable_prior_backupChangeDatabaseConfigCheckTarget
enable_ghostChangeDatabaseConfigCheckTarget
ghost_flagsChangeDatabaseConfigCheckTarget
targetsSpec 级别按每个CheckTarget展开

其中config列以 JSONB 存储PlanCheckRunConfig(内含上述重复字段),而payload列则始终未被使用(reserved)。


二、核心洞察:一运行一 Plan 的唯一性约束

该重构成立的前提,是设计文档强调的一个关键约束:

每个 Plan 只有一个 Plan Check Run(plan_id上存在唯一约束)。当计划检查被重新执行时,旧的运行会被直接替换。

由此可以推出三点结论:

  • 无需保留历史配置:旧运行被替换,历史配置没有保留价值;
  • 结果自带目标信息:检查结果中包含 target 信息,天然自描述;
  • 配置可以从当前 Plan 状态推导:由于运行始终与最新 Plan 关联,运行时推导必然拿到最新状态。

这三点共同保证了"运行时派生"不会引入正确性问题——检查永远基于 Plan 的最新配置执行,反而从根本上消除了配置过期风险。


三、数据模型变更:裁剪plan_check_run

移除的列

  • config列(JSONB,存储PlanCheckRunConfig
  • payload列(未使用,预留)

保留的列

说明
id主键
created_at/updated_at创建/更新时间
plan_id指向 Plan 的外键
status运行状态(RUNNING / DONE / FAILED / CANCELED)
resultJSONB,存储PlanCheckRunResult

Proto 层变更

设计文档要求对proto/store/store/plan_check_run.proto进行如下清理:

  • 删除PlanCheckRunConfigmessage;
  • 删除CheckTargetmessage(仅用于 config);
  • 如果 API 层向客户端暴露了 config,则同步更新 API proto(proto/v1/plan_service.proto)。

对照当前仓库的 proto/store/store/plan_check_run.proto,该文件已只保留PlanCheckRunResult(含ResultSqlSummaryReportSqlReviewReport等)以及ChangedResources系列 message,PlanCheckRunConfigCheckTarget均已不在其中——证明上述清理已落地。同时,PlanCheckRunResult.Result中保留了target(格式为instances/{instance}/databases/{database})、typesheet_sha256字段,这与设计文档"结果自带目标信息、结果自描述"的结论一致。

Store 层变更

  • PlanCheckRunMessage中移除Config字段;
  • 更新 CRUD 操作,使其不再读写 config/payload 列。

四、运行时派生:纯函数式的配置推导

派生结构体

设计文档给出的核心方案是:getPlanCheckRunFromPlan()不再创建并持久化 config,而是返回一个内存中的结构体:

type DerivedCheckTarget struct { Target string // database resource name SheetSHA256 string // from plan spec EnablePriorBackup bool EnableGhost bool GhostFlags map[string]string Types []storepb.PlanCheckType }

在仓库的实际实现中,该结构体以 backend/runner/plancheck/check_target.go 中的CheckTarget落地,字段与设计一致,并补充了注释说明:

  • Target:规范化的数据库资源名,形如instances/{instance}/databases/{database}projects/{project}/instances/{instance}/databases/{database}
  • SheetSha256:SQL sheet 的内容哈希;
  • EnablePriorBackup:是否在迁移前开启备份;
  • EnableGhost:是否启用 gh-ost 在线迁移;
  • GhostFlags:gh-ost 的配置参数;
  • Types:针对该目标需要执行的计划检查类型。

Executor 流程

设计文档定义了重构后的执行流程:

  1. 获取 Plan Check Run(包含plan_idstatus);
  2. 通过plan_id获取 Plan;
  3. 调用派生函数得到 targets 与 config;
  4. 针对每个 target,使用派生出的 config 执行检查;
  5. 将结果写入result字段。

仓库中的实际调度实现

对照 backend/runner/plancheck/scheduler.go,runPlanCheckRun()完整实现了上述流程:

  1. 通过s.store.GetPlan()projectID + planUID获取 Plan;
  2. 校验plan.Config.GetApprovalInputVersion()与运行声明的approvalInputVersion一致,否则将该运行标记为 CANCELED("stale plan check run")——这是并发场景下防止旧运行写入过期结果的兜底机制;
  3. 获取 Project,若项目已归档(project.Deleted)则取消运行;
  4. 调用GetDatabaseGroupForPlan()(见 backend/runner/plancheck/database_group.go)在需要时解析数据库组并展开匹配的数据库列表;
  5. 调用DeriveCheckTargets()在运行时推导 targets;
  6. 遍历 targets,通过s.executor.RunForTarget()(接口定义见 backend/runner/plancheck/executor.go)执行检查并聚合结果;
  7. 依据执行结果调用UpdatePlanCheckRunIfApprovalInputVersion()将运行标记为 DONE / FAILED / CANCELED,结果写入PlanCheckRunResult
  8. 运行结束后向ApprovalCheckChan发送信号触发审批查找(approval finding),进而推动 rollout 创建。

值得注意的是,步骤 8 表明 Plan Check 是审批链路的前置闸门:只有当检查 DONE 之后,审批与发布流程才会继续推进。


五、派生函数的实现细节:组展开、CI 采样与 gh-ost 识别

设计文档特别强调:"派生逻辑本身保持不变(数据库组展开、CI 采样)"。仓库中的 backend/runner/plancheck/derive.go 展示了完整的派生实现,它同时印证并深化了设计:

1. 两个入口,两种采样策略

func DeriveCheckTargets(ctx context.Context, s *store.Store, project *store.ProjectMessage, plan *store.PlanMessage, databaseGroup *v1pb.DatabaseGroup) ([]*CheckTarget, error) { return deriveTargets(ctx, s, project, plan, databaseGroup, true) } func DeriveReviewTargets(ctx context.Context, s *store.Store, project *store.ProjectMessage, plan *store.PlanMessage, databaseGroup *v1pb.DatabaseGroup) ([]*CheckTarget, error) { return deriveTargets(ctx, s, project, plan, databaseGroup, false) }
  • DeriveCheckTargets应用 CI 采样。Plan Check 本质是 CI 校验,采样用于控制检查成本;
  • DeriveReviewTargets不采样。评审运行(review run)的 DONE 意味着每一个 (spec, target) 单元都被评估过,必须看到完整目标集。

2. 目标展开逻辑

deriveTargets()遍历plan.Config.Specs,按 spec 类型分支处理:

  • CreateDatabaseConfig:创建数据库不执行计划检查;
  • ChangeDatabaseConfig:若Release != ""(发布场景)则跳过计划检查;否则:
    • 若目标列表中唯一目标恰为数据库组名,则用databaseGroup.MatchedDatabases展开;
    • 否则直接使用Targets列表;
    • applySampling为 true 且project.Setting.GetCiSamplingSize() > 0且目标数超出采样上限时,截断到前samplingSize个。

3. gh-ost 指令解析

派生函数会通过getSheetContent()依据SheetSha256读取 SQL sheet 内容,调用ghost.IsGhostEnabled()判断是否启用 gh-ost,若启用则调用ghost.ParseGhostDirective()解析 gh-ost 指令得到GhostFlags;最终根据是否启用 gh-ost 决定追加PLAN_CHECK_TYPE_GHOST_SYNC检查类型:

types := []storepb.PlanCheckType{ storepb.PlanCheckType_PLAN_CHECK_TYPE_STATEMENT_ADVISE, storepb.PlanCheckType_PLAN_CHECK_TYPE_STATEMENT_SUMMARY_REPORT, } if enableGhost { types = append(types, storepb.PlanCheckType_PLAN_CHECK_TYPE_GHOST_SYNC) }

4. 数据库组解析

backend/runner/plancheck/database_group.go 中的GetDatabaseGroupForPlan()负责:识别 Plan 的 change-database spec 是否指向数据库组 → 查询数据库组 → 列出项目下全部数据库 → 通过utils.GetMatchedDatabasesInDatabaseGroup()计算匹配数据库并填充MatchedDatabases。注释特别提醒:allDatabases必须是完整未过滤的数据库列表,否则组展开会静默不完整。


六、迁移脚本:一次安全的在线 DDL

设计文档给出的迁移 SQL 为:

ALTER TABLE plan_check_run DROP COLUMN config; ALTER TABLE plan_check_run DROP COLUMN payload;

该迁移已在仓库中落地为 backend/migrator/migration/3.14/0021##remove_plan_check_run_config_payload.sql,内容与设计完全一致。由于两个列中payload本就未使用、config可由 Plan 在运行时重新推导,删除操作不涉及数据回填或转换,属于低风险的列裁剪迁移。


七、保持不变的部分

设计文档明确列出了重构中"不变"的边界,确保改动不扩散:

  • PlanCheckRunResultproto 保持不变(按 target 存储实际检查结果);
  • Plan Check Run 的状态生命周期不变(RUNNING → DONE / FAILED / CANCELED);
  • 派生逻辑本身不变(数据库组展开、CI 采样);
  • "每个 Plan 一个 Plan Check Run"的唯一约束不变。

这些不变项意味着该重构是纯内部架构优化:对外部 API 消费者、状态机与检查语义均无影响,风险被严格控制在存储层与运行器的实现细节内。


八、涉及文件一览(按设计文档与仓库现状对照)

设计文档给出的改动清单如下,结合当前仓库可以看到各层的实际落点:

文件变更
Proto(store)proto/store/store/plan_check_run.proto移除PlanCheckRunConfigCheckTarget(已落地,当前仅保留PlanCheckRunResult
Proto(API)proto/v1/plan_service.proto若向客户端暴露 config 则移除
迁移backend/migrator/migration/3.14/0021##remove_plan_check_run_config_payload.sqlDROPconfigpayload
Storebackend/store/plan_check_run.go移除Config字段,更新 CRUD
APIbackend/api/v1/plan_service.go简化创建逻辑,抽出派生函数
Runnerbackend/runner/plancheck/scheduler.go运行时获取 Plan 并推导 targets
Executorsbackend/runner/plancheck/*.go接收派生出的配置(CheckTarget

九、测试佐证:派生语义的可验证性

重构后的派生逻辑由单元测试直接守护。backend/runner/plancheck/derive_test.go 中的TestDeriveReviewTargetsSkipsCISampling构造了一个含 3 个目标的 Plan(CiSamplingSize: 1),断言:

  • DeriveCheckTargets仅返回 1 个目标——"plan checks apply the CI sampling limit";
  • DeriveReviewTargets返回全部 3 个目标——"review must evaluate every target regardless of CI sampling"。

该测试同时验证了设计文档"派生逻辑本身保持不变"与"CI 采样"两条核心承诺,是理解派生语义的最佳入口。


总结

plan-check-run-runtime-derivation是一次典型的"以数据模型简化换取运行时计算"的架构演进:借助"一 Plan 一运行"的唯一约束,Bytebase 将计划检查的配置从持久化副本改为纯函数派生,删除了config/payload两列,把getPlanCheckRunFromPlan()收敛为DeriveCheckTargets/DeriveReviewTargets两个派生入口,并在调度器中按"取运行 → 取 Plan → 派生目标 → 执行检查 → 写回结果"的流程运行。对于想要理解 Bytebase 计划检查、审批门禁与 gh-ost 集成链路的开发者,backend/runner/plancheck 目录下的derive.goscheduler.gocheck_target.go与配套测试是最直接的阅读起点。

【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 2:28:20

CTF Web入门:HTTP协议与请求头伪造实战解析

1. 第二章到底在学什么ctfshow的「web应用安全与防护」系列,在CTF圈子里基本算入门必修课。这系列题不像pwn和reverse有很高的门槛,也不需要你把汇编、内核啃完再动手,是一套「从零到一让你理解Web漏洞到底是怎么产生的」的题目集。第二章的位…

作者头像 李华
网站建设 2026/9/15 2:27:24

WPF自定义AutoGrid控件:动态网格布局的优雅解决方案

最近在调 WPF 上位机界面,又遇到那个绕不开的老需求:界面上要动态显示一组工位状态卡片,工位数不固定,今天可能是 6 台,明天加了产线就变成 14 台,卡片还得按网格对齐。最笨的办法是每次在后台代码里往 Gri…

作者头像 李华
网站建设 2026/9/15 2:26:41

基于PyTorch与Flask的垃圾分类系统开发部署全指南

简介:这是一套以Python为核心实现、并配有部署指南的垃圾分类系统毕业设计资源,面向计算机、通信、人工智能、自动化等相关专业学生及从业者,既可用于毕业设计、课程大作业,也适合作为从零搭建项目的进阶练习。项目源代码经过调试…

作者头像 李华
网站建设 2026/9/15 2:24:33

微信小程序云开发实战:星座运势与周公解梦源码拆解

简介:这是一份面向微信小程序开发者和个人站长的星座运势与周公解梦双模块源码,适合想要快速搭建内容查询类小程序、或学习云开发项目结构的初中级开发者。压缩包共294个文件,大小仅1.41MB,其中gif与png图片素材共182个&#xff0…

作者头像 李华
网站建设 2026/9/15 2:24:32

JavaScript+MySQL高校招生智能问答系统实战

简介:本资源是一个基于JavaScript与MySQL开发的高校招生咨询智能问答系统,面向计算机专业本科生及Web开发初学者,解决招生政策查询响应滞后、人工咨询覆盖不足等实际问题,适合作为优质毕业设计或课程设计项目参考。压缩包共663个文…

作者头像 李华
网站建设 2026/9/15 2:24:30

DenseUnet盐体分割实战:地震剖面像素级掩膜与工程调优指南

简介:基于DenseUnet的岩石盐体图像分割实战项目,为深度学习入门者与地质遥感研究人员提供了一条完整可复现的路径。资源包含Python训练、评估、预测三个核心脚本,代码注释详尽,配合约20MB轻量压缩包,可快速完成从数据准…

作者头像 李华