news 2026/9/13 17:53:11

Neon 时间线历史保留策略(Retention Policy)解析:PITR、快照与基于 LSN 的垃圾回收

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Neon 时间线历史保留策略(Retention Policy)解析:PITR、快照与基于 LSN 的垃圾回收

Neon 时间线历史保留策略(Retention Policy)解析:PITR、快照与基于 LSN 的垃圾回收

【免费下载链接】neonNeon: Serverless Postgres. We separated storage and compute to offer autoscaling, code-like database branching, and scale to zero.项目地址: https://gitcode.com/GitHub_Trending/ne/neon

本篇文章基于 docs/rfcs/011-retention-policy.md 展开,系统讲解 Neon 中"用户可见的时间线历史"是如何以保留策略(retention policy)的形式存在的:PITR 时间段、命名快照、自动快照与分支点如何在内部统一为 LSN 范围与 LSN 点,并由 base image + WAL slice 两套文件承载。结合 pageserver、safekeeper 与 pageserver_api 配置模型 的源码实现,你可以掌握 Neon 历史数据如何被保留、如何被回收,以及 GC(垃圾回收)阈值背后"空间阈值 + 时间阈值"双维度计算的真实机制,进而在部署与调优时正确设置gc_horizongc_periodpitr_interval等核心参数。

保留策略的用户视角:PITR 周期与快照

RFC 011 提出的核心模型是:用户为一条时间线(timeline)指定保留策略,该策略由两部分组成:

  1. PITR 周期(PITR period):需要保留的近期历史时长,以分钟、小时或天为单位。只要落在这个时间窗口内,用户就可以在任意时间点创建分支(branch)或快照(snapshot)、打开计算节点(compute node)并执行查询。也就是说,PITR 周期决定了"多老的历史仍然可以按时间点回放"。
  2. 快照(snapshot):一个命名的、确定的时间点。快照在内部由 LSN 表示,用户给快照起名后即可长期引用。系统还支持自动快照:用户可以指定一个时间间隔,让系统周期性地自动创建快照,例如"每天凌晨 2 点创建一个快照",并且可以设定一个保留时长,超过该时长的旧自动快照会被移除。

RFC 中的示意图直观地表达了这种"范围 + 点"的组合:

Snapshot Snapshot PITR "Monday" "Tuesday" PITR ----######----------+-------------+-------------######>

左侧和右侧的######是 PITR 周期(连续的时间范围),中间的+是命名的快照点。值得注意的是,RFC 也特别提示:这个模型"可能过于灵活",现实产品希望保持用户界面简单,例如只允许在分支尖端(tip of a branch)设置 PITR 周期,但这对内部实现没有本质区别。

分支级策略

如果存在多条分支,可以针对不同分支指定不同的策略。这一点在 pageserver 的Timeline结构中有着直接体现:每条时间线独立持有自己的gc_infoGcInfo),其中记录了该时间线自己的保留相关的 LSN;而消费计量(consumption metrics)在统计 PITR 历史大小时,也是按 timeline 维度计算的(详见 consumption_metrics/metrics.rs 中的pitr_history_size_since_parent),因此每条分支的历史保留范围天然可以独立配置。

保留策略的内部表示:点与范围

RFC 强调,保留策略的底层构成是点(point,用于快照)范围(range,用于 PITR 周期)

系统必须能够重建保留策略覆盖范围内的任意页面(page)。在此范围之外的历史页面版本,则可以交给垃圾回收(GC)清理掉。对于"何时执行 GC、GC 多激进",系统拥有很大的自由度——这正是 pageserver 中 GC 相关调度逻辑 存在大量参数的原因。

在 pageserver 的实现中,这一思想被编码为GcCutoffs结构体:

  • space字段:根据gc_horizon(字节数阈值)计算出的 LSN,代表"为了保留指定字节数的 WAL 历史,必须保留多老的数据";
  • time字段:根据pitr_interval计算出的 LSN,代表"为了支持至少一个 PITR 周期时长的历史读取,必须保留多老的数据";None表示尚未计算出 PITR cutoff;当pitr_interval为 0 时该值为Some(last_record_lsn)(即完全不保留历史)。

GcCutoffs::select_min()取两者中的最小值作为真正的 GC 边界:只有同时满足"空间阈值"与"时间阈值"才能执行 GC,任何一个方向都不允许把仍然需要的历史提前清理掉。这正是 RFC "保留策略 = 点 + 范围" 在工程上的落地。

底层存储:base image 与 WAL slice

页面版本以两类文件保存:

  • base image:某个关系(relation)在特定 LSN 处全部页面的转储(dump)。
  • WAL slice:一个 LSN 区间内(例如 100–200)的全部 WAL 记录。

RFC 用如下示意图说明数据布局:

| | | | --Base img @100 + | | | | WAL slice | | 100-200 | | | --Base img @200 + | | | | WAL slice | | 200-300 | | | + | V

要恢复某个 LSN(例如 150)处的页面,就需要 LSN 100 处的 base image,以及覆盖 100–200 的 WAL slice。这一"base image + 在其上重放 WAL"的机制,在 pageserver 的 WAL redo 路径中有对应的实现(参见 pageserver/src/walredo.rs 与 pgxn/neon_walredo/walredoproc.c):读取 base image 后,将 WAL 记录逐条重放,直到目标 LSN。

按关系粒度组织与"热点感知"

这些工作以关系(relation)或关系段(relation segment)为粒度进行:

  • 对更新非常频繁的关系,系统会更频繁地为其创建 base image 和 WAL slice;
  • 对更新不频繁的关系,系统则更久地持有该关系较近期的 WAL,只有当需要释放原始 WAL 所占磁盘空间时,才把该关系的 WAL 落成 slice 文件。

RFC 明确指出这里需要一个"兜底机制"(backstop):在全部 WAL / base image 被持久复制到 S3 之前,原始 WAL 必须暂时存放在某处——要么在 WAL service(safekeeper),要么在 S3。也就是说,保留策略的容量承诺不能越过"尚未上传的 WAL"这条红线。在 safekeeper 侧,safekeeper/src/timeline.rs 中的WalResidenceData就是围绕"本地 WAL 保留/截断"这一约束工作的:它维护了 WAL 起始 LSN、截止 LSN 以及"已上传至 S3 的截止 LSN",从而让 safekeeper 可以在数据安全地进入 S3 之后才截断本地 WAL。

热数据与冷数据的处理

对频繁更新的关系,"按需生成 base image"的模式在 pageserver 中表现为image_creation_threshold(默认值为 3,见 libs/pageserver_api/src/config.rs 中的DEFAULT_IMAGE_CREATION_THRESHOLD):当某个键(key)在 L0/L1 层积累了足够多(超过阈值)的 WAL 记录时,压缩(compaction)过程就会强制生成一个 image 层,从而缩短每次页面重建时需要的 WAL 重放长度。这正是 RFC 所述"对更新频繁的关系更快地创建 base image"的实现机制。

同时,compaction.rs 中的compute_force_image_creation_lsn会基于 PITR cutoff 与pitr_interval计算出一个"强制创建 image 的 LSN"(image_layer_force_creation_period控制该周期):对于超过 PITR 周期、很快就要被 GC 回收的历史,系统会周期性地为仍需要保留的层强制生成 image,避免未来重建页面时需要跨越过长的时间区间重放 WAL。

分支点也是保留点

RFC 强调了一个容易忽略的事实:分支点(branch point)在内部也是"保留点"(retention point),与用户可见的快照地位相同

如果一条分支从父分支的 LSN 100 处 fork 出来,那么父分支必须能够在 LSN 100 处重建任意页面,因为子分支还需要引用这些历史版本。反过来,如果某个页面在子分支上被修改,父分支就不再需要保留该页面的旧版本(除非它自身还有别的保留义务)。

在 pageserver 的实现中,这条规则体现为"retain LSN"机制:每条时间线的GcInfo会记录子分支及租约(lease)要求的 LSN(参见 pageserver/src/tenant/timeline.rs 中的retain_lsns列表与lsn_covered_by_lease)。GC 计算出的最终 cutoff 必须"不低于"这些 retain 点,否则子分支将无法读取其分支点处的数据。分支点、快照点和 PITR 窗口共同组成了完整的保留约束集合。

保留策略相关的配置参数与默认值

RFC 将保留策略拆解为"用户可见的 PITR 周期 + 快照 + 自动快照",而 pageserver 在运行时通过一组 Tenant 级配置实现它们。以下是 libs/pageserver_api/src/config.rs 中定义、并反映在 pageserver 的 OpenAPI 规格 与 HTTP 路由 中的核心参数:

参数默认值类型作用
pitr_interval"7 days"Duration(时长字符串)PITR 周期,即按时间维度必须保留的历史长度;为 0 表示禁用 PITR 历史
gc_horizon64 * 1024 * 1024(64 MiB)u64(字节)按空间维度必须保留的 WAL 字节数,防止历史过短导致空间阈值先于时间阈值触发
gc_period"1 hr"DurationGC 调度周期,即每隔多久执行一次 GC 计算与执行
image_creation_threshold3usize某键的 WAL 记录条数超过该阈值时强制创建 image 层
image_creation_preempt_threshold3usize预占式 image 创建阈值(提前量)

这些配置既可以通过 pageserver 的配置文件设置全局默认值(default_tenant_conf),也可以通过 Tenant API 按租户/时间线覆盖(TenantConfigPatch支持对gc_horizongc_periodpitr_intervalimage_creation_threshold等字段做增量修改,见 libs/pageserver_api/src/models.rs)。也就是说,RFC 设想的"不同分支可配置不同策略"在 API 层面由"全局默认 + 租户级/时间线级覆盖"的分层模型支持。

GC 执行与调度:从 cutoff 到回收

结合 pageserver/src/tenant/timeline/compaction.rs 可以看到 GC 的整体执行流程:

  1. 计算 cutoff:根据gc_horizon(空间)与pitr_interval(时间)得到GcCutoffs,再与分支保留点、租约 LSN 以及上一次的 applied cutoff 取交集,得到本次 GC 的水位线(get_gc_compaction_watermark即返回min(space_cutoff, time_cutoff, latest_gc_cutoff, standby_horizon))。
  2. 调度gc_period决定 GC 任务的触发频率;若 PITR cutoff 尚未计算出(None),则select_min()不允许回收任何数据——这保证了系统在尚未建立正确的水位线之前绝不清除历史。
  3. 执行回收:在gc_compaction流程中,低于 cutoff 的层会被识别、重写或丢弃;只有当"层范围完全位于 cutoff 之下"且没有 retain LSN 引用时,才真正删除数据。执行完成后会更新 applied cutoff,并通过 index-only 上传将latest_gc_cutoff持久化到index_part.json,从而保证重启后仍能基于正确的 cutoff 继续访问(见 compaction.rs 中关于index_part.json的注释)。

提示:源码注释特别强调——如果 GC 尚未计算 PITR cutoff,就什么都不能回收("if we haven't computed the PITR cutoff yet, we can't GC anything")。因此在实际部署中,pitr_intervalgc_period的取值需要保证 GC 能及时运行并持续产生有效的 cutoff。

从 RFC 到实现的关键映射总结

RFC 011 中的概念pageserver 中的实现
PITR 周期(时间范围)TenantConfig::pitr_interval,转换为GcCutoffs.time
快照 / 分支点(LSN 点)GcInfo中的 retain LSN 列表、子分支引用、租约
base imageimage 层(L1/L2 image 文件),由image_creation_threshold等参数驱动生成
WAL slicedelta 层 / WAL 文件,覆盖一段 LSN 区间
兜底:上传 S3 前的原始 WAL 保留safekeeper 的WalResidenceData(WAL 起始 LSN 与已上传 LSN 的跟踪)
垃圾回收GC 任务按gc_period调度,依据min(space, time)cutoff 执行gc_compaction

总结

Neon 的保留策略在用户侧表现为"PITR 周期 + 命名快照 + 自动快照",在系统内部则统一收敛为"LSN 范围(PITR)+ LSN 点(快照、分支点)"的集合。RFC 011 提出的这套模型至今仍是 pageserver GC 机制的设计骨架:GcCutoffs同时以空间(gc_horizon)与时间(pitr_interval)两个维度界定保留边界,base image 与 WAL slice 的组合提供了任意 LSN 点的页面重建能力,而分支点作为隐式保留点保证了分支/快照读取的正确性。

理解这一模型的价值在于:当你需要调整 Neon 的历史保留能力(延长 PITR、增加自动快照、或为节省存储缩短历史)时,最终都会反映为对pitr_intervalgc_horizongc_period等参数的配置;而所有这些参数的正确边界,都受制于 RFC 中所强调的"必须能重建保留范围内任意页面"以及"原始 WAL 必须安全进入 S3 之后才能被截断"这两条底层约束。

【免费下载链接】neonNeon: Serverless Postgres. We separated storage and compute to offer autoscaling, code-like database branching, and scale to zero.项目地址: https://gitcode.com/GitHub_Trending/ne/neon

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

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

Java实现论文查重系统:中文分词、N-Gram与相似度算法实战指南

简介:一份基于Java的论文查重系统完整源码包,面向计算机相关专业学生、Java开发者与文本相似度检测初学者。系统核心采用SimHash算法计算原文与待检测论文之间的相似度,输出重复率结果,支持文件输入输出和命令行参数指定路径&…

作者头像 李华
网站建设 2026/9/13 17:51:53

AI助力跨境电商商品描述本地化与转化提升

1. 项目背景与核心价值跨境电商业内人都知道,商品描述本地化是个长期痛点。去年帮深圳某3C卖家做德语站优化时,他们团队用谷歌翻译直接生成的产品说明,把"防水等级IP68"译成了"可以带着游泳",导致退货率激增2…

作者头像 李华
网站建设 2026/9/13 17:45:03

Unity 2023安装避坑指南:从环境搭建到Hello World验证

1. 这不是“点下一步就完事”的安装教程,而是你真正能跑起来第一个Unity项目的起点 Unity 2023不是随便装个软件就能开始写代码的工具,它是一整套需要协同运转的开发环境——编辑器本身只是冰山一角,背后是.NET运行时、图形驱动适配、构建目标…

作者头像 李华