
Neon 自动扩缩容核心组件 vm-monitor 深度解析cgroup 内存监控、LFC 文件缓存与 WebSocket 控制面【免费下载链接】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/neonvm-monitor下文简称 monitor是 Neon 自动扩缩容autoscaling系统的核心组件之一。它与autoscale-scheduler、autoscaler-agent协同工作承担两项关键职责在内存接近上限时向 agent 发出即时扩容请求以及通过管理 Postgres 的本地文件缓存LFC与 cgroup 来落地扩容/缩容决策。本文以 libs/vm_monitor/README.md 为骨架结合仓库源码深入讲解其架构、通信协议、内存监控与文件缓存管理机制帮助读者理解 Neon 是如何在虚拟机粒度上实现按需伸缩的。1. 定位monitor 在自动扩缩容体系中的角色Neon 的自动扩缩容系统由三部分构成autoscale-scheduler负责全局调度决策决定某个 VM 何时扩容、缩容autoscaler-agent运行在每个 VM 上的代理负责与 Kubernetes/NeonVM 交互执行资源调整vm-monitor运行在 VM 内部由compute_ctl拉起承担内务总管角色——它感知 Postgres 的真实内存压力代表 Postgres 向 agent 提出扩容诉求并在扩容获批后把新资源落实到文件缓存与 cgroup 上。monitor 的两大职责README 原文可以概括为通知 agent 进行即时扩容当 Postgres 所在 cgroup 的内存使用量超过阈值时monitor 通过 WebSocket 向 agent 发送扩容请求避免 Postgres 被 OOM-kill执行伸缩决策管理 Postgres 的本地文件缓存file cache以及承载 Postgres 的 cgroup把扩容/缩容目标具体落地。CPU 与内存的扩缩容通过NeonVMNeon 自研的、面向 Kubernetes 的 QEMU 工具完成而控制何时收到内存告警的手段则是把 Postgres 启动在neon-postgres这个 cgroup 中并设置其memory.{max,high}阈值。monitor 的早期原型在独立的 vm-monitor 仓库中开发该仓库现已不再维护但其提交历史对排查问题仍有参考价值——当前实现已迁移至本仓库的libs/vm_monitor目录。2. 架构总览四个松散耦合的子系统monitor 由几个松散耦合的子系统组成README 的 Structure 一节源码层面分别对应libs/vm_monitor/src/下的模块子系统源码模块职责ServerHTTP/WS 服务端lib.rs基于axum的 HTTP 服务器接受连接并将其升级为 WebSocket同一时刻只允许一个连接Filecache文件缓存管理器filecache.rs与 Postgres 文件缓存LFC通信的结构体启动时建立连接并保持整个 monitor 生命周期Cgroup watchercgroup 监控器cgroup.rs轮询neon-postgrescgroup 的内存使用量并把滚动聚合结果发送给 runnerRunner核心协调器runner.rs将 filecache 与 cgroup watcher 结合通过Dispatcher与 agent 通信按需调用两者的函数完成扩容/缩容其中 Dispatcherdispatcher.rs和协议类型protocol.rs负责与 agent 之间的数据交换与连接管理。模块之间的关系Runner是主入口runner.rs的模块注释明确写道This is the Monitor part of the monitor binary and is the main entrypoint for all functionality它持有Dispatcher、OptionFileCacheState与OptionCgroupState——后两者是可选的原因在于是否传入pgconnstr管理 LFC与 cgroup 名称由启动参数决定。3. 启动方式与命令行参数monitor 提供了两种启动途径Cargo.toml 的[[bin]]声明独立二进制vm-monitor入口为 monitor.rs用于在完整的自动扩缩容系统中单独测试 monitormonitor 之前由 vm-builder 启动现在可以用这个二进制模拟当时的场景仅支持 Linux非 Linux 平台直接panic!(the monitor requires cgroups, which are only available on linux)。由compute_ctl内嵌启动作为计算节点进程的一部分运行。3.1 命令行参数monitor 的命令行参数定义在 lib.rs 的 Args 结构使用clap解析参数说明典型值--cgroup/-c需要监控memory.high事件的 cgroup 名称即 Postgres 运行的 cgroupneon-postgres--pgconnstr/-p用于管理 Postgres 文件缓存的连接串compute 节点的内部连接串--addr/-a监听连接请求的地址面向 agent0.0.0.0:10301面向 informant127.0.0.1:103693.2 compute_ctl 如何拉起 monitor在 Neon 生产路径中monitor 由compute_ctl拉起。compute.rs 的 start_vm_monitor 展示了完整的启动逻辑当环境变量AUTOSCALING存在时才启动 monitor否则不启动若计算规格中disable_lfc_resizing为真则不向 monitor 传pgconnstr即不启用 LFC 自动调整传入参数为cgroup来自params.cgroup、pgconnstr来自params.filecache_connstr、addr来自params.vm_monitor_addr同时创建CancellationToken用于在 Postgres 退出后统一取消 monitor 的所有线程见 compute.rs 的清理逻辑。可见 monitor 的生命周期与 Postgres 绑定Postgres 退出时 monitor 被取消释放文件 watcher。4. 通信层单连接 WebSocket 与协议版本协商4.1 单连接模型Server 是一个简单的axum服务器路由为GET /monitorlib.rs请求会被升级为 WebSocket 连接。同一时刻只允许一个连接当新连接到达时ws_handler会先向broadcast::Sender发送信号关闭旧连接源码注释形象地称之为 the cycle of death and rebirth再启动新的 monitor 实例lib.rs。start_monitor使用 4 秒超时等待Runner::new完成初始化初始化失败或超时都会记录日志并返回允许后续新连接重试lib.rs。所有线程通过spawn_with_cancel包裹收到CancellationToken取消信号例如 Postgres 退出或新连接到来后优雅退出lib.rs。4.2 协议版本协商Dispatcher::newdispatcher.rs在建连时执行协议握手等待 agent 发送其支持的协议版本区间JSON形如{min, max}monitor 计算双方最高共同版本highest_shared_version取两个区间交集的 max 值若区间无交集向 agent 返回ProtocolResponse::Error并终止。当前协议的PROTOCOL_MIN_VERSION与PROTOCOL_MAX_VERSION均为V1_0protocol.rs即当前只有 v1.0 一个版本。4.3 消息类型协议消息通过serde序列化为 JSON 文本tag 字段为type。出站消息monitor → agentprotocol.rs OutboundMsgKind消息含义UpscaleRequest {}monitor 发现内存压力过大紧急请求扩容UpscaleConfirmation {}处理完 agent 下发的扩容通知后回执DownscaleResult { ok, status }缩容尝试的结果okfalse表示因内存仍在高位等原因暂不可缩容status说明原因InvalidMessage { error }/InternalError { error }协议或内部处理错误HealthCheck {}双向心跳由 agent 发起入站消息agent → monitorprotocol.rs InboundMsgKind消息含义UpscaleNotification { granted }agent 告知已获批新资源Resources{cpu, mem}monitor 需落地扩容并回复UpscaleConfirmationDownscaleRequest { target }agent 请求降低资源占用monitor 处理后回复DownscaleResultInvalidMessage/InternalError对端错误通告HealthCheck {}心跳Resourcesprotocol.rs包含 vCPU 数cpu: f64与内存字节数mem: u64序列化时使用Allocation这一字段名以匹配 agent 侧的api.Allocation类型。值得注意的细节是Runner.counter消息 ID 计数器始终保持奇数用于避免与 agent 生成的偶数 ID 冲突runner.rs。5. 内存监控CgroupWatcher 的工作原理5.1 cgroup v2 与采样配置CgroupWatcher使用cgroups-rscrate 管理 cgroup仅 Linux见 Cargo.toml 的target.cfg(target_os linux).dependencies。构造时强制要求系统处于cgroups v2unified模式否则直接报错cgroup.rs。采样配置cgroup.rs Config及默认值配置项默认值含义memory_poll_interval100ms拉取内存统计的间隔memory_history_len5用于构建滚动聚合的样本数即使用约 500ms 的历史做决策memory_history_log_interval20周期性日志的最近样本数约每 2s 输出一次避免刷屏memory_history_log_noskip_interval15s数据无明显变化时最多跳过日志的时长5.2 监控主循环watchcgroup.rs是 CgroupWatcher 的入口以watch::Sender(Instant, MemoryHistory)向 runner 推送聚合结果具体流程以 100ms 为周期读取 cgroup 内存子系统统计内存样本取自memory_stat的active_anon inactive_anon即不可回收内存non-reclaimable的近似cgroup.rs用环形缓冲区ring_buf_recent_values_iter维护最近若干样本计算滚动平均值avg_non_reclaimable连同样本数与时间跨度构成MemoryHistorycgroup.rs周期性输出日志若数据与上次记录足够接近status_is_close_or_similar差值小于较小值的 1/8 且不超过 128MiBcgroup.rs则跳过日志以降低噪音。环形缓冲区与相似度判定逻辑分别有单元测试覆盖ring_buf_iter、check_similarity_behaviour见 cgroup.rs可作为理解边界行为的参考。6. 文件缓存管理LFC 的大小计算与动态调整6.1 Postgres 侧的 LFC 与 GUCNeon 的 Postgres 本地文件缓存Local File Cache, LFC由pgxn/neon/file_cache.c实现涉及两个 GUCfile_cache.cneon.max_file_cache_size缓存上限PGC_POSTMASTER级别、MB 单位、默认 0禁用neon.file_cache_size_limit当前软限制PGC_SIGHUP级别、MB 单位可在运行期通过pg_reload_conf()热更新lfc_check_limit_hook保证它不能大于neon.max_file_cache_sizefile_cache.c。6.2 monitor 侧的配置FileCacheConfigfilecache.rs默认值如下配置项默认值含义resource_multiplier0.75缓存目标大小 总资源 × 75%min_remaining_after_cache256 MiB扣掉缓存后系统必须保留的最小内存低于共享内存场景是因为超额分配是安全的——内存不足时内核会从 page cache 驱逐而不是杀掉进程spread_factor0.1控制缓存从 0 增长到目标大小的速率约等于每给缓存增加 1 字节系统预留 N 字节validatefilecache.rs对配置做一致性校验resource_multiplier必须在 (0,1) 之间且resource_multiplier × (spread_factor 1) 1否则两条增长曲线无法在合理区间相交配置不合法。6.3 缓存大小计算公式calculate_cache_sizefilecache.rs根据总内存计算期望缓存大小取两条曲线的较小值并向下取整到 MiBsize min( available / (1 spread_factor), total × resource_multiplier ) 其中 available max(total − min_remaining_after_cache, 0)直观理解当内存较少时spread_factor曲线占主导为系统其余部分保留增长空间内存充足时resource_multiplier曲线封顶不超过总内存的 75%。6.4 连接、查询与设置连接FileCacheState::new用tokio_postgres建立连接连接对象以spawn_with_cancel独立运行filecache.rs读取当前大小get_file_cache_size查询SELECT pg_size_bytes(current_setting(neon.file_cache_size_limit))得到字节数filecache.rs设置大小set_file_cache_size先读取neon.max_file_cache_size封顶再以整数 MB 形式执行ALTER SYSTEM SET neon.file_cache_size_limit N;随后调用SELECT pg_reload_conf();使其生效filecache.rs失败重试query_with_retry在查询失败时重建数据库连接并重试一次filecache.rs。monitor 在启动时会显式设置一次初始缓存大小即使当前值相同目的是确认自己具备修改权限runner.rs。7. 伸缩决策Runner 主循环7.1 阈值计算monitor 通过提前预警避免 Postgres 被 OOM-kill。cgroup 阈值由Config::cgroup_threshold计算runner.rsthreshold total_mem × (1 − cgroup_min_overhead_fraction)cgroup_min_overhead_fraction默认 0.15即保证阈值之上至少保留总内存的 15%从而保证当 cgroup 使用量超过总内存的 85% 时必定触发扩容请求。Runner 的整体配置runner.rs Config默认值配置项默认值含义sys_buffer_bytes100 MiB内核占用的估计内存/proc/meminfo的 MemTotal 与实际物理内存之差计算可用内存时先从总内存中扣除cgroup_min_overhead_fraction0.15阈值之上必须保留的最小内存比例cgroup_downscale_threshold_buffer_bytes100 MiB缩容时新阈值必须高于当前使用量 该缓冲否则拒绝缩容7.2 扩容路径扩容触发条件与动作runner.rs runrunner 监听 cgroup watcher 的watch::Receiver每次收到新的MemoryHistory时检查avg_non_reclaimable是否 ≥ 当前阈值若超阈值且距上次扩容请求已超过 1 秒避免刷屏对应 issue #5865则向 agent 发送UpscaleRequest当 agent 回传UpscaleNotification{granted}后handle_upscalerunner.rs执行落地动作按usable_system_memory new_mem − sys_buffer_bytes计算新的期望缓存大小并set_file_cache_size用同样的内存口径重算 cgroup 阈值并更新CgroupState.threshold完成后回复UpscaleConfirmation。注意handle_upscale中使用了 agent 告知的资源量而非系统实际自报内存因此sys_buffer_bytes的校准尤为重要——这也是该字段 TODO 注释强调的当前仍需信任 agent 的扩容资源量的原因runner.rs。7.3 缩容路径try_downscalerunner.rs是缩容的核心逻辑较谨慎若 monitor 未管理任何 cgroup/LFC直接返回成功从watch::Receiver读取最近一次内存历史做两道安全闸门若距上次统计已超过 5 秒last_time.elapsed() 5sbail 报错——既覆盖启动后一直没收到指标的情形也覆盖指标断流的情形若累计样本数 ≤ 1拒绝缩容返回okfalse等待积累足够数据计算新阈值cgroup_threshold(usable_system_memory)若新阈值 当前使用量 100 MiB 缓冲拒绝缩容并在status中给出数值化原因通过校验后先缩 LFCset_file_cache_size再更新 cgroup 阈值返回DownscaleResult{ok: true, status}。可见缩容被设计成宁可多等、不可误伤内存仍在高位或数据不足时一律暂缓。7.4 消息分派process_messagerunner.rs按InboundMsgKind分发UpscaleNotification→handle_upscale 回执DownscaleRequest→try_downscaleDownscaleResultHealthCheck→ 原样回HealthCheck收到 agent 的InvalidMessage/InternalError通告时仅记录warn日志。8. 关键参数速查与调优提示结合上述源码将 monitor 相关的全部可调参数汇总如下启动参数vm-monitor二进制 / compute_ctl--cgroup要监控的 cgroup 名如neon-postgresPostgres 必须运行于其中--pgconnstrPostgres 连接串用于管理 LFC由disable_lfc_resizing控制是否传入--addr监听地址agent 侧0.0.0.0:10301informant 侧127.0.0.1:10369。代码级配置当前为硬编码默认值Runnersys_buffer_bytes100MiB、cgroup_min_overhead_fraction0.15、cgroup_downscale_threshold_buffer_bytes100MiBCgroupWatchermemory_poll_interval100ms、memory_history_len5、日志间隔 20 次/约 2s、跳日志上限 15sFileCacheresource_multiplier0.75、min_remaining_after_cache256MiB、spread_factor0.1阈值规则扩容阈值 总内存 × 0.851 秒内不重复请求扩容缩容需满足新阈值 ≥ 当前使用量 100MiB且指标新鲜≤5s、样本充足1。Postgres 侧 GUCneon.max_file_cache_sizeMBPGC_POSTMASTER默认 0 禁用neon.file_cache_size_limitMBPGC_SIGHUP运行时经ALTER SYSTEMpg_reload_conf()调整且不得大于 max。调优时需特别注意FileCacheConfig::validate的约束resource_multiplier与spread_factor必须满足resource_multiplier × (spread_factor 1) 1否则配置非法例如resource_multiplier0.75搭配spread_factor1会因两条增长曲线永不达到 75% 而失败。9. 测试与验证monitor 的正确性依赖两处测试支撑单元测试cgroup.rs内置了环形缓冲区迭代含环绕边界values(0,4)[7,8,9,0]等与内存相似度判定1/8 与 128MiB 双重边界的测试cgroup.rs端到端集成独立二进制vm-monitormonitor.rs的注释说明它用于将 monitor 作为整个自动扩缩容系统的一部分进行测试模拟此前由 vm-builder 启动的部署形态可配合 agent 验证完整的握手—扩容—缩容闭环。运行测试只需在仓库根目录执行标准的 Cargo 测试命令monitor 依赖 Linux 的 cgroups测试/运行均需在 Linux 上进行cargo test -p vm_monitor10. 总结vm-monitor是 Neon 自动扩缩容体系里离 Postgres 最近的一环它把Postgres 的真实内存压力翻译成对 agent 的扩容诉求把调度器批复的资源翻译成 LFC 大小与 cgroup 阈值的具体调整。本文从 README 的四个子系统出发逐一对照源码揭示了其单连接 WebSocket 模型、v1.0 协议的消息语义、cgroup v2 内存采样与滚动聚合、LFC 大小计算公式与ALTER SYSTEM热更新路径以及 Runner 主循环中扩容果断、缩容谨慎的完整决策逻辑。对于想要深入理解 Neon 扩缩容机制或希望复现/扩展其内存监控与缓存管理能力的开发者libs/vm_monitor 下的源码是最直接、最完整的参考实现。【免费下载链接】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),仅供参考