Neon Compute Tools 实战指南:compute_ctl 计算节点启动流程、状态机与运维详解
【免费下载链接】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
导读
compute_tools是 Neon 开源仓库中负责"计算节点(compute node)"这一侧的完整工具集,其核心二进制compute_ctl是一个用 Rust 编写的 Postgres 包装器(wrapper),通常作为 Docker 容器 entrypoint 或 systemdExecStart运行,负责在 Neon 存算分离架构下完成计算节点从空目录到可对外服务的全部初始化工作。本文将基于 compute_tools/README.md 为主体,结合 compute_tools/src 目录下的源码实现,系统讲解compute_ctl的启动流程、命令行参数、后台服务线程、HTTP API、自动伸缩(autoscaling)集成、状态机以及测试与跨平台编译方法,帮助你从"能用"进阶到"理解其内部机制"。
compute_ctl 的定位与运行形态
在 Neon 的存算分离架构中,Postgres 作为"计算节点"与存储层(pageserver)分离:每次启动计算节点都是一次"从零开始"的全新启动。compute_ctl就是为了管理这一过程而生的:
- 它以 JSON 文件形式接收集群(计算节点)规格说明(compute spec);
- 每次启动都是全新启动:数据目录会被删除并重新初始化;
- 它负责与 safekeeper、pageserver 通信,把 Postgres 引导到正确的时间线(timeline)上;
- 它启动 Postgres、创建角色和数据库,并最终"挂起"等待 postmaster 退出。
从 compute_tools/Cargo.toml 可以看到,compute_tools是一个独立 Cargo 包(compute_toolsv0.1.0),依赖了compute_api、vm_monitor、remote_storage、postgres_initdb、pageserver_page_api等工作区内部库,以及clap(命令行解析)、tokio、axum、hyper(HTTP 服务)等通用依赖,这决定了其功能边界:既要与 Neon 控制平面/存储层通信,又要对本地 Postgres 做进程级管理。
compute_ctl 的启动与初始化流程
按 README 的描述,compute_ctl在计算节点初始化期间处理所有 Neon 特有的工作,完整流程如下:
- 接受集群(计算节点)规格说明(cluster/compute spec),规格以 JSON 文件形式给出;
- 每次启动都是全新启动(fresh start),因此每次运行都会删除并重新初始化数据目录;
- 把配置文件写入
PGDATA目录; - 同步 safekeeper 并取得 commit LSN;
- 使用上一步返回的 LSN 从 pageserver 拉取
basebackup; - 尝试启动
postgres并等待其就绪、可以接受连接; - 检查并
alter/drop/create角色与数据库; - 挂起(hang)等待
postmaster进程退出。
上述流程在源码中对应 compute.rs 中ComputeNode的启动逻辑:其中第 4、5 步的"同步 safekeeper 并取得 LSN"由 sync_sk.rs 的check_if_synced/ping_safekeeper实现,basebackup 通过pageserver_page_api(libs/pageserver_api的页服务客户端)从 pageserver 获取,且支持压缩(BaseBackupCompression)。
需要特别说明的是:这是一个"无状态"的启动模型——数据不保存在本地,而是全部从存储层重建,这正是 Neon "scale to zero" 能力的基础:计算节点可以被随时销毁,下次启动时通过 safekeeper 的 LSN 与 pageserver 的 basebackup 恢复到一致状态。
spec 从哪来:控制平面 API 或本地文件
README 中的示例使用-S /var/db/postgres/specs/current.json直接指定本地的 spec 文件。从当前源码 compute_ctl.rs 看,compute_ctl还支持通过-p/--control-plane-uri与-i/--compute-id从 Neon 控制平面拉取 spec:
- spec.rs 中
get_config_from_control_plane()会请求{base_uri}/compute/api/v2/computes/{compute_id}/spec,携带NEON_CONTROL_PLANE_TOKEN环境变量作为Authorization: Bearer头; - 请求采用最多 3 次尝试的重试逻辑:网络错误、503(服务暂不可用)、502 会重试;其他状态码(如 404、500)则直接失败不重试;
- 控制平面返回
Empty状态表示"还没有 spec",compute_ctl会进入等待状态。
无论 spec 来自本地文件还是控制平面,最终都会解析为 compute.rs 中的ParsedSpec,其中包含tenant_id、timeline_id、pageserver_conninfo、safekeeper_connstrings等关键信息。对于旧版本控制平面生成的 spec,ParsedSpec::try_from还支持从pageserver_connstring字段或cluster.settings中的 GUC(如neon.pageserver_connstring、neon.tenant_id、neon.timeline_id)反向推导连接信息,保持了向后兼容。
命令行参数详解
README 给出的典型用法:
compute_ctl -D /var/db/postgres/compute \ -C 'postgresql://cloud_admin@localhost/postgres' \ -S /var/db/postgres/specs/current.json \ -b /usr/local/bin/postgres各参数含义:
| 参数 | 含义 |
|---|---|
-D/--pgdata | Postgres 数据目录路径(PGDATA),每次启动会被清空重建 |
-C/--connstr | 连接 Postgres 的连接串(cloud_admin是 Neon 的超级用户角色) |
-S | 集群(计算节点)spec 的 JSON 文件路径 |
-b/--pgbin | postgres可执行文件路径,默认值postgres,也可用环境变量POSTGRES_PATH覆盖 |
结合当前源码 compute_ctl.rs 的Cli结构(基于clap派生),compute_ctl实际支持的参数远不止上述四个,整理如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
-b, --pgbin | postgres(envPOSTGRES_PATH) | Postgres 可执行文件路径 |
-r, --remote-ext-base-url | 无 | 远程扩展存储代理网关(extension storage proxy gateway)的基础 URL,用于按需下载扩展 |
--external-http-port | 3080 | 外部 HTTP 服务器端口,控制平面、metrics 抓取器等通过它访问 compute |
--internal-http-port | 3081 | 内部 HTTP 服务器端口,供 compute 内进程(neon 扩展、local_proxy)使用 |
--http-port | 无 | Hadron 部署的向后兼容参数,功能等同--external-http-port(内部端口自动 +1) |
-D, --pgdata | 必填 | 数据目录 |
-C, --connstr | 必填 | 连接串 |
--privileged-role-name | neon_superuser | "弱"超级用户角色名,只能由小写字母与下划线组成(有正则校验) |
--cgroup(Linux) | neon-postgres | 自动伸缩场景下 postgres 所在的 cgroup 名称 |
--filecache-connstr(Linux) | host=localhost port=5432 ... user=cloud_admin | 连接 Postgres 供 vm-monitor 文件缓存使用的连接串 |
--vm-monitor-addr(Linux) | 0.0.0.0:10301 | vm-monitor 监听地址 |
--resize-swap-on-bind | false | 绑定阶段是否调整 swap 大小 |
--set-disk-quota-for-fs | 无 | 为指定文件系统设置磁盘配额 |
-c, --config | 无 | 本地配置文件(spec)路径,与-p互斥 |
-i, --compute-id | 必填 | compute 的唯一 ID |
-p, --control-plane-uri | 无 | 控制平面 API 基础 URL;指定后从控制平面拉取 spec(要求同时提供-i) |
--installed-extensions-collection-interval | 3600(秒) | 已安装扩展统计的采集间隔 |
--dev | false | 开发模式,跳过 VM 特有操作(如进程终止) |
--pg-init-timeout | 无 | Init 状态下的 Postgres 启动超时 |
--lakebase-mode | false | Databricks lakebase 部署模式(Hadron 相关) |
关键解读:
-C指定的连接串会在ComputeNode::new()(compute.rs)中被附加一组额外的 GUC 选项:-c role=cloud_admin -c default_transaction_read_only=off -c search_path='' -c statement_timeout=0 -c pgaudit.log=none。原因是用户可能通过ALTER DATABASE ... SET ...设置了statement_timeout、default_transaction_read_only等参数,会阻碍compute_ctl对数据库 schema 的配置,因此必须在连接前强制重置(控制平面提供的选项会被追加在后面,允许覆盖)。- 在 Linux + 设置了
AUTOSCALING环境变量的情况下,compute.rs 的maybe_cgexec()会使用cgexec -g memory:neon-postgres启动 postgres,使其运行在neon-postgrescgroup 中,从而允许自动伸缩系统精确控制 postgres 的资源占用。
两个核心服务线程:compute-monitor 与 http-endpoint
README 指出,compute_ctl除了主流程外还会派生两个独立服务线程:
- compute-monitor:检查 Postgres 的最后活动时间戳(last activity timestamp),并将其写入共享的
ComputeNode状态; - http-endpoint:运行一个基于 Hyper 的 HTTP API 服务器,提供就绪(readiness)与最近活动(last activity)查询。
compute-monitor 的检测原理
实现位于 monitor.rs。launch_monitor()会启动一个名为compute-monitor的线程,以500ms(MONITOR_CHECK_INTERVAL)为周期循环执行,其活动检测逻辑check()依次检查:
- 数据库统计变化(实验性检测,受
ActivityMonitorExperimental特性开关控制):对pg_stat_database求和(active_time、sessions,排除postgres、template0、template1),任一指标变化即视为有活动; - 后端状态变化:查询
pg_stat_activity中client backend类型的连接(排除自身与cloud_admin),若存在非idle后端则"最后活动=现在";若都是idle,取state_change时间戳的最大值; - walsender 数量:
select count(*) from pg_stat_replication where application_name != 'walproposer',有 walsender 则不挂起; - 逻辑复制订阅:
pg_stat_subscription中pid is not null的订阅存在则不挂起; - autovacuum worker:
pg_stat_activity中backend_type = 'autovacuum worker'存在则不挂起。
这些"不应挂起"的例外检测非常关键:monitor 的目的本质上是为 scale-to-zero 决策提供依据——如果存在复制、订阅或后台任务,compute 就不应该被自动关闭。同时 monitor 还维护两个 Prometheus 指标PG_CURR_DOWNTIME_MS(当前停机时长)与PG_TOTAL_DOWNTIME_MS(累计停机时长),并在 compute 处于Terminated/TerminationPendingFast/TerminationPendingImmediate/Failed等终态时优雅退出。
另外,monitor 在等待 Postgres 进入Running状态时受pg_init_timeout约束(默认 60 秒);若超时仍未进入 Running(例如用错误的 spec 启动、连上了错误的 pageserver/safekeeper),会直接exit(1)让计算节点重启,以便用最新 spec 重试(见 monitor.rs)。
http-endpoint:内部与外部两套 HTTP API
compute_ctl实际启动的是两个Hyper/axum HTTP 服务器(见 http/server.rs):
- 外部服务器(默认端口 3080):面向控制平面、metrics 抓取器,路由包括
/status、/configure、/refresh_configuration、/terminate、/promote、/metrics、/check_writability、/hadron_liveness_probe等; - 内部服务器(默认端口 3081):只绑定 loopback,仅对 compute 内的进程(Postgres neon 扩展、local_proxy)开放,路由包括
/extension_server/{*filename}(下载扩展)、/extensions(安装扩展)、/grants、/refresh_configuration。
各路由实现位于 compute_tools/src/http/routes:
- status.rs:
GET /status,加锁读取共享ComputeState并序列化为ComputeStatusResponse返回,实现 README 所述"就绪与最后活动查询"; - configure.rs:
POST /configure,接收 JSON 格式的ConfigurationRequest,解析为ParsedSpec后写入共享状态、置为ConfigurationPending,再阻塞等待 compute 变为Running(若失败则返回 500 及错误信息);这也是状态机中Running → ConfigurationPending的触发入口; - terminate.rs、promote.rs、refresh_configuration.rs:分别对应终止、提升(灾备切换)与热刷新配置。
ComputeState(compute.rs)是跨线程共享的核心结构:status(当前状态)、last_active(最后活动时间)、error、pspec(当前 spec)等字段都在Mutex保护之下,配合Condvar实现状态变更通知;每次set_status()都会更新COMPUTE_CTL_UP指标,便于监控系统追踪当前状态。
AUTOSCALING 环境变量与 vm-monitor
README 明确指出:如果设置了AUTOSCALING环境变量,compute_ctl会启动位于libs/vm_monitor的 vm-monitor。对于 VM 计算节点,vm-monitor 与 VM 自动伸缩系统通信,协调降级(downscaling),并在资源紧张时请求立即升级(upscaling)。
从 compute_ctl.rs 可见,autoscaling 模式下涉及三个关键参数:--cgroup(postgres 所在 cgroup,默认neon-postgres)、--filecache-connstr(vm-monitor 连接 Postgres 用于文件缓存管理的连接串)、--vm-monitor-addr(默认0.0.0.0:10301)。
vm-monitor 本体是工作区独立库,位于 libs/vm_monitor,在compute_tools的依赖声明(Cargo.toml)中通过path = "../libs/vm_monitor/"引入。其角色可以概括为:作为 compute 内部与外部自动伸缩系统之间的"资源代言人"——平时配合 scale-to-zero 观察资源利用率以推动降级,当 Postgres 在 cgroup 层面出现内存/CPU 压力时,则向上请求升级,从而让 Neon 的计算节点在"最小可用资源"与"峰值需求"之间动态调整。
计算节点状态机(State Diagram)
README 附带的 mermaid 状态图完整描述了 compute 在compute_ctl管理下的生命周期。该状态机在源码中的落地形态即ComputeStatus(见 compute.rs 的set_status()/set_failed_status()与COMPUTE_CTL_UP指标),下面完整保留原图:
对关键状态的解读:
- Empty:compute 进程刚被拉起,尚无任何 spec;
- ConfigurationPending / Configuration:已收到(或正在拉取)spec,进入配置阶段——对应 README 启动流程的第 2~5 步(清空数据目录、写配置、同步 safekeeper、拉 basebackup);
- Init:spec 立即可用时直接从 Empty 进入,对应"启动 Postgres 并等待就绪"阶段;若失败进入
Failed; - Running:配置完成、Postgres 可接受连接,这是稳态;
- RefreshConfigurationPending / RefreshConfiguration:运行中收到
/refresh_configuration请求,拉取新 spec 并热重配置(如变更实例规格、GUC 参数),失败会回到RefreshConfigurationPending重试; - TerminationPendingFast / TerminationPendingImmediate:收到终止请求;Fast 模式会保留 30 秒让控制平面检查状态,Immediate 立即终止;随后进入
Terminated并退出进程; - Failed:任何阶段配置失败都会进入;仍可通过
/refresh_configuration请求尝试恢复,或者进程直接退出。
从代码实现看,两个终止路径的差异也体现在 monitor 的退出逻辑中(monitor.rs):monitor 一旦发现 compute 进入这四个终态之一便停止活动检测、优雅退出。
测试与代码质量
README 给出了开发compute_tools时常用的三个命令,原样保留并补充说明:
# 1. Cargo 格式化 cargo fmt # 2. 运行测试 cargo test # 3. Clippy 静态检查(将警告视为错误) cargo clippy --all --all-targets -- -Dwarnings -Drust-2018-idioms仓库中已存在的测试包括 compute_tools/tests/config_test.rs 与 compute_tools/tests/pg_helpers_tests.rs,前者针对配置解析/生成逻辑,后者针对 Postgres 辅助函数。此外compute_tools的testingfeature(testing = ["fail/failpoints"])会启用 failpoint 支持,便于在 compute_ctl.rs 的单元测试中注入故障场景(例如模拟不同 spec 组合下的--pgdata/--connstr解析)。同时 Cargo.toml 中#表明整个 crate 禁用 unsafe 代码,这也解释了 clippy 检查为什么对代码风格如此严格。
跨平台编译:从 macOS 交叉编译 Linux GNU 可执行文件
README 提供了两种从 macOS(x86)交叉编译 Linux GNU(Rust 术语中的x86_64-unknown-linux-gnu平台)可执行文件的方法,这是 CI 或本地构建 compute 镜像时的常见需求。
方式一:使用一次性 Docker 容器
使用官方 rustlang/rust(或rust)镜像,把当前目录挂载进去编译:
docker run --rm \ -v $(pwd):/compute_tools \ -w /compute_tools \ -t rustlang/rust:nightly cargo build --release --target=x86_64-unknown-linux-gnu或者一行版:
docker run --rm -v $(pwd):/compute_tools -w /compute_tools -t rust:latest cargo build --release --target=x86_64-unknown-linux-gnu方式二:Rust 原生交叉编译
在宿主机上添加目标平台,并安装 macOS 交叉编译工具链:
# 添加编译目标 rustup target add x86_64-unknown-linux-gnu # 安装 macOS 交叉编译器工具链 brew tap SergioBenitez/osxct brew install x86_64-unknown-linux-gnu最后通过CARGO_TARGET_*环境变量指定链接器后构建:
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-unknown-linux-gnu-gcc cargo build --target=x86_64-unknown-linux-gnu --release注意事项:compute_tools依赖链中包含 Linux 平台专用代码(例如 compute.rs 中#[cfg(target_os = "linux")]的 filecache/cgroup/vm-monitor 参数、nix系统调用、rlimit等),因此 README 提供的两种交叉编译方案目标明确针对 Linux 部署环境;本机(macOS)开发时这些平台相关字段会被cfg条件编译剔除,不影响本地cargo test与cargo clippy的正常使用。
总结
compute_ctl是 Neon 存算分离架构在计算节点侧的"总调度器":它把"清空数据目录 → 同步 safekeeper 取 LSN → 从 pageserver 拉 basebackup → 启动 Postgres → 配置角色/数据库 → 挂起等待"这一整套无状态启动流程自动化,并通过 compute-monitor 与内部/外部两套 HTTP API 支撑起 scale-to-zero、自动伸缩与动态重配置能力。README 中的状态机图则是对这一整套生命周期的权威抽象——无论是排查Failed、理解/configure热更新,还是调试终止流程,都可以从这张图出发,在 compute.rs、monitor.rs 与 compute_tools/src/http/routes 中找到对应的代码实现。
【免费下载链接】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),仅供参考