备份与恢复窗口¶
一个目标的历史是一条链:按计划进行的全量备份,以及两次全量之间连续不断的变更流(Postgres WAL、MySQL binlog)。 凡是被某个全量备份及其后续变更覆盖的每一秒,都可以恢复。这段跨度就是时间点窗口。
gantt
dateFormat YYYY-MM-DD HH:mm
axisFormat %a %d
section Full backups
daily full :milestone, 2026-10-01 02:07, 0d
daily full :milestone, 2026-10-02 02:07, 0d
daily full :milestone, 2026-10-03 02:07, 0d
daily full :milestone, 2026-10-04 02:07, 0d
section Change stream
WAL / binlog segments :active, 2026-10-01 02:07, 2026-10-04 18:00
全量备份¶
| 引擎 | 模式 | 工具 | 恢复 |
|---|---|---|---|
| Postgres 15–17 | physical(默认) |
通过复制协议执行 pg_basebackup -Ft -X fetch |
基础备份 + WAL 重放,精确到秒 |
| Postgres 15–17 | logical |
pg_dump -Fc,每个数据库一个对象;支持排除表和选择性导出 |
pg_restore,存在物理链时再通过它重放 WAL |
| MySQL 8.4 | logical |
mysqldump --single-transaction --source-data=2(例程、事件、触发器) |
加载 + binlog 重放,精确到秒 |
每个备份都会记录一份清单:数据库、表、列、索引、行数估计和大小,以及存在迁移表时的迁移版本。
控制台和 tenax diff 比较的就是这些内容。
变更流¶
每个启用了时间点恢复的目标都有一个流式传输程序:
- Postgres:Tenax 自己的 WAL 接收器,基于物理复制槽(ADR 0005)。 复制槽保证流式传输程序重启期间不丢失任何 WAL。
- MySQL:从上一次全量备份的 binlog 位置开始执行
mysqlbinlog --read-from-remote-server --raw --stop-never。
每个段写完即上传,仍在写入的段每隔 partialInterval(默认 60 秒)上传一次,所以最后一分钟同样可以恢复。
每个段都附带一份摘要:每张表的提交、插入、更新和删除次数、DDL,以及可选的脱敏行预览。
如果流中断(复制槽丢失、binlog 被清除),Tenax 会把这条链标记为断裂,发出告警(TenaxStreamChainBroken),
启动一条新链并立即进行一次全量备份(原因为 chain-restart)。旧窗口在断点之前的部分仍然可以恢复。
保留¶
全量备份按层级保留;变更段只保留到窗口所需的时间范围;其余一切由垃圾回收清除,但已固定的备份绝不会被删除。
| 预设 | 窗口 | 每日 | 每周 | 每月 | 每年 |
|---|---|---|---|---|---|
prod |
7 天 | 14 | 8 | 12 | 5 |
dev |
3 天 | 7 | 4 | 3 | 0 |
critical |
14 天 | 30 | 12 | 24 | 7 |
一天中第一个提交的全量备份就是当天的每日备份;一个 ISO 周中的第一个就是当周的每周备份,依此类推。
一个备份可以同时属于多个层级,不会产生任何复制。固定(tenax pin <id> --reason …)可以让备份不受层级约束地保留下来。
GC 在删除前会先把计划写入存储桶,先删 manifest 再删数据,并在一天后清理孤立的和未完成的上传。
存储格式¶
<store prefix>/<org>/<project>/<namespace>/<target>/
full/<backup-id>/manifest.json # written last: the commit marker
full/<backup-id>/data/<database>.zst.age # dumps or the base backup tar
full/<backup-id>/inventory.json.zst.age # also readable with the catalog key
full/<backup-id>/tags.json # tiers, pins, verification results
stream/…/<segment>.zst.age # WAL / binlog segments and their summaries
stream/position.json # the streamer's confirmed position
- 压缩:按策略设定的级别执行 zstd 压缩,只压缩一次,在加密之前进行;默认开启
(ADR 0013)。
compression.algorithm: none则按原样存储数据。 - 加密:每个对象都在客户端用 age 加密给存储的接收者。存储桶永远看不到明文。
- 完整性:每个对象的大小和 SHA-256 都记录在 manifest 中,每次读取时都会校验。
- 提交:备份只有在写入
manifest.json之后才算存在。在此之前发生崩溃只会留下由 GC 清除的孤立对象, 绝不会出现一个看起来完整的备份。
负载控制¶
备份从不盲目地与生产业务争抢资源:有按主机、按目标和全局的并发槽位、带宽限制、优先级、计划时间抖动,
以及一个负载保护机制,在服务器活跃连接过多、复制延迟过大或存在长时间运行的事务时推迟全量备份。
所有这些都在 BackupPolicy 中设置。