ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Neon Synthetic Size 完全指南:理解 Serverless Postgres 的存储计费与分支定价模型

Neon Synthetic Size 完全指南:理解 Serverless Postgres 的存储计费与分支定价模型 Neon Synthetic Size 完全指南理解 Serverless Postgres 的存储计费与分支定价模型【免费下载链接】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本文是 Neon 存储引擎中Synthetic Size合成大小的权威技术解读。Synthetic Size 是 Neon 用来回答我的数据库到底有多大这一问题而设计的核心度量指标直接决定项目的存储计费、PITR 历史保留与分支成本感知。读完本文你将掌握 Synthetic Size 的完整计算模型、INSERT/DELETE/分支/分支分歧四种典型场景的推算方法以及单分支大小无法归属这一设计背后的权衡并能在源码层面定位其实现尺寸计算实现、定价模型算法、HTTP 查询接口。为什么需要 Synthetic SizeCopy-on-Write 存储带来的度量难题Neon 的存储采用计算与存储分离架构其核心特性是Copy-on-Write 分支copy-on-write branching。分支不会复制数据而是共享底层存储页只在写入时产生增量。正是这种共享机制让我的数据库有多大这个问题变得难以回答一个物理文件可能被多个分支共享无法简单地加总分支创建瞬间不产生任何额外存储但随分支持续演进会逐渐消耗空间物理存储中还存在垃圾回收garbage collection、压缩compression、远端存储remote storage等实现细节这些都会让物理大小与用户可感知的大小脱节。为此Neon 为整个项目计算一个Synthetic Size。称其为合成的是因为它完全基于两类用户可见的逻辑量推导而不是物理存储消耗逻辑大小logical size——在独立 PostgreSQL 安装上你能看到的数据库大小WAL 量——与独立 PostgreSQL 上同一组更新产生的 WAL 完全相同。Synthetic Size 的设计目标官方文档中明确给出充分体现 Copy-on-Write 特性例如创建一个分支不会立刻增加 Synthetic Size只有分支开始与父分支分叉后才会逐渐影响总大小与存储实现细节解耦不依赖垃圾回收、远端存储、压缩等内部实现这样存储系统任何优化都只会降低 Neon 自身的 COGS运营成本而任何多存了数据的缺陷也不会转嫁给用户。需要强调的是物理大小与 Synthetic Size 之间存在强相关性但 Synthetic Size 刻意设计为独立于实现细节——它回答的是用户视角下应该为多少数据买单而非底层实际占了多少磁盘。术语与假设在进入计算细节前需要先明确几个贯穿全文的定义来自文档逻辑大小logical size分支在某个时间点的尺寸即所有数据库中所有表的总大小相当于 psql 中\l显示的总量再加上 Postgres 的 SLRU 与少量元数据。注意目前 Neon 的 logical size 并未包含 SLRU 和元数据详见get_current_logical_size_non_incremental()附近的注释。时间点point in time在 Neon 内部以LSN值定义。时间戳可以转换为 LSN但存储系统内部只与 LSN 打交道。PITR horizon可按分支单独设置既可以是时间区间如 5 天或几小时也可以是 WAL 字节数。若以时间区间给出计算时会先转换为对应 LSN。PITR horizon 0表示不保留任何历史数据。从源码实现看感兴趣的时间点被建模为若干带LsnKind标记的 LSN包括BranchStart时间线起点、BranchPoint子分支点、GcCutOffGC 截止点、BranchEnd最后一条记录、以及用于 LSN 租约的LeasePoint/LeaseStart/LeaseEnd见 pageserver/src/tenant/size.rs 中的LsnKind枚举。这些 LSN 正是gather_inputs()构建尺寸模型输入的骨架pageserver/src/tenant/size.rs。计算模型快照 WAL 的理想化存储Synthetic Size 的输入只有三样文档数据库在不同时间点的逻辑大小生成的 WAL 总量PITR horizon 设置。计算基于一个理想化的存储模型假装存储只有两种东西——快照snapshots某个时间点数据库的完整镜像WAL日志。在最简单的情况下项目只有一个 main 分支、PITR horizon 固定Synthetic Size PITR horizon 起点处即还能恢复到的最老时间点分支的逻辑大小覆盖整个 PITR horizon 的 WAL 大小。其含义是快照让你能恢复到 PITR horizon 的起点WAL 让你能从该点恢复到 horizon 内任意时间点WAL -----------------------######### ^ snapshot Legend: ##### PITR horizon. This is the region that you can still access with Point-in-time query and you can still create branches from. ----- history that has fallen out of the PITR horizon, and can no longer be accessed重要说明这不是存储系统真实的工作方式真实实现同样是快照 WAL模型但快照是针对单个数据库页和页区间range of pages而非整个数据库拍摄的且复杂得多。这个理想化模型只是为了让 Synthetic Size 成为真实存储消耗的一个合理代理proxy。这一理想化模型在源码中体现为tenant_size_modelcrate 的StorageModel它把每个时间线切成若干Segment每个 Segment 记录parent父 Segment、lsnLSN 位置、size该点的逻辑大小可空、needed是否必须被保留并带有SegmentMethod标记如SnapshotHere、Wal、Skipped见 libs/tenant_size_model/src/calculation.rs。gather_inputs()正是负责把这些 Segment 从各时间线的 GC 信息、分支点、PITR cutoff 中组装出来再通过fill_logical_sizes()并行、带缓存地补齐各 Segment 的逻辑大小pageserver/src/tenant/size.rs。关于 PITR cutoff 的源码细节在 gather_inputs() 中可以看到一个关键取舍计算只考虑GcCutoffs::time时间维度上的 PITR cutoff不考虑GcCutoffs::space内部空间 cutoff。源码注释明确指出从用户视角看他们只要求保留到时间边界pitr_cutoff因此即使空间 cutoff 之下还存着数据只要用户删除数据库并等待 PITR 区间过去Synthetic Size 依然会下降。此外tenant_size_handler支持用retention_period查询参数临时覆盖 cutoff仅当比真实 cutoff 更短时生效且不会更新缓存的尺寸与 Prometheus 指标见 pageserver/src/http/routes.rs。示例一INSERT 数据假设数据库在 PITR horizon 起点有10 GB数据此后又插入了5 GB新数据。新增的 5 GB 数据大约消耗 5 GB WAL此时10 GB快照 5 GBWAL 15 GB若将项目 PITR horizon 设为 0不保留任何历史则 PITR horizon 起点移到分支末端快照在插入完成后的时刻计算15 GB快照 0 GBWAL 15 GB两种情况结果相同因为所有历史都由插入构成新插入的数据无论作为逻辑快照的一部分、还是作为 WAL 存储占用的空间大致一样。*文档也给出了这个近似公式的边界实际上 WAL 含头部与额外开销而逻辑快照包含页面上的空余空间因此插入在 WAL 中的大小可能小于或大于插入后最终表的大小——但大多数情况下两者在同一量级。示例二DELETE 数据再看删除场景文档仍然从10 GB数据库开始。删除 5 GB 数据并执行VACUUM释放空间逻辑大小降到5 GB。假设删除与 vacuum 的 WAL 共100 MB此时 Synthetic Size 为10 GB快照 100 MBWAL 10.1 GB这远大于删除后的逻辑大小5 GB——因为系统仍须保留被删除的数据它们在 PITR 窗口内对查询和分支仍然可见。若把 PITR horizon 设为 0或等待时间流逝使数据跌出 PITR horizon被删除的数据不再可访问Synthetic Size 随之缩小5 GB快照 0 GBWAL 5 GB实战启示如果你在 Neon 上删除大量数据后仍看到存储费用居高不下原因就在于 PITR 窗口内历史数据仍被保留。等待数据越过 PITR horizon或主动调低 horizonSynthetic Size 才会下降。这正对应源码中只按时间 cutoff 计算、不按空间 cutoff 计算的设计——用户等待 PITR 区间过去就能看到尺寸回落pageserver/src/tenant/size.rs。分支BranchingCopy-on-Write 在尺寸上的体现分支让计算变得更复杂。Neon 分支是 Copy-on-Write 的Synthetic Size 也如实反映这一点创建分支不立即改变 Synthetic Size分支点位于 PITR horizon 之内恢复该时间点所需的数据本来就必须保留分支上的修改产生 WAL计入 Synthetic Size。示例分支 INSERT再次以 10 GB 数据库开始main 分支插入 2 GB 数据在该点创建分支main 分支再插入 3 GB子分支插入 1 GB。child ##### | | WAL main ---------############### ^ snapshotSynthetic Size 由三部分组成PITR horizon 起点的快照10 GBmain 分支的 WAL2 GB 3 GB 5 GB子分支的 WAL1 GB总计16 GB分歧分支Diverging Branches快照与 WAL 的取舍如果各分支的修改量很小如上例Synthetic Size 分支点前的共享快照 各分支 WAL。但当分支分歧很大时为分支单独保存一个快照更划算文档示例完全分歧的分支从 10 GB 数据库开始。main 分支插入 5 GB 数据创建分支后子分支立即删除全部数据并插入 5 GB 新数据main 分支同样操作。假设 PITR horizon 要求两个分支各保留最后 1 GB WALsnapshot v WAL child ---------############## | | main ----------------------############## ^ WAL snapshotSynthetic Size 组成main 分支 PITR horizon 起点的快照4 GBmain 分支 WAL1 GB子分支 PITR horizon 起点的快照4 GB子分支最后 1 GB WAL1 GB总计10 GB而另一种做法——只在分支点保留一个快照并保留两分支全部 WAL——则需要10 GB 快照 5 GB 5 GB WAL 20 GB明显更贵。到底哪种方案更省取决于两分支的修改量WAL 量与分支点处的逻辑大小。关键机制在每个分支点系统会同时用两种方法计算并选择更便宜的一种。一个直观的理解方式是分支刚创建时是瘦分支只存分支点以来的 WAL随着修改增多、WAL 增长到某个临界点后为分支保存一份全新快照并截断 WAL 反而更划算。这个自动选择更优方案的逻辑正是 calculation.rs 中size_here()递归函数的职责。它对每个 Segment 计算两种条件下的最优方案Method 1Skipped如果该节点不需要可跳过它在子树中后续位置取快照Method 2SnapshotHere在该 Segment 处取快照前提是它的逻辑size已知再加各子树的增量成本Method 3Wal从父 Segment 通过 WAL 到达成本为seg.lsn - parent.lsn即该段 LSN 区间对应的 WAL 字节数再加上子树的增量成本。随后在父节点可用与父节点不可用两种前提下分别挑选最便宜的组合当成本相同时实现还刻意用/的细微差别来优先选择跳过节点、或优先使用 WAL 而非快照calculation.rs。calculate()遍历所有根 Segment 累加total_size且当全租户时间线都已卸载offloaded时报告total_size.max(1)保证数据点能出现在 S3 文件中calculation.rs。单个分支的大小是什么为何无法归属Synthetic Size按整个项目租户计算并包含所有分支。由于无法把尺寸的各部分干净地归属到单个分支Neon 中不存在某分支的大小这一概念文档。示例把尺寸归属到分支想象从 main 分支同一点创建分支 A 和 B两个分支上各做少量更新。此后六个月main 分支数据完全翻新了多次保留期为 1 个月------ A / --------------------*------------------------------- main \ -------- B此时租户的 Synthetic Size 基于分支点处的逻辑快照计算假设快照10 GB分支 A、B 的 WAL 各1 MB则总 Synthetic Size 10002 MB先忽略 main 分支它只是被加进总和。问题来了如何把 10002 MB 拆分到 A 和 B文档给出了三种可行方法每一种都有各自的缺陷减法方法Subtraction method对每个分支计算如果该分支不存在总 Synthetic Size 会减少多少——即删除该分支能节省多少。此方法下 A、B 的大小都是1 MB。问题10 GB 的共享逻辑快照既不算在 A 也不算在 B 头上所有分支大小之和不等于租户总 Synthetic Size。若删除分支 A你省下 1 MB符合直觉但B 的大小会突然从 1 MB 跳到 10001 MB令人惊讶。除法方法Division method把公共部分在所有需要它的分支间平分。此方法下 A、B 的大小都是5001 MB。问题各分支之和恰好等于总 Synthetic Size但同样有反直觉之处——删除 A 时你可能以为能省 5001 MB实际上只省 1 MB且B 的大小从 5001 跳到 10001 MB。加法方法Addition method对每个分支把它依赖的所有快照和 WAL 全部计入即使其中一部分被其他分支共享。此方法下 A、B 的大小都是10001 MB。问题所有分支大小之和大于总 Synthetic Size删除 A 时总尺寸也不会像你以为的那样减少 10001 MB。替代方案与结论文档还讨论了一些替代思路一种取巧做法把整个分支树图形化展示为每段 WAL 或逻辑快照标注其大小让用户直观看到哪些分支依赖哪些段、哪些段被共享——文档认为这在 UI 里本身就该有或者用减法方法计算各分支数字**额外增加一个共享大小shared size**数字涵盖被多个分支共同需要的数据。结论文档原话的要点核心在于把 Synthetic Size 归属到单个分支并不直接。上述方法实现起来都不难但各有问题。哪种方法合理高度取决于你想用这个数字做什么、想回答什么问题。这一结论在代码架构上也有印证ModelInputs中保留了timeline_inputs含各时间线的ancestor_lsn、last_record、latest_gc_cutoff、next_pitr_cutoff等但源码注释明确说明timeline_inputs 不参与尺寸计算仅用于解释输入pageserver/src/tenant/size.rs。也就是说模型只产出租户级总量与各 Segment 的分段结果并不尝试给出每分支大小。如何获取 Synthetic SizeHTTP 接口与测试验证在源码中Synthetic Size 的计算入口是 pageserver 的 HTTP 管理接口tenant_size_handlerpageserver/src/http/routes.rs路径参数tenant_shard_id必填且仅 shard 0 支持尺寸计算其他 shard 返回 400见 routes.rs查询参数inputs_onlytrue|false默认false为 true 时只返回模型输入用于调试不执行计算查询参数retention_period覆盖实际 cutoff仅当更短时生效且不更新缓存与 Prometheus 指标请求头Accept: text/html时返回 HTML 形式含 SVG 分支图由tenant_size_model::svg支持否则返回 JSONJSON 响应包含id、size总字节数WAL 与逻辑大小的混合体故单位为字节与segment_sizes、inputs。该接口的文档注释明确指出它不用于 consumption metrics 计费路径而是用来调试任何计算问题routes.rs。单元测试方面pageserver/src/tenant/size.rs 内置了两组稳定的 JSON 输入验证verify_size_for_one_branch单分支场景输入含BranchStart、GcCutOff、BranchEnd三段断言总尺寸等于220121784320字节verify_size_for_multiple_branches多分支场景源自集成测试test_tenant_size_with_multiple_branches但使用固定 LSN包含三条时间线、跨时间线的BranchPoint链接断言总尺寸等于37_851_408字节。这两组测试直接验证了快照 WAL模型在单分支与多分支分歧场景下的行为与文档中的理想化模型一一对应。总结Synthetic Size 是 Neon 存储体系中最核心的用户可见度量其要点可归纳为维度说明计算范围整个项目租户包含所有分支输入各时间点的逻辑大小、WAL 量、PITR horizon 设置模型理想化的快照 WAL两层模型设计目标反映 Copy-on-Write 特性与 GC/压缩/远端存储等实现细节解耦INSERT 特性插入数据无论存为快照还是 WAL总尺寸大致不变DELETE 特性删除的数据在 PITR 窗口内仍计入尺寸越过 horizon 后回落分支特性创建分支零成本分歧大时自动切换为分支独立快照方案分支归属无法干净归属到单分支减法/除法/加法各有缺陷理解 Synthetic Size 不仅有助于把控 Neon 项目的存储成本尤其是大 DELETE 后的费用不降现象、以及分支数量与分歧度对计费的影响也能让你透过这个度量看清 Neon 存储快照 WAL的本质。想要深入底层建议从三份核心源码入手尺寸输入收集、定价模型算法 与 租户尺寸查询接口。【免费下载链接】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),仅供参考
返回列表