Skip to content

feat: 日志增量与按时间点恢复:MySQL binlog、PostgreSQL 物理全量与 WAL - #10

Merged
CodFrm merged 38 commits into
mainfrom
feat/incremental-pitr
Oct 12, 2026
Merged

CodFrm merged 38 commits into
mainfrom
feat/incremental-pitr

Conversation

@CodFrm

@CodFrm CodFrm commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

概要

本 PR 是第 3 轮:日志增量与按时间点恢复。需求见 docs/specs/2026-10-08-incremental-pitr.md,它包含运行时验证之后的两次修订。

备份方式:

  • MySQL / MariaDB:“全量 + 增量(binlog)”。
    • 以副本身份持续接收 binlog,按“增量归档间隔”(1–60 分钟,默认 5)写进仓库。
    • 指定数据库时,恢复只重放这些库的变更。
    • 需要 binlog、ROW 格式、复制权限和 RELOAD 权限,缺任一项时向导灰掉该方式并给出修复语句。
  • PostgreSQL 15–18:“物理全量 + WAL 增量”。
    • 在源库建立复制槽 opsnap_<任务 ID>,基础备份覆盖整个集群。
    • 暂停、改为仅全量或删除任务时删除复制槽。
    • max_slot_wal_keep_size 未设上限时提示风险;有自定义表空间时不可用。
  • 编辑任务可以切换备份方式。改为增量后立即执行一次全量,修改归档间隔在当前间隔结束后生效。

接收与归档:

  • 断线自动重连;主控端重启后从最后一次成功归档处续接。
  • 未归档的日志只以加密形式暂存,密钥只在内存中。
  • 主控端磁盘剩余不足 1 GiB 时暂停接收。
  • 增量快照中存放原样的 binlog / WAL 段文件,带任务、全量、起止位点与时间等标签。
  • 归档失败显示原因并在下个间隔重试。
  • 增量中断(binlog 被清理、复制槽被作废等)会立即自动补一次全量,触发方式为“增量中断后重建”。
  • PostgreSQL 物理任务暂停后再启用:先重建复制槽并执行一次“启用后重建”的全量,不标为增量中断。
  • 增量只保留最近 N 天,增量不生成运行记录。

恢复(任务详情 → 恢复):

  • 方式:选快照,或按时间点(精确到秒,按事务提交时间截止)。MySQL 5.7 与 MariaDB 按事件时间近似截止。
  • 目标与确认:
    • MySQL 与 PG 逻辑模式可以恢复到原数据源或另一个同类数据源,只替换快照中的库,不沿用原 GTID。
    • 恢复的导入和重放都写进目标的 binlog。
    • 恢复到原数据源需要输入名称,并默认先做一次恢复前快照。
    • 目标上已有同名库时需要勾选确认;版本、类型与并发都有规则。
  • PG 物理模式:恢复到 SSH 主机。
    • 先“检查目标”:已停机、PG 版本、写权限、属主、保留目录。数据目录是挂载点时,写明将复制的大小与剩余空间,空间不足时不能开始。
    • 然后移走原内容、写入基础备份与 WAL、执行启动命令、等待提升为主库、清理。
    • 恢复到本任务的源集群时,暂停接收,成功后补一次“恢复后重建”的全量。源库仍在运行时,同主机上的物理副本按普通目标处理。
  • 恢复可以取消;失败时写明步骤、原因和目标状态。恢复不计入概览。
  • README 写明不经过 OpsNap、只用 kopia 官方 CLI 和官方工具做按时间点恢复的步骤,以及选取全量与增量的规则。

界面:

  • 向导第 2、4、5 步加入增量选项;任务列表显示增量状态标签。
  • 任务详情加入“增量”面板:已接收位置、延迟、最近归档、可恢复区间与时间条。
  • 三种恢复弹窗:按时间点、选快照、PostgreSQL 物理。

验证

  • 在 541eaf2 上 make verify 通过:golangci-lint 0 issues,47 个 Go 包测试通过,Vitest 512,Playwright e2e 55。
  • 收尾评审:按 spec、按代码各一轮。加上修正轮的两轮评审,发现的问题都已修复。
  • 运行时验证:在 coding.internal 上运行真实镜像,依赖 docker.internal 上 opsnap-test 的真实服务:MySQL 5.7 / 8.0 / 8.4、MariaDB 11.4、PostgreSQL 15 / 16 / 18、带 sshd 的 PG 15、MinIO、SOCKS5。
    • 第一次验证发现 5 项不成立,修正轮修复后完整复验,全部成立:
      • 恢复导入原先不写 binlog,导致之后的按时间点恢复失败。
      • RELOAD 探测原先按子串匹配,用户名里含权限字样会被误判为具备该权限。
      • 数据目录是挂载点时,“移到旁边保留”实为复制,文案却写“不占额外空间”。
      • 同 IP 上的另一个集群原先被当作本任务的源集群。
      • PG 物理任务暂停后再启用,原先会误报一次增量中断。
    • MySQL 8.0 / 5.7 / MariaDB 与 PG 物理的按时间点恢复,数据与写入时刻逐轮一致,包括 DDL、按库过滤和范围外的库。
    • 离线恢复:按 README,只用 kopia CLI 0.23 和官方 mysqlbinlog / PostgreSQL,MySQL 与 PG 物理各做一次按时间点恢复,数据一致。
    • 秘密扫描覆盖进程 argv、元数据库、容器日志、数据目录(含暂存)与全部接口响应,0 命中。仓库密钥与令牌只在生成它们的响应中出现,这是设计如此。
    • 替代观察:磁盘不足 1 GiB 用 tmpfs 数据目录模拟。

已知问题与未观察

  • 重建全量刚完成时,如果存储同时不可读,增量面板仍显示“接收中”,而接收尚未开始。存储恢复后会自动补上,数据不丢。
  • 运行时未观察:
    • 增量保留 N 天;
    • PG < 15 等使物理方式灰掉的条件;
    • MySQL binlog 保留不足 1 天的提醒;
    • 若干失败与超时路径;
    • 保留目录空间不足时不能开始(由单元测试与 Playwright 覆盖)。

元数据库变更

  • 新增表:job_incrementals,记录每个任务的接收状态。
  • 扩展:jobs 增加 archive_interval 与 incremental_since;job_runs 增加恢复相关字段与物理恢复参数;job_incrementals 增加 enabled_at。
  • 迁移:migrations/20261008_job_incremental.go、20261009_job_incrementals.go、20261010_job_run_restore.go、20261011_job_incremental_since.go、20261011_job_run_restore_physical.go、20261012_job_incremental_enabled_at.go。

CodFrm added 30 commits October 9, 2026 11:57
- 增量(binlog)任务的全量用 --source-data=2 / --master-data=2 在导出头部取得位点,GTID 开启时用 --set-gtid-purged=COMMENTED 取得 GTID 集合,MariaDB 由导出的文件和位置经 BINLOG_GTID_POS 换算;位点写进快照标签 start-pos 与 start-time 和运行日志,取不到时本次运行失败
- 仅全量任务的导出参数与行为不变
- compose 增加按 profile 启动的 MySQL 5.7、8.4、MariaDB 11.4、PostgreSQL 15、18 与带 sshd 的 PostgreSQL 目标;test-env.sh 增加 profile-up / profile-down;testenv 增加读取函数;补充 testing.md、verification.md 与 .env.example
- 新增 internal/pkg/pgphys:经复制连接建立、读取与删除物理复制槽 opsnap_<任务 ID>,执行 PG15+ 的 BASE_BACKUP(WAL、MANIFEST)并自行解码 n/d/p/m 子消息,得到 base.tar(含 pg_wal/)与 backup_manifest;位置写成 <LSN>;tli=<时间线>
- 低于 15 或有自定义表空间时本次运行失败并写明原因,不建复制槽;没有收到终点或备份清单时不形成快照,归入“校验”
- kopiarepo 的 SnapshotRequest 增加 Finalize:流读完后补全标签;物理全量的 start-pos/end-pos 为起止位置,start-time 为收到终点的时刻
- BASE_BACKUP 在快照写入开始后发出;暂停、删除任务或改为仅全量时删除复制槽,失败只记日志
- 真实测试在临时 PG 15、18 上证明只用这份快照即可在 pg-ssh 上恢复并成为主库;更新 AGENTS.md 与 architecture.md
…口执行确认、版本与并发规则,可选恢复前快照,取消与失败写明目标当前状态,kopia 快照流式读取,按目标版本选用 mysql 并以 0600 选项文件传凭据
…00 临时文件交给 mysqlbinlog / mariadb-binlog 重放,由目标分配新的 GTID,重放失败写明重放到的时刻;README 补离线按时间点恢复步骤
…段补零)与恢复配置,执行启动命令(输出去掉秘密)后经 SSH 隧道等待提升为主库并清理;恢复到本任务源集群时先停接收、成功后以“恢复后重建”重建;探测读取 data_directory 供预填;README 补离线物理恢复步骤
…前方式能恢复的全量(e2e 用 bin/seedsnap 准备物理全量)、仅全量任务按增量链清理旧增量、第 4 步另有增量说明、快照库清单未知时预先要求确认同名库、提升为主库时写出重放位置、物理方式日志不提导出工具,并撤回备份任务 spec 中多出的修订
…断的归档不算归档失败;reconcile 对刚结束的接收重新读取状态、同一任务只留一个接收,关闭时先取消;PostgreSQL 保活只在其后再无 WAL 时报告已追上;mysqlbinlog 重放不再因 WaitDelay 截断输出;工具读取输入出错时报读取错误并修复竞争;物理恢复的启动命令输出经远端临时文件、提升后检查数据目录的偶发 SSH 失败重试;逻辑恢复对 13 以下目标不用 WITH (FORCE),全局对象超限报错;binlog 起点在首个文件末尾时接着下一个文件;恢复选项给出每份全量的可恢复区间,弹窗按它选基础全量;有恢复进行时持续高频刷新,换快照后同名库确认失效;合并重复的毫秒取整、PG 连接串与 TLS 文件、复制连接 TLS、重建排队与工具输出缓冲
…目标恢复,不暂停接收、不补恢复后重建;检查结果写明是哪一种;地址相同的主机标为“与源库同一主机”
…接收;暂停时本任务自己删除的复制槽不判为中断;接收流记下所属的任务启用时间,启用时间只增不减
…INLOG MONITOR 的 REPLICATION CLIENT 算作具备复制权限;README 离线恢复写明新接收流(restart)出现的全部情形;补全运行触发方式说明
…群;排队启用后重建失败时状态显示已暂停;挂载点上 du 读不全时按读不出大小处理;去掉空断言测试并合并重复的排队逻辑
@CodFrm
CodFrm merged commit 3242b5b into main Oct 12, 2026
5 checks passed
@CodFrm
CodFrm deleted the feat/incremental-pitr branch October 12, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant