FEATURED · 精选文章

Neon 计量体系剖析:从 RFC 021 到源码实现,构建 Serverless Postgres 的按量计费与成本核对基础设施

发布时间 / 2026/9/13 14:48:22
来源 / 创域科博编辑部
栏目 / 资讯中心
Neon 计量体系剖析:从 RFC 021 到源码实现,构建 Serverless Postgres 的按量计费与成本核对基础设施 Neon 计量体系剖析从 RFC 021 到源码实现构建 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 仓库内的设计文档 docs/rfcs/021-metering.md 为主线深入讲解 Serverless Postgres 场景下消费计量Consumption tracking系统的设计动机、六类核心指标、Push 型采集架构、事件上报协议并结合 docs/consumption_metrics.md 与 libs/consumption_metrics 等源码实现说明 pageserver 与 proxy 实际是如何采集、缓存、批量上报消费事件的。读完本文你将掌握 Neon 计量系统从事件定义、采集分工到上报链路与去重机制的完整技术脉络并能在自己的部署中正确配置metric_collection_*相关参数。一、设计目标计量数据服务于计费与对账两件事RFC 021 开篇即点明建立消费计量体系有两个高度重合但又不完全相同的目标支撑按量计费consumption-based billing收集计费模型所需的原始数据例如计算资源占用时长、网络流量、存储占用等核对 AWS 账单cross-check AWS bills通过追踪写入数据量与真实存储占用等指标建立内部模型来预测底层云资源成本从而与 AWS 实际出账相互校验。两个目标的差异在于计费模型可以只暴露少数几个指标RFC 中建议仅需CPU time、synthetic storage size与traffic而成本核对需要更细粒度的物理资源数据如写入数据量、真实磁盘占用以便还原底层 S3 / 计算 / 带宽成本。因此指标集合必须兼顾两者——既不过度复杂又能还原真实资源消耗。二、六类核心指标度量对象与粒度划分RFC 定义了六类需要采集的指标每一类都有明确的物理含义与归属粒度per-endpoint / per-branch / per-tenant指标含义采集粒度归属服务CPU timeeffective_compute_seconds墙钟秒数 × 当前核数由于内存与核数按固定比例配置内存大小可视为核数的函数每个 endpointConsole / autoscaler-agentTrafficProxy 上的进出流量每个 endpointProxyWritten sizewritten_size写入的数据量与流量、存储大小均不同写入时既占用 safekeeper 磁盘带宽又必然跨 AZ 向所有 safekeeper 传递 WAL每个 timeline/branch每条 timeline 至多一个写入者PageserverSynthetic storage size现有 pageserver/v1/tenant/{}/size暴露的值用于 UI 展示分支物理大小per-tenantRFC 附带提出疑问能否改为 per-branch 以便在 UI 展示分支物理大小PageserverReal storage sizetenant 目录在 pageserver 磁盘上的实际大小per-tenantPageserverS3 storage sizetenant 数据在 S3 上的大小per-tenantPageserverRFC 特别解释了 Written size 区别于另外两类大小指标的原因它反映的是写入动作带来的瞬时资源占用——写入的瞬间既产生 safekeeper 的磁盘带宽消耗也强制产生跨 AZ 的 WAL 传输。因此它是成本核对模型中不可或缺的输入。三、采集分工谁掌握什么数据谁就负责上报计量系统的一个核心设计原则是由最了解某项数据的组件负责采集与上报避免跨组件推算。3.1 Proxy唯一知晓流量流动的组件Proxy 是唯一能看到真实网络流量进出的组件因此流量指标由它负责。由于 Proxy 是无状态的任何重启都会重置累计值所以上报间隔必须足够短RFC 建议约 1 分钟上报的是自上次上报以来的增量delta而非计数器绝对值——增量事件更便于按时间段做积分求和。典型增量事件示例来自 RFC 021{ metric: proxy_io_bytes_per_client, type: incremental, start_time: 2022-12-28T11:07:19.317310284Z, stop_time: 2022-12-28T11:07:19.317310284Z, idempotency_key: 2022-12-28 11:07:19.317310324 UTC-1-4019, value: 12345454, endpoint_id: 5d07d9ce9237c4cd845ea7918c0afa7d }增量事件天然携带时间窗口start_time为上次上报时间这让下游能识别计量缺口例如某次上报发送失败。RFC 还指出两个设计便利无活跃连接时 Proxy 可以不报任何数据由于增量可加多个 Console 实例服务同一用户/endpoint 时无需协调即可并行上报流量。3.2 Console / autoscaler-agent掌握启停事件负责 CPU 时间Console 掌握 endpoint 的 start/stop 事件因此能计算分配给每个 endpoint 的 CPU 时间同时它知道操作成功与否可以避免在挂起suspend失败后仍向客户计费。但 Console 并不掌握限值内 endpoint 的当前算力大小因此 RFC 分两个阶段处理未启用自动扩缩容时Console 直接报告自上次start_compute事件以来的秒数启用自动扩缩容后由autoscaler-agent按与 Proxy 上报流量相同的节奏上报cpu time × compute_units_count。{ metric: effective_compute_seconds, type: increment, endpoint_id: blazing-warrior-34, event_start_time: ..., event_stop_time: ..., value: 12345454 }RFC 特意建议合并上报单一值cpu time × compute_units_count而不是拆成两个字段——这样事件 schema 更简单、与流量的处理方式一致且保持可加性additivity。3.3 Pageserver有状态可计算其余全部指标Pageserver 掌握或可计算剩余所有指标Written size基本就是last_received_lsn后续实现中为last_record_lsnSynthetic storage size可计算但代价较高Real storage size可通过 layer map 或文件系统计算S3 storage size通过 S3 API 调用计算。由于部分指标计算昂贵上报周期主要由实现细节决定RFC 建议例如每小时一次。好在 pageserver 是有状态的所有指标可以按绝对值上报而无需增量。RFC 同时指出written size本质是 safekeeper 相关指标但它在 pageserver 和 safekeeper 上都可得因此可以完全避免从 safekeeper 上报任何数据。{ metric: remote_storage_size, type: absolute, time: 2022-12-28T11:07:19.317310284Z, idempotency_key: 2022-12-28 11:07:19.317310324 UTC-1-4019, value: 12345454, tenant_id: 5d07d9ce9237c4cd845ea7918c0afa7d, timeline_id: a03ebb4f5922a1c56ff7485cc8854143 }四、数据采集架构选型Push 优于 PullRFC 用专门一节论证了为什么计量数据不适合沿用已有的 Pull 型 Prometheus 采集Pull 模型难以判断某个指标何时发生变化——例如垃圾回收会在整整一周内持续释放磁盘空间即使项目这一周完全离线若要遍历所有 tenant/branch/endpoint需要相当多代码且很可能在收集器中为每个指标打各种排除不变化 tenant的补丁。而Push 模型天然只发布正在变化的指标pageserver 知道自己何时执行 S3 offload、垃圾回收、开始/停止从 safekeeper 消费数据proxy 知道哪些客户端连着console/autoscaler-agent 知道活跃的 CPU 时间。因此结论明确采用 Push 型上报模型。五、上报路径选型经 Console 转发而非直连消息总线Push 模型具体怎么落地RFC 比较了两种方案方案优点缺点a. 各组件直接 Push 到公共总线segment、Kafka 等扩展性好本地测试困难引入新依赖需向所有组件分发连接密钥仍需把部分事件回传 Console 以在 UI 实时展示b. 各组件 HTTPPOST事件到 Console由 Console 转发给 segment 及 metronome / orb / onebill 等计费系统只有 Console 需要与 segment 通信数据流经 Console 时可直接保存最新指标值无需从 segment 回灌单点汇聚最终选型为方案 b各组件proxy / pageserver / autoscaler-agent将消费事件发送到 Console 的单一端点由 Console 统一负责转发与持久化。六、上报协议与 Console 处理流程6.1 统一的批量上报端点所有组件向 Console 的同一端点批量 POST 事件POST /usage_events HTTP/1.1 Content-Type: application/json [ { metric: remote_storage_size, type: absolute, time: 2022-12-28T11:07:19.317310284Z, idempotency_key: 2022-12-28 11:07:19.317310324 UTC-1-4019, value: 12345454, tenant_id: 5d07d9ce9237c4cd845ea7918c0afa7d, timeline_id: a03ebb4f5922a1c56ff7485cc8854143 } ]6.2 两类事件语义RFC 将事件严格划分为两种类型它们与组件的有状态性对应incremental增量自上一事件或服务重启以来的消费变化适用于effective_cpu_seconds、traffic_in_bytes、traffic_out_bytesabsolute绝对值指标当前值所有大小size类指标均为绝对值。每个服务可以按自己的节奏上报并自由地把不同 tenant/endpoint 的数据捆绑在同一个批次中。6.3 Console 收到事件后的三步算法创建并向 segment 发送同内容事件可对 endpoint 级事件补充 tenant/timeline 信息更新数据库中各 tenant / endpoint 指标的最新状态检查任一指标是否超过允许阈值必要时停止该项目。由于数据是批量到达的第 2 步可以做批量更新以减少数据库查询次数。RFC 估算proxy 流量是最频繁的指标批量化后每分钟约产生number_of_proxies次数据库请求短期可接受但会产生较多死元组dead tuples。若成为问题可把第 2 步改为 Redis 辅助流程2.1. 检查 Redis 中是否存在$tenant_$metric/$endpoint_$metric键2.2. 若无存储值且指标是增量型则从 DWH数据仓库保存所有事件的聚合值取当前值并发布2.3. 发布新值绝对指标或将增量累加到存储值上增量指标。6.4 消费看门狗Consumption watchdog与可扩展性由于所有数据都流经 Console无需任何后台线程/协程去轮询检查消费是否超限——消费只会通过POST /usage_events变化因此限值检查可以直接内联在同一 handler 中完成实现零额外开销。可扩展性方面未来若新增指标如 s3 流量Console 代码默认应该能处理并发布 segment 事件即使它不认识该指标名——这一默认放行原则保证新指标接入时无需同步修改 Console 代码。6.5 命名与 schema 规范每个指标名必须以单位结尾当前是_seconds与_bytessegment 事件在适用时必须携带tenant_id与timeline_id/endpoint_id。七、从 RFC 到实现仓库中的计量采集现状RFC 021 是设计蓝图而仓库中的实现已经在 docs/consumption_metrics.md 中有完整的现状说明并沉淀出独立的共享库 libs/consumption_metrics/src/lib.rs7.1 共享事件模型与去重键共享库定义了事件结构的核心类型EventType枚举精确对应 RFC 的两类语义Absolute { time }与Incremental { start_time, stop_time }libs/consumption_metrics/src/lib.rs通用EventExtra, Metric结构包含kindtype 标签、metric、idempotency_key、value以及通过#[serde(flatten)]展开的extra字段IdempotencyKey由当前时间-node_id-随机数组成如2022-12-28 11:07:19.317310324 UTC-1-4019下游消费者用它检测上传重试导致的重复数据常量CHUNK_SIZE 1000事件按每批最多 1000 条切块避免超出单次请求大小上限批量格式为{ events: [metric1, metric2, ...] }。7.2 Pageserver 的计量采集实现pageserver 侧实现在 pageserver/src/consumption_metrics.rs关键机制包括独立后台线程run()在metric_collection_endpoint配置为空时直接返回默认关闭采集否则在后台运行时BACKGROUND_RUNTIME启动collect_metrics与calculate_synthetic_size_worker两个任务磁盘缓存与重启续传上次成功上报的指标缓存在 workdir 下的last_consumption_metrics.json磁盘缓存逻辑见 pageserver/src/consumption_metrics/disk_cache.rs。启动时通过restore_and_reschedule恢复缓存并把第一次上报调度到与上次上报节奏对齐保证跨重启仍按固定间隔上报双通道上传每次迭代同时tokio::join!执行 HTTP 上报与可选的远端存储S3备份上传共享预先批量生成的幂等键增量与绝对值并报pageserver/src/consumption_metrics/metrics.rs 中的Name枚举定义了实际上报的指标名written_sizeabsolute、written_data_bytes_deltaincremental基于上次上报的 written_size 与 LSN 差、written_size_since_parent、pitr_history_size_since_parent、timeline_logical_size、remote_storage_size、synthetic_storage_size。其中written_data_bytes_delta是 RFC 中 written size 指标的落地形态通过last_record_lsn与缓存值的checked_sub计算回退时补报 0分片约束只从 shard 0 采集与上报消费指标is_shard_zero()判断避免其他分片重复计算合成大小synthetic size 独立计算由单独的 worker 按synthetic_size_calculation_interval周期调用calculate_synthetic_size上报时使用缓存值可能略滞后于实时值值为 0 时沿用上次会话的缓存值且只为非零值生成事件以避免下游日志噪音。7.3 Pageserver 配置参数采集端点与间隔在 pageserver 配置中指定libs/pageserver_api/src/config.rs 中定义pageserver 侧解析见 pageserver/src/config.rs配置项默认值说明metric_collection_endpointNone采集上报端点默认关闭采集metric_collection_interval10 分钟采集与上报周期metric_collection_bucketNone可选同时把事件备份写入远端存储S3synthetic_size_calculation_interval1 分钟合成存储大小后台计算周期测试代码给出了最小可用配置样例见 test_runner/regress/test_pageserver_metric_collection.pymetric_collection_interval1s metric_collection_endpointhttp://127.0.0.1:PORT/billing/api/v1/usage_events metric_collection_bucket{ ... } # 可选远端存储配置 synthetic_size_calculation_interval3s7.4 Proxy 的计量采集实现proxy 侧实现在 proxy/src/usage_metrics.rs与 RFC 的设计一一对应每个 endpoint 一个MetricCounter含transmitted/received原子计数器与opened_connections以Ids{endpoint_id, branch_id, private_link_id}为键放入并发 map周期性迭代中通过move_metrics()原子地取出并清零自上次以来的收发字节数生成proxy_io_bytes_per_client增量事件——事件窗口为(prev, now)正是 RFC 所设计的上次上报时间 → 本次上报时间无活跃连接且无数据时不发送should_report()用Arc::strong_count 1判断连接是否仍打开零流量且连接关闭则跳过避免空转上报分批上报事件按chunk_size切成EventChunk再按共享库的CHUNK_SIZE1000二次切分后逐个 POST 到端点请求超时 10s重试间隔 60s可选备份若配置了backup_metric_collection_config的远端存储事件还会被 gzip 压缩成 NDJSON 文件路径形如year.../month.../.../{uuid}.ndjson.gz上传并带重试退避backoff::retry。proxy 的命令行配置项为metric_collection_endpoint与metric_collection_interval无默认值配置即启用结构定义见 proxy/src/config.rs 中的MetricCollectionConfig。7.5 测试验证计量链路有端到端测试守护test_pageserver_metric_collection.py启动 mock HTTP 服务作为/billing/api/v1/usage_events端点强制metric_collection_interval1s通过MetricsVerifier校验各批次事件的连续性如written_data_bytes_delta的时间窗必须无缝衔接并验证 pageserver重启后仍能从磁盘缓存续传、远端存储LocalFs 模拟中能解压出合法的 NDJSON 事件。proxy 侧另有 test_runner/regress/test_proxy_metric_collection.py 对应验证。八、已知限制与后续 TODO按 docs/consumption_metrics.md 与源码标注当前实现仍存在一些明确留待改进的点错误处理粗糙pageserver 侧若单个 tenant 指标采集失败会导致整个迭代失败、所有 tenant 的指标均不发送无重试HTTP 上报目前不实现重试proxy 对备份上传有backoff::retry但主通道 HTTP 仅记录错误并继续代码中标注// TODO: retry?间隔需调优采集间隔仍属经验值需要结合实际负载打磨跨重启去重依赖磁盘缓存pageserver 的增量计算依赖last_consumption_metrics.json缓存文件且源码注释中留有校验 generation 号的 TODO// TODO: add generation field and check against generations未来版本可能引入代际校验以增强多副本场景的正确性。九、总结从 docs/rfcs/021-metering.md 这份设计文档可以看出Neon 的计量体系自始就是以事件为中心的围绕计费与 AWS 对账两个目标定义六类指标按照谁知道数据谁上报的原则分配给 proxy / console / pageserver 三个角色通过POST /usage_events汇聚到 Console再以增量 绝对值两类事件、幂等键去重、批量分块上传的方式落地。仓库中 libs/consumption_metrics、pageserver/src/consumption_metrics.rs 与 proxy/src/usage_metrics.rs 的实现忠实还原了这份蓝图并在此基础上补充了磁盘缓存续传、S3 备份通道与分片约束等工程细节。对于任何需要自建 Serverless 数据库计量/计费体系的团队这套按指标归属分工 Push 汇聚 双语义事件 幂等去重的架构都具有直接的借鉴价值。【免费下载链接】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),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻