跳转到内容

Archive 保留与清理:到底能删掉多少 log?

archive 卷快满了,有人想删掉旧的 cluster-log 段。archive API 会很配合地让你删。purgeSegments 懂段边界,也懂哪些 recording 正在活动——但它完全不知道你删掉的这些字节,是不是某个节点免于重建的唯一依靠。

本页就是那道缺失的检查。它给出该测哪些位置、哪条不变式决定「重启还是重建」,以及一套失败时也安全的清理流程。 节点已经故障之后该怎么办,见快照失败与恢复;为重建路径兜底的备份, 见备份与灾难恢复。

这个场景归根到底只有一个问题

Section titled “这个场景归根到底只有一个问题”

在删任何东西之前,在启动一个已经离线一阵子的节点之前,你回答的其实是同一个问题:这个节点能正常重启,还是必须重建?

节点本地状态活的日志源是否覆盖到该节点的位置?结论
完整快照集 + 本地 log 连续是(L ≤ P)正常重启。 加载本地快照 → 本地回放 → 向 leader 追赶。
完整快照集 + 本地 log 连续否(L > P)重建。 追赶中间有个缺口,重启多少次都补不上。
无快照,本地 log 从位置 0 起连续是正常重启,全量本地回放。慢,但成立。
无快照,本地 log 起点不为 0——重建,除非这是一份有意 rebase 过、且经过验证的镜像。
最新快照提交了却加载不起来上一代快照集及其 log 仍在先 invalidate-latest-snapshot,然后重新核对这张表。
空盘(换过的实例)活的日志源从 0 开始都还留着重启能成,但通常比直接用备份provision更慢。
空盘活的日志源已清理过 0 之后的部分从经过验证的镜像重建。 没有例外。

下面全部内容,就是怎么测出 L 和 P,以及怎么在清理时永远不把某个节点从第 1 行推到第 2 行。

按时间做保留决策——「留七天」——只是对真正重要的东西的一种近似,真正重要的是位置。要测的是这几个:

记号含义从哪里读
S某个完整快照集所对应的 log 位置ClusterTool recovery-plan
A该节点 cluster-log recording 中实际还在盘上的最早位置ArchiveTool describe
P重启节点本地可用 log 的末端(append 位置)ClusterTool recovery-plan / archive 描述
L所选活日志源能回放或复制的最早位置在那个源上跑 ArchiveTool describe
C清理后 recording 的新起点(也就是切点)你的清理方案

一个完整快照集是一份 consensus module 快照(serviceId = -1)加上每个已配置 service 各一份快照,且全部处于同一 leadership term 和同一 log 位置。少了一个 service 的快照集不是「退路」——它什么都不是。

由此直接得到两条不变式。

本地快照恢复成立的条件:

A ≤ S ≤ P

滞后节点能追上的条件:

L ≤ P

又因为清理只能按段边界进行,本地实际可用的切点是:

C = 满足 C ≤ S 的最大有效段边界

千万别把 C 向上取到 S 之后。本地恢复的回放正是从快照位置开始的;把 S 处的 log 拿走,你精心保留的那个快照就成了废物。

上面的不变式带来一个策略后果,而多数团队会一直把它留在默认状态,直到某次故障逼着面对。请明确地选:

模型 A —— 任何已配置成员都能正常重启。 对每个成员,你都保留一份完整且经过验证的快照集、此后的本地 log,以及活日志源上 足以从该成员最后一次确认的 append 位置接续的那段 log。你的清理永远不会越过任何离线成员的恢复位置。这会限制你回收磁盘的力度, 而且一个长期离线的节点就能把整个集群的保留策略绑住。

模型 B —— 窗口之外的节点一律重建。 你只保留够最近重启用的量,跑一份持续验证的 ClusterBackup,更老的 log 就放掉。 落在窗口之外的节点直接重新归类为重建,而不是重启。

对可替换的云实例来说,模型 B 才是合理的默认值,而且这个模型更诚实——它把备份变成了承重结构,也逼着你必须去测。模型 A 则是在替一个 几周没人检查过的节点,悄悄许下「能正常重启」的承诺。

启动滞后节点之前:go/no-go 关卡

Section titled “启动滞后节点之前:go/no-go 关卡”

「文件都在,那就启动吧」——20 分钟的重建就是这样变成 4 小时故障的。先跑下面这套,保持节点停止。

Terminal window
AERON_ALL_JAR=/opt/aeron/aeron-all.jar
CLUSTER_DIR=/var/lib/my-cluster/cluster
ARCHIVE_DIR=/var/lib/my-cluster/archive
SERVICE_COUNT=1
java -cp "$AERON_ALL_JAR" io.aeron.cluster.ClusterTool "$CLUSTER_DIR" recording-log
java -cp "$AERON_ALL_JAR" io.aeron.cluster.ClusterTool "$CLUSTER_DIR" recovery-plan "$SERVICE_COUNT"
java -cp "$AERON_ALL_JAR" io.aeron.archive.ArchiveTool "$ARCHIVE_DIR" describe

记下 S、A、P、L,再加上 H(当前集群 commit 位置)和 B(最新一份经过验证的备份镜像的位置)。然后确认:恢复计划 选中的每个 recording 都真的存在;快照集覆盖了 consensus module 和每一个 service;本地 log 在将要回放的那段范围内连续; 成员 ID、集群 ID、service 数量、应用版本和各个 endpoint 都正确。

本地回放量 = P - S (有快照)
全量本地回放量 = P - A (无快照)
leader 传输 / 追赶 = H - P
备份镜像的尾部追赶 = H - B

这些是字节范围,不是时间——本地 service 回放通常受 CPU/应用限制,而 archive 复制受存储或网络限制,所以要把它们换算成时间, 得靠你自己恢复演练量出来的吞吐。把那些数字留着:回放 位置/秒、恢复 字节/秒、复制 字节/秒。

最新的基线并不自动等于最快的基线。 一个只落后 20 分钟、但回放要按应用速度走的节点,可能反而输给一份老了一小时、 却能按磁盘速度恢复的备份镜像。选「准备 + 回放 + 传输 + 校验」总和最低的那条。

3. 出现下列任一情况,直接否掉正常启动这条路

Section titled “3. 出现下列任一情况,直接否掉正常启动这条路”
恢复计划选中的快照 recording 不可用
或本地回放范围不连续
或 L > P
或没有快照,且 A 并不代表一个有效的初始状态

提前把放弃条件定下来,因为故障中途的乐观情绪正是吃掉时间窗口的东西:

  • 在一个恢复超时周期内,恢复位置毫无推进;
  • 反复回到同一个选举状态;
  • 请求的回放位置早于源 recording 的起点;
  • archive 报告 recording 未知或段不可用;
  • 预估完成时间已经超出你的 RTO。

恢复进行期间,暂停清理任务,不要让两个 follower 同时冲同一个 leader archive 恢复,也不要去限速你正指望着的那条复制链路。

盯选举状态、本地复制与回放位置、append 位置、commit 位置、剩余差距 H − 本地位置,以及 archive/cluster 错误计数器—— 计数器和告警规则见指标与告警。成功的标准是 FOLLOWER_READY、位置收敛 与应用状态校验全部通过,而不是「进程还活着」。

你会依次经过的三个 follower 状态:

具体会跑到哪几个阶段,取决于节点的位置——比较新的节点可能直接进追赶,滞后的节点三个阶段都要走。

purgeSegments 只保证两件事,而且都写在它自己的 javadoc 里:新起点「必须是现有起点之后某个段的首字节位置」, 以及「正在录制或正在被回放的段是无法分离的」。这两条都不是恢复层面的检查。下面这套流程才是。

触发一次集群快照,等所有需要支持本地正常重启的成员都完成,并在每个成员上确认拿到了完整快照集。记录每份快照的位置和 recording ID。然后验证这些快照 recording 真的能回放出来——recording.log 里有条目只是元数据,不是证据。对备份也做同样的事, 并记下它的位置。

每个成员保留的最旧完整快照位置记为 S_member;每个被许诺可以正常重启的离线成员,其最后一次确认的 append 位置记为 P_member。

C_live ≤ min(所有被许诺正常追赶的成员的 P_member)
C_member ≤ S_member

向下取到一个有效的段边界。如果某个离线节点落在了拟定切点之后,就把选择摊开来做:要么推迟这次清理,要么撤回「可以正常重启」 这个承诺、把该节点标记为待重建。别让算术悄悄替你决定。

动存储之前先产出一份清理清单:集群与成员 ID、cluster-log recording ID、当前起止位置、拟定的新起点、保留下来的各个快照集、 被保护的最旧那个可正常重启成员的位置、备份位置与最近一次成功恢复测试的日期,以及预计会变成可分离状态的确切段文件。

出现下列情况就否掉这次清理:没有保留任何完整的本地快照集;切点落在 S 之后;某个受保护的滞后节点位置早于 C_live; 在模型 B 下备份缺失或未经测试;选中的 recording 中有正在活动或正被回放的;catalog、recording.log 与实际 recording 三者不一致。

全程保住 quorum,并且一次只动一个节点:

  1. 干净地停掉一个 follower;
  2. 备份它的 cluster 元数据和 archive catalog;
  3. 把段分离到算好的边界;
  4. 校验 archive 和恢复计划;
  5. 只有校验通过之后,才删除已分离的段;
  6. 重启该 follower,等到 FOLLOWER_READY;
  7. 校验 service 状态和位置收敛,然后再动下一个 follower。

leader 放到最后处理,等 leadership 已经转移,或者放进一个受控的维护窗口里。

永远不要把文件系统删除当成清理手段。走 archive API,好让 catalog 和 recording 起点保持一致——对段文件 rm 一下, catalog 就还在描述已经不存在的数据了。

磁盘腾出来了不算成功。在退掉上一代之前,要测试:当前节点能否重启;处于所支持最旧滞后位置的节点能否重启;能否回退到上一代保留快照; 能否从备份重建一台空主机;以及追赶完成后应用状态是否一致。

对象规则
cluster-log 段在算好的安全边界之前可以分离,前提是满足上面那些不变式。只要 recording.log 还在引用某条 recording,就绝不要把它整个清掉。
快照 recording要删的完整代必须比所有保留退路都旧,并且不再被目标恢复计划或任何恢复流程引用。存储允许就保留两个完整代。绝不要只留某一代里的部分 service。
孤立的快照 recording失败快照留下的、从未写进 recording.log 的 recording,在确认没有恢复元数据引用它们之后可以回收。
recording.log恢复元数据,不是批量数据。不要跟着段一起清,也不要手工改。

按位置做自动化,别按磁盘用量

Section titled “按位置做自动化,别按磁盘用量”

时间或剩余空间策略可以提议一个切点;但只有位置检查有权批准它:

候选切点 = 受下列因素约束的段边界:
保留下来的本地快照位置、
受保护的滞后成员位置、
备份 / 重建策略、
recording 与回放的活动状态

除了集群计数器之外,还要盯 archive recording 的起止位置、每个成员最新两个完整快照 的位置、备份状态与年龄、磁盘耗尽的预计时间,以及最近一次空节点恢复测试成功的日期。这最后一项指标才是关键——它能告诉你 模型 B 到底是不是真的可靠。

具体到 EC2 部署:如果希望实例替换之后本地恢复能力还在,就把 archive 和 cluster 状态放到持久化存储上;否则就宣布实例是 一次性的,并把备份 provision 做进 bootstrap 流程。至少跑一份持续验证的 ClusterBackup,放在集群故障域之外。另外, 除非你有意支持「从 0 全量 log」恢复、或者那台主机已经先 provision 过,否则绝不要在空主机上复用成员 ID。

  • RTO 几乎完全由你选了哪个恢复基线决定,而这个选择又被你几周前清理掉的东西约束着。P − S(回放距离)是你靠提高快照 频率来控制的那个数;H − B 是你靠备份新鲜度来控制的那个。
  • p99 / 最大延迟 只受间接影响,但这条联系是真实的:阶段 1 要求做一次集群快照,而在 OSS 里所有成员都在同一 log 位置 打快照,于是一次保留周期就会给热路径带来一次 stop-the-world 停顿。在繁忙的撮合引擎上,这会落在最大延迟上——如果你用 Premium,这就是standby 快照的理由;如果没用,那就把清理窗口排到低峰期。
  • 吞吐量 不受清理本身影响,但一个正在恢复的 follower 会和实时流量抢 leader 的 archive 回放、磁盘和网络。同时恢复两个, 就是把一次恢复变成一次吞吐故障的做法。
  • 清理语义逐字引自 AeronArchive.java 中 purgeSegments、detachSegments、deleteDetachedSegments 的 javadoc(「must be first byte position of a segment after the existing start position」;「not possible to detach segments which are active for recording or being replayed」),服务端的强制检查在 ArchiveConductor.purgeSegments (isValidDetach / isDeleteAllowed)。
  • follower 追赶状态取自 ElectionState.java (FOLLOWER_LOG_REPLICATION(9)、FOLLOWER_REPLAY(10)、FOLLOWER_CATCHUP(13)、FOLLOWER_READY(16))与 Election.java; 启动时从本地元数据恢复见 ConsensusModuleAgent.java, 恢复计划的构建与校验见 RecordingLog.java。
  • seed-recording-log-from-snapshot 把位置 rebase 到零、以及 recording.log.bak 备份,连同 recording-log / recovery-plan / invalidate-latest-snapshot 各命令,均出自 ClusterTool.java。 archive 侧工具(describe、verify、compact、delete-orphaned-segments、mark-invalid)出自 ArchiveTool.java。
  • 运维指引:Cluster Backup 与 Operating Aeron Cluster。
  • 背景:The Aeron Files。

本站与 Adaptive Financial Consulting Limited 或 Aeron 项目无任何关联,未获其背书或赞助。 Aeron 是 Adaptive Financial Consulting Limited 的注册商标。

Aeron 是 Adaptive Financial Consulting Limited 在英国及其他国家/地区的商标。