数据库灾备【免费下载链接】databasusPostgreSQL backup tool with Point-In-Time-Recovery and restore verification项目地址https://gitcode.com/gh_mirrors/po/databasus点击查看免费下载本文基于 Databasus 已归档的变更设计文档 automatic-physical-reanchor-after-failover深入讲解当 PostgreSQL 主库发生 promotion故障转移后物理备份如何检测时间线切换、自动调度一次即时替换 FULL、在替换期间保住 WAL以及同一时间线上的 PITR 如何继续可用。读完本文你可以理解 Databasus 物理备份在故障转移场景下的完整行为边界、调度器优先级规则以及事务级咨询锁如何防止孤儿 WAL 清理误删正在运行的 FULL 备份所需的日志段。问题背景一次 promotion 如何打断增量备份链PostgreSQL 的增量备份pg_basebackup --incremental要求前后两次备份处于同一条时间线上每个 INCR 必须能沿着自己那条链的 WAL 回放到父备份的stop_lsn。一旦源集群执行了 promotion例如从 standby 提升为主pg_control之外的状态会切换到新时间线旧时间线为根的增量链就此失效。在实现该变更前Databasus 存在三个相互叠加的缺陷时间线判断的最大时间线陷阱。原CheckTimelineCompatibility将 live 时间线与目录中已完成 FULL 行和.history行里的最大时间线比较见 timeline.go 的设计文档引用位置。一旦 WAL streamer 把新时间线的.history行编目进来这个最大值就会与已提升的服务器一致——即使某个 INCR 仍然锚定在旧时间线上的根 FULL。也就是说看起来时间线匹配和这条链真的可以延伸不再等价。断裂后要等一个完整增量周期才被处理。调度器先检查增量节奏再处理可延伸链缺失的情况因此一条刚断掉的链可能空等一个完整的增量间隔如果 FULL 周期是每周缺口可达数天。孤儿 WAL 清理与 FULL 创建之间没有互斥。清理器发现并删除 WAL 段时不与 FULL 的创建串行化首次 FULL 运行期间归档的 WAL 有被删掉的风险而恢复选择又直接依赖保留策略拥有的链区间旧链无法看到比新 FULL 起点更远的同时间线 WAL。设计文档同时指出已有的持久化协调原语——终端状态的备份行、单在途in-flight声明、以及三次尝试即触发 FULL 的刹车机制——足以支撑这次变更不需要新增状态表。目标与非目标目标Goals以 INCR 所属的根 FULL 的精确时间线作为每个 INCR 的时间线权威在pg_basebackup之前和期间都能检测到 promotion启动一次即时替换 FULL且不制造调度器重试循环在替换 FULL 运行期间保住 WAL保住同时间线 PITR。非目标Non-Goals跨多条 PostgreSQL 时间线的恢复在数据库模型上持久化当前时间线新增 reanchor 状态表、新的备份配置项或新的重试策略。这组非目标非常关键它把变更约束在从现有终端行推导状态的范式内而不是引入第二套事实来源。决策一INCR 用根 FULL 的时间线做兼容性判断变更前FULL 与 INCR 共用一套时间线决策变更后拆成两个入口INCR 决策CheckIncrementalTimelineCompatibility接收根 FULL 的时间线作为期望值直接与 live 值比较FULL 决策CheckFullTimelineCompatibility保持与目录历史newestKnownTimelinetimeline.go#L394-L423的比较——因为 FULL 可能开启一条新时间线仍需拒绝时间线回退regression或整个集群变了system identifier 不同。核心比较逻辑收敛在 decideTimelineCompatibility 中产出四种结果TimelineContinue、TimelineFailoverDetectedlive 更新、TimelineRegressionlive 更旧、TimelineDifferentClustersystem identifier 不一致。值得特别关注 live 时间线的读取方式。resolveLiveTimelineID 的注释解释了一个极易踩坑的细节控制文件的时间线会滞后于 promotion——PostgreSQL 在 checkpoint 时才更新它而 promotion 请求的 checkpoint 会按checkpoint_completion_target分散执行因此提升后的主库在数分钟内仍会报告旧时间线。如果直接读pg_control_checkpoint()一个过期读数会让 INCR 去延伸一条集群早已离开的时间线。因此实现优先从pg_walfile_name(pg_current_wal_lsn())解析 live 时间线仅在 recovery 状态该函数不可用时才回退到控制文件值。INCR 执行器在打开备份 slot 之前通过现有 repository 加载其根 FULL根元数据缺失或不完整按 broken-parent 处理。被否决的备选方案在数据库行上存储当前时间线这复制了已经挂在备份工件上的数据而且仍然不能回答某条 INCR 必须延伸的是哪条时间线新增 reanchor 状态表断链的 INCR 行或失败的 FULL 行本身就能在重启后存活并记录了为什么需要替换。决策二仅在流异常失败后复检集群身份preflight 决策返回紧邻流开始之前观察到的 live 时间线。当流返回非完成结果、且执行上下文仍然活跃时执行器打开一条全新的检查连接重新评估身份。分类规则实现见 executor.go场景复检结果终端状态INCR 流中断live 时间线更新TIMELINE_SWITCH_DETECTEDCHAIN_BROKEN替换掉临时性的流失败结果FULL 流中断live 时间线比 preflight 更新FAILOVER_DURING_BACKUPERROR时间线回退或 system identifier 变化沿用既有安全拒绝TIMELINE_REGRESSION/SYSTEM_IDENTIFIER_MISMATCHCHAIN_BROKEN复检本身连接失败保留原始结果并记录日志不变备份被取消ctx.Err()非空或状态为 CANCELED不触发复检保留取消结果两个新错误原因在 API 枚举中是纯增量扩展不删除、不重命名既有值TIMELINE_SWITCH_DETECTED与FAILOVER_DURING_BACKUP定义于 enums.go#L41-L42序列化行为由 enums_test.go 的 JSON 断言锁定。实现上有几个刻意的保守选择对应recheckFullStreamFailure与recheckIncrementalStreamFailureexecutor.go#L285-L331复检失败时保留原始流失败结果common.Logger.WarnContext记录后原样返回只有复检成功且判定为 failover 时才改写分类。被否决的备选方案按signal: interrupt文本分类 stderr同一段文本可能来自客户端取消也可能来自内部流的看门狗stderr 单独无法证明发生了 promotion每次成功备份都复检成功的流元数据已经提供了完成工件的时间线既有的 history 校验覆盖了这条路径重复复检没有收益。决策三从终端行推导一次即时替换 FULL调度器优先级变更后的逐 tick 决策顺序实现于 decideBackupKind显式强制 FULLForceFullRequestedAt非空时间线相关的终端备份行且尚未收到替换尝试既有的强制 INCR 与节奏cadence决策。注意第 1 项之前还有一个前置闸门hasUnresolvedSystemIdentifierMismatch见下文为真时自动调度整体被抑制仅强制 FULL 可以穿透。待替换的判定条件decideImmediateReplacementFull 只依赖两个终端行断链 INCR最新 INCR 行状态为CHAIN_BROKEN且错误原因为TIMELINE_SWITCH_DETECTED且不存在CreatedAt晚于该 INCRCompletedAt的 FULL 行——即还没有人为它发起过替换尝试被 failover 打断的 FULL最新 FULL 行状态为ERROR且错误原因为FAILOVER_DURING_BACKUP它本身就是待替换信号。两个巧妙的不变量避免了引入额外状态插入替换行本身就会改变最新目录状态替换行一经插入CreatedAt晚于触发行的CompletedAt无后续 FULL 尝试条件即不再成立在途声明阻止并发插入同一数据库同时只允许一个在途备份。替换 FULL 若因其他原因失败或被取消其终端行不再匹配任何即时条件后续尝试交还给既有节奏逻辑与recentFullAttemptsWindow刹车即最近 N 次 FULL 尝试中若没有一次完成则不再立即重锚见 hasNoRecentCompletedFull若替换过程中又发生 promotion新的FAILOVER_DURING_BACKUP结果会再产生一次即时尝试——每次 promotion 对应且仅对应一次即时替换。事务时点的再校验关闭陈旧决策窗口多调度器实例场景下一个实例可能在旧决策上行动而第一个替换已经在另一实例上完成并释放了声明。因此 claimAndInsert 在获取与创建声明相同的每库咨询锁之后重新加载最新 FULL 与 INCR 行并通过 isImmediateReplacementTriggerCurrent 重放触发谓词只有当恰好那个终端行仍是当前行、且不存在更晚的 FULL 尝试时才真正插入替换行否则以errBackupDecisionSuperseded静默放弃。这封上了决策在 tick 间过期的窗口。SYSTEM_IDENTIFIER_MISMATCH 的抑制语义SYSTEM_IDENTIFIER_MISMATCH不是对一个错误集群做 FULL能修复的。hasUnresolvedSystemIdentifierMismatch 用一段 SQL 取两表 UNION 后最新的 mismatch 终端时间与最新已完成FULL 的完成时间比较只要没有更晚的完成 FULL自动节奏与 reanchor 决策一律被抑制。规则细节显式强制 FULL 优先级更高允许运维修好连接后重试但普通 preflight 仍会拒绝向不同集群写备份强制 FULL 若以泛化失败或取消结束不能清除抑制——因为它没有证明集群身份只有之后完成的强制 FULL 才能压过 mismatch建立新的可延伸链恢复正常调度。被否决的备选方案由执行器设置ForceFullRequestedAt持久化终端结果与设置请求标志是两次写入存在崩溃窗口调度器推导则不需要第二次状态迁移把所有断链都提到节奏之前会改变SUMMARIZER_OFF的行为——那是源端设置问题FULL 修不好提前只会让 FULL 与断链 INCR 无限交替烧源调度器中对isChainBrokenBySummarizerOff的显式豁免scheduler.go#L912-L917正是保留这一行为的证据只靠在途声明去重陈旧决策声明会在第一个替换结束时消失另一实例届时可以凭旧决策插入第二个替换。决策四用事务咨询锁串行化 FULL 创建与孤儿删除新增一个共享的仓库层操作两条路径在任何行锁之前先取同一把事务级咨询锁coordination.go#L14-L19SELECT pg_advisory_xact_lock(hashtextextended(physical-backup: || ?::text, 0))调度器事务咨询锁 → 在途声明 → 类型化备份行插入见 claimAndInsert 的事务体孤儿删除事务咨询锁 → 读取在途声明 → 若是 FULL 则跳过本轮删除否则在提交前删选定的存储对象与目录行孤儿清理路径见 cleaner.go#L508-L553实际删除经由DeleteOrphanWalSegmentsInSpan并受 WAL 字节与行数预算约束。链级联删除cascadeDelete同样先取这把锁service.go#L614-L620并在需要删除 WAL 前先查HasInFlightFullBackupservice.go#L643-L652保证三个入口的锁顺序一致。被否决的备选方案不持共享锁、只检查在途声明FULL 完全可能在清理器检查完之后立即声明该库并在运行中丢失 WAL——这正是锁要封住的窗口锁一条备份配置行物理目录服务将直接依赖另一个特性的私有持久化模型耦合方向错误。决策五恢复可达性与保留所有权分离保留策略继续维持到下一个已完成 FULL 为止的链区间变化在恢复侧。ResolveRestoreSet 不再用保留区间上界裁剪 WAL 查询而是从所选根 FULL 的起点查询同时间线上界不限的已提交 WALFindByChainSpan(..., chain.Span.Start, LSNMax)restore_set.go#L61-L66再套用既有的连续段检查contiguousWalRun与目标时间判断目标落在旧 FULL 完成之后、新 FULL 完成之前且 WAL 连续 → 用旧 FULL 连续 WAL 完成 PITR即使这些 WAL 越过了新 FULL 的起点目标越过 WAL 缺口 → 返回WalGapBeforeTargetError携带LatestRestorableLSN告知最远可恢复点到达目标需要跨时间线的 WAL → 报告目标不可恢复直到提升后时间线上有 FULL 为止显式能力边界。被否决的备选方案放宽所有链区间同一份 WAL 会归属多条保留链改变删除记账沿.history族谱追到新时间线当前 manifest 与恢复路径只描述单一 WAL 范围跨时间线 PITR 需要独立的专项设计属于非目标。风险与权衡设计文档逐条列出的风险及缓解风险缓解身份复检当时无法连到已提升的服务器保留原始失败下一次正常尝试会重新走 preflight连接修正后调度仍被抑制直至运维介入允许显式强制 FULL仅当其后完成一次 FULL 才恢复正常调度咨询锁冲突UUID 加physical-backup:前缀再哈希避免与其他应用咨询锁命名空间重叠残余 64 位哈希碰撞风险可忽略对象存储删除持有咨询锁保留既有 WAL 字节与行数预算使事务有界替换 FULL 完成前无跨时间线 PITR返回既有的 target-unreachable 错误并作为显式能力边界记录在规格中迁移、验证与可观测行为迁移计划无数据库迁移。部署后端代码发布前运行 PostgreSQL 17 与 18 的 promotion 测试回滚即回退后端变更——新增的错误原因是文本值既有行与 API 载荷保持可读这也是枚举只增不改约束带来的回滚安全性。验证清单tasks.md全部已完成枚举序列化go test ./internal/features/backups/backups/core/physical/enumsFULL/INCR 时间线拆分对相等、更新、更旧、不同集群四象限的覆盖./usecases/physical/postgresql非完成流后身份复检保留取消与探针失败结果调度器优先级、双调度器陈旧决策、替换泛化失败、二次 promotion、mismatch 失败/取消/成功后续、SUMMARIZER_OFF豁免./backuping/physical锁顺序双向验证含Test_CleanOrphanWalForDatabase_WhenFirstFullInProgress_KeepsWalArchivedDuringBackupTest_ResolveRestoreSet_WhenTargetDuringNewerFull_UsesOlderChainWalThroughTarget及既有缺口测试./core/physical/chain_viewPostgreSQL 17/18 promotion 集成覆盖确认保留的时间线历史不能让过期 INCR 跨越其根 FULL 时间线、随后在提升后时间线上自动 FULL、清理、并从新链恢复。可观测行为规格已固化为 postgresql-physical-backup-failover 规格以 WHEN/THEN 场景形式规定了根 FULL 时间线匹配才启动 INCR、promotion 前的拒绝分类、流中断复检的四种情形、一次即时替换的触发与去重、活跃 FULL 期间的 WAL 留存以及新 FULL 运行期间的同时间线 PITR 可用性与跨 promotion 目标拒绝。相关的既有规格还包括 replication-credentials 与 wal-retention 两个子目录下的约束。小结这套设计的全部聪明之处在于拒绝引入新状态时间线权威归属根 FULL 的时间线这一工件级事实即时替换由终端行推导并发安全由既有的在途声明加一把前缀化的事务咨询锁兜住。对运维的直接收益是——源集群 promotion 之后备份系统不再需要人工介入即可在下一个 tick 内开始重建恢复点且重建窗口内的同时间线 PITR 与 WAL 完整性都有规格和测试背书。赞分享数据库灾备【免费下载链接】databasusPostgreSQL backup tool with Point-In-Time-Recovery and restore verification项目地址https://gitcode.com/gh_mirrors/po/databasus点击查看免费下载相关推荐TigerBeetle故障转移自动故障检测与切换机制TigerBeetle故障转移自动故障检测与切换机制 为什么金融级存储必须零停机 支付系统面临的终极挑战当服务器崩溃、网络分区或磁盘损坏时如何确保每一笔数据库金融科技分布式数据库后端Argo Rollouts多集群故障转移自动切换备份集群Argo Rollouts多集群故障转移自动切换备份集群 引言为什么需要多集群故障转移 在现代云原生架构中业务连续性至关重要。单集群部署面临单点故障风险云原生灰度发布DevOps后端如何监控和优化Qwen3-30B-A3B-Thinking-2507-FP8模型的推理性能如何监控和优化Qwen3 30B A3B Thinking 2507 FP8模型的推理性能 Qwen3 30B A3B Thinking 2507 FP8是基于上一篇NormCap高级使用技巧自动识别文本类型与智能格式化输出下一篇CANN pyasc TQueBind.has_tensor_in_que 使用指南查询 Queue 入队状态与源码原理剖析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
RocketRide Dictionary 节点实战:用 LLM 从文本与表格中自动构建企业术语表 【免费下载链接】rocketride-server High-performance AI pipeline engine with a C core and 50 Python-extensible nodes. Build, debug, and scale LLM workflows with 13 model providers, 8 vector databases, and agent orchestration, all from your IDE. Includes VS C… · 2026/9/25 3:15:16
哈工大SSE练习39:C语言在线评测从拆题到AC的完整指南 看到标题里的“SSE”,先别急着把它跟前端那个 Server-Sent Events 对应起来。在哈工大,SSE 是同学们对 C 语言课程那个在线编程练习平台的约定俗成叫法。不管是软件学院还是计算学部的同学,大一学 C 语言基本都绕不开在这上面刷题。系统界面不… · 2026/9/25 3:47:34
conventional-changelog-writer 版本演进全解析:从 v1 到 v9 的架构变迁与配置项深度指南 开发工具CLI文档 【免费下载链接】conventional-changelog Generate changelogs and release notes from a projects commit messages and metadata. 项目地址: https://gitcode.com/gh_mirrors/co/conventional-changelog 点击查看 免费下载 本文以 packages/conv… · 2026/9/25 3:47:34
ESP32上WASM为何不能直接调用硬件:架构设计与安全隔离 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 3:47:28
ipatool:从 App Store 快速下载任意版本 IPA 的命令行工具指南 ipatool:从 App Store 快速下载任意版本 IPA 的命令行工具指南 【免费下载链接】ipatool Command-line tool that allows you to search for iOS, iPadOS, tvOS, visionOS, and macOS apps on the App Store, and download .ipa or macOS .pkg app packages. 项目… · 2026/9/25 3:47:10
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37