尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Neon Storage Controller 架构解析:Serverless Postgres 存储控制面的设计、调度与一致性保障

Neon Storage Controller 架构解析:Serverless Postgres 存储控制面的设计、调度与一致性保障 Neon Storage Controller 架构解析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 仓库中的回溯性设计文档RFC2025-02-14-storage-controller.md系统讲解storage-controller服务的核心设计它如何把 Tenant/Timeline 物理映射到 Pageserver 与 Safekeeper如何在不依赖自建共识机制的前提下保证数据安全如何以内存为主、Postgres 为辅的方式支撑百万级分片规模。读完本文你将掌握 Neon 存储控制面的总体架构、意图/观测Intent/Observed调度模型、Reconciler 对账机制、无停机部署与优雅下线流程并能结合仓库源码storage_controller 目录进一步深入验证每一处实现。1. 什么是 Storage Controller存储抽象的统一 APINeon 将计算与存储分离见 separation-compute-storage.md而storage-controller是存储侧的控制面服务。按照 RFC 的定义它管理Tenant 与 Timeline 到 Pageserver 与 Safekeeper 的物理映射对外充当存储这一抽象概念的 API上层系统控制平面、proxy、compute 等在创建、删除、迁移 tenant/timeline 时无需关心具体该与哪台 pageserver 或 safekeeper 通信也无需理解编排背后的微妙规则全部交由 controller 完成。------------------------------------------------------ | Control Plane / 上层系统 | ----------------------------------------------------- | HTTP API (tenant/timeline CRUD) v -------------------- | storage-controller | | (in-memory 状态) | ------------------ | | 调度/对账 | | 心跳/健康 v v ----------- ----------- | Pageserver| | Safekeeper| 统称 nodes ----------- -----------从源码结构看该服务独立成 cratestorage_controller/Cargo.toml主入口在 storage_controller/src/main.rs。RFC 指出controller 最初只管理 pageserver2025 年起扩展为同时管理 safekeeper文档中不带限定词的 nodes 一词通常指 pageserver。一个关键的历史动机controller 是 2024 年上半年为实现**存储分片sharding**而引入的核心组件尤其是 分片拆分 RFC。分片将单个 tenant 拆成多个 tenant shard每个 shard 可以独立调度、独立迁移这使谁在哪个 pageserver 上的管理复杂度陡增因此必须有一个专门的编排服务。2. 三大核心设计取舍Design ChoicesRFC 从持久化、共识、规模三个维度给出了 controller 的设计哲学这些取舍直接决定了后续所有实现。2.1 持久化外部 Postgres 是唯一的持久状态所有持久状态都依赖一个外部 Postgrescontroller 自身不使用任何本地持久存储。同时controller 刻意避免任何不必要的持久化 I/O大多数进行中的变更跟踪都在内存中完成而不是把每一步进度写进数据库在 pageserver 之间迁移 tenant shard 时只通过数据库递增 generation number绝不持久化 tenant shard 的完整状态。RFC 给出的好处有两点其一数据库不会成为实际上的扩展瓶颈作者预期内存规模问题会先于每秒事务数问题到来其二当使用云数据库服务承载 controller 的 Postgres 时可以显著降低成本。代价则是存在一个引导bootstrapping问题controller 无法孤立部署必须先有一个可用的数据库系统。RFC 明确了三种典型部署前提部署场景数据库来源云环境使用云厂商提供的 Postgres 托管服务成熟的本地环境使用已有的数据库运维设施测试/开发环境单节点 vanilla Postgres 即可满足这一设计在 storage_controller/src/persistence.rs 的文档注释中得到印证。注释明确列出必须持久化与作为实现细节持久化的内容必须持久化generation number必须单调递增以保证数据安全、Tenant 的 PlacementPolicy 与 TenantConfig其真值来源在外部、Node 的调度策略实现细节Node 的 host/port理论上可以让节点自发注册心跳但让本服务作为与哪些节点通信的权威在运维上更简单。数据库访问全部收敛在Persistence类型中使用dieselcrate 提供客户端功能这样便于理解哪些代码在触碰数据库。同时database 只在无法以其他方式恢复状态的场合才使用例如 tenant shard 的次要secondarypageserver 位置并不入库而是启动时从运行中的 pageserver 学习或通过调度决策补齐缺口。这在持久化模块中有同样表述——secondary 位置不入库显著降低了数据库负载使 controller 可以跑在非常廉价的 Postgres 实例上。2.2 共识不实现强共识用数据库事务兜底controller没有自实现任何强共识机制而是采用两条委托策略强一致需求委托给数据库事务例如 pageserver 的 generation number通过 Postgres 事务保证高可用部署靠数据库中的控制器实例记录用一个按时间戳区分的可用 controller 实例表来确定 leader而不是让多个 controller 直接协商。RFC 明确指出这样做的收益避免常年运行三个 controller 的固定成本实现简化——不必把所有配置变更都表述成 raft 事务。其代价是在部分网络隔离的情况下一个 controller 可能做出与真正活跃的 controller 相冲突的 pageserver 状态变更从而引发可用性问题。RFC 给出了三层边界约束controllers数据库表让失控节点最终意识到自己已不是 leader 并主动退位若失控节点无法访问数据库它就隐式地停止进展失控 controller 无法对系统造成持久损害pageserver 数据与 safekeeper 配置都受 generation number 保护而 generation 只能经由 Postgres 事务更新——也就是说没有任何 controller 会信任自己独立决定 generation。这一数据库 CASCompare-And-Swap思路在 storage_controller/src/leadership.rs 与 storage_controller/src/service.rs 中落地例如DatabaseError::Cas错误类型见 persistence.rs以及文档所述递增版本号前先检查期望值的事务模式。Leader 选举的入口逻辑见 leadership.rs 的step_down_current_leader/become_leader启动时先在数据库中定位当前 leader必要时请求其让位start_as_candidate模式再在数据库中把自己标记为 leader。2.3 规模面向高但非无限的规模设计RFC 给出了明确的量化目标每个 tenant shard 的内存占用约 8kB因此在资源适中的服务器上扩展到百万级 attached shards是现实的。处于 detached未附着到 pageserver状态的 tenant 不需要 controller 持续管理可以从内存降级回数据库。典型的更新频率tenant shard 大约每周更新一次即部署周期。部署时每个 pageserver 会迁移数千个 tenant因此 controller 极少数情况下需要做 O(N)一次性作用在所有 shard 上的工作。O(N) 工作的两个场合① 正常启动时从数据库加载到内存② 非干净启动没有从前一个 controller 接手观测状态时扫描所有 pageserver 上的全部 shard。RFC 强调这两个位置必须写得高效——在高规模下storage controller 的启动耗时仍应控制在数十秒量级。当单个 controller 触及规模上限时解决方案是再部署一个 controller配上它自己的 pageserver 与 safekeeper每个controller 其存储服务器应被视为一个逻辑集群cell。3. 高层设计内存为核心的双态系统storage-controller本质是一个内存系统所有 attached tenant 的状态既保存在内存中也以某种形式体现在持久化 Postgres 里。这种以内存为主、数据库为辅的架构贯穿整个服务。3.1 基础设施Tokio Service RwLockcontroller 是基于 tokio 的异步 Rust 二进制见 main.rs 中tokio::runtime::Builder::new_current_thread()的构造。服务围绕Service类型构建它实现所有对外交互的入口HTTP handler 大多是 service 函数的薄封装并持有大部分内存状态例如 tenant shard 列表。Service模块位于 storage_controller/src/service.rs并拆分为 service/ 子模块含safekeeper_reconciler、safekeeper_service、chaos_injector、feature_flag等。状态存放在包裹于RwLock中的ServiceInner。这个整体大锁用于简化状态变更的推理每个获取写锁的函数都可视为对内存状态的一次可串行化事务。RFC 坦承这个锁显然是瓶颈但仍然可扩展以管理数百万 tenant。从 service.rs 的字段可见Service依赖的各子系统Scheduler、Heartbeater、Leadership、ComputeHook、Persistence、ChaosInjector等与 RFC 的描述一一对应。3.2 基础设施CLI 与配置入口storage_controller/src/main.rs 中的Cli结构体基于 clap提供了完整运维入口值得展开监听与认证参数说明--listen/--listen-httpsHTTP/HTTPS 监听地址两者至少指定一个--public-key用于认证客户端控制面的 JWT 公钥--jwt-token与所辖 pageserver 通信用的令牌--safekeeper-jwt-token与所辖 safekeeper 通信用的令牌--control-plane-jwt-token/--peer-jwt-token与云控制面 / 对等 controller 通信的令牌--control-plane-url控制面存储 API 前缀数据库与心跳--database-url如postgresql://localhost:1234/storage_controller、--db-connect-timeout默认 5s、--max-offline-interval、--max-warming-up-intervalpageserver 重启附近的更宽容离线宽限期、--heartbeat-interval。对账并发--reconciler-concurrency、--priority-reconciler-concurrency、--safekeeper-reconciler-concurrency每个 safekeeper 的并行对账上限。分片相关--split-threshold自动拆分阈值默认关闭、--max-split-shards默认 16、--initial-split-threshold、--initial-split-shards默认 2、--shard-split-request-timeout。安全与严格模式非--dev模式下strict 模式若public_key、pageserver/safekeeper/control-plane JWT 令牌缺失或未设置--control-plane-url或--timeline-safekeeper-count 3启动都会直接报错拒绝运行见main.rs中StrictMode的校验逻辑。--dev模式仅用于测试可跳过认证。秘密secrets可按CLI 参数优先、其次环境变量的顺序加载环境变量包括DATABASE_URL、PAGESERVER_JWT_TOKEN、SAFEKEEPER_JWT_TOKEN、CONTROL_PLANE_JWT_TOKEN、PEER_JWT_TOKEN、PUBLIC_KEY。其他实用参数--chaos-interval/--chaos-exit-crontab混沌测试周期性触发 tenant 迁移或立即退出、--use-https-pageserver-api/--use-https-safekeeper-api、--ssl-key-file/--ssl-cert-file/--ssl-ca-file/--ssl-cert-reload-period默认 60s、--tenant-rate-limit默认每 tenant 每秒 10 次请求、--start-as-candidate、--timelines-onto-safekeepers、--timeline-safekeeper-count默认 3按可用区分散选择、--kick-secondary-downloads测试用加速热力图下载、--handle-ps-local-disk-loss特性开关等。3.3 Pageserver 的 tenant 调度与对账核心循环这是 controller 最核心的机制RFC 以意图/观测Intent/Observed模型展开3.3.1 Intent 与 Observed 双状态每个 tenant shard 由TenantShard类型表示见 storage_controller/src/tenant_shard.rs它同时携带两种状态intent意图状态shard 应该如何分布——哪个 pageserver 承载主附着attached、哪些承载次要secondary副本、偏好可用区preferred_az_id。设置意图状态的动作称为调度scheduling。observed观测状态从 pageserver 实际观测到的当前分布。执行远程 I/O 让观测状态匹配意图状态的动作称为对账reconciliation。代码中IntentStatetenant_shard.rs恰好包含三个字段attached: OptionNodeId、secondary: VecNodeId、preferred_az_id: OptionAvailabilityZone。TenantShard还维护了generation、policyPlacementPolicy、config透传给 pageserver 的 tenant 配置、sequence协调更新与后台 reconciler、last_error、consecutive_reconciles_count连续对账计数达到阈值视为卡死等运行期字段。Scheduler类型storage_controller/src/scheduler.rs负责做出意图状态的选择为新建 tenant shard 挑选 pageserver、在原 pageserver 故障时指定替代者。调度器对每个节点维护SchedulerNode统计shard 数、attached shard 数、home shard 数、可用区、是否可调度并使用调度评分NodeSchedulingScore如NodeAttachmentSchedulingScore/NodeSecondarySchedulingScore排序候选节点。观测状态在 tenant 对账后更新并包含针对某 pageserver 的None状态表示未知。RFC 特别强调这个设计的目的保证安全清理——当一次对 pageserver 的远程调用开始但未完成或 pageserver 重启后我们对它的状态不确定时可以用None安全地兜底避免在未知状态下贸然做破坏性清理。3.3.2 Reconciler后台对账任务Reconciler类型storage_controller/src/reconciler.rs负责实际更新 pageserver 以实现意图状态。它的生命周期要点当Service判定某个 shard 需要对账时实例化由后台 tokio 任务拥有并运行至完成Reconciler 无权访问Service状态它构造时被填充一份相关信息快照完成后把结果提交到 channel由Service消费并更新 tenant shard 的观测状态——这保证了内存大锁不会被长时间占用的对账任务拖累Reconciler可以访问数据库但只用于一个目的在把 shard 附着到 pageserver 之前更新其 generation number。这正对应 RFC 2.1 节迁移只触碰数据库递增 generation的设计改变 tenant 调度的操作会在必要时派生 reconciler同时存在一个后台循环逐个检查所有 shard 是否需要对账——这保证了即使之前某些对账失败最终仍能取得进展最终收敛。从 reconciler.rs 可看到Reconciler的完整字段intent 目标TargetState、需要 detach 的节点列表、ReconcilerConfig含优先级 Normal/High、secondary 预热超时、下载请求超时等、ComputeHook用于在迁移后通知运行中的 Postgres 计算实例失败则置pending_compute_notification保证后续重试、取消令牌CancellationToken当 pageserver 下线或 PlacementPolicy 改变时中止对账、GateGuard优雅停机时等待所有 reconciler 响应取消等。3.3.3 通用路径与在线迁移live migration特例RFC 指出 Reconciler 有两条路径通用路径按需对 pageserver 做 attach/detach在线迁移特例当观测状态表明存在一个健康的已附着位置可供迁移时就走这条路径。实际生产中在线迁移更常见它实现了早期 在线迁移 RFC 描述的过程。028-pageserver-migration.md 详细说明了在线迁移的三个优先级目标无缝客户端可用性不中断 快速 高效最小化 I/O 与成本。其核心是双附着dual-attached状态与温热次要位置warm secondary locations先把 tenant 附着到新节点并预热从 S3 下载数据完成 graceful cutover 后再从旧节点 detach。这也解释了为何TenantShard的 intent 同时维护attached与secondary两个位置集合——分片可同时附着于多个 pageserver这正是后面 timeline CRUD 删除规则复杂性的根源。3.3.4 调度优化Scheduling optimisation在周期性后台对账循环中controller 还会执行调度优化寻找处于次优位置的 shard 并移动它们。典型场景shard 附着在偏好可用区之外例如节点故障后迁移出去的把它们迁回偏好 AZ同一 tenant 的多个 shard 附着在同一台 pageserver上例如分片拆分后把它们分散到别处。调度优化是多步骤过程以保证优雅切换先创建新的 secondary 位置 → 等待其预热warm up→ 再 cutover。RFC 特别强调这不是显式操作队列而是通过迭代调用优化函数实现——每个中间状态都会被识别为可以产生下一步优化的状态从而自然推进整个流程。调度器代码中NodeSchedulingScore::for_optimization的存在印证了这一点优化评分会丢弃基于节点利用率的成分避免因磁盘占用等暂时性波动引发调度震荡flapping。3.3.5 心跳与故障检测Heartbeater类型storage_controller/src/heartbeater.rs负责检测 pageserver 不可用。其状态机包含三态Available含last_seen_at与利用率、WarmingUppageserver 重启期间、Offlinesafekeeper 侧则只有Available/Offline。检测结果反馈给Service采取行动当某 pageserver 被标记为不可用时其上的 tenant shard 会被重新调度并派生 Reconciler 将 shard 切换cutover到新位置。相关宽限参数max-offline-interval、max-warming-up-interval由 CLI 暴露见上文 3.2 节。3.4 Timeline 的 CRUD 操作与 generation 语义RFC 特别讨论了 pageserver 上 timeline 的创建与删除CRUD。其难点在于S3 才是哪些 timeline 存在的权威存储由 pageserver 的 generation number 体系管辖而一个 shard 可能同时附着在多个 pageserver上因此 CRUD 必须处理并发附着timeline 操作仅在满足以下条件时才算是持久的pageserver 返回 ack 之后该 pageserver 的 generation 仍然是最新的删除操作尤其严格只有当所有附着位置都 ack 了删除删除才算持久。因为如果只有最新 generation 的位置 ack而某个旧 generation 的附着为它写入了 index那么这条 timeline 仍可能死而复生。这条规则可以在 pageserver-api 与 pageserver 侧的 generation 检查逻辑中找到呼应——它本质上是以数据库事务维护 generation、以 generation 判定操作有效性这一全局原则在 timeline CRUD 上的具体应用。3.5 零停机 controller 部署当两个 storage controller 同时运行时它们通过数据库协调出一个 leader另一个 controller 可以把请求代理proxy给 leader。RFC 明确指出这不是强共识机制controller 必须同时承受脑裂split-brain场景。其保护手段是凡是递增版本号之类的代码都使用先检查期望值再修改的数据库事务。脑裂可能影响可用性例如两个 controller 争抢把 shard 附着到何处但绝不应当影响持久性与数据完整性。详细机制见 Storage controller restarts RFC。代码层面leadership.rs 实现了 leader 的获取/让位流程peer_client.rs 提供GlobalObservedState从旧 leader 接管观测状态与PeerClient代理请求、请求让位start_as_candidate标志控制新实例是否主动请求现任 leader 让位。3.6 部署期间 pageserver 的优雅排空与填充Drain Fillcontroller 提供排空drain 填充fillpageserver的功能用于部署新 pageserver 二进制在 pageserver 重启期间让客户端不再使用它从而做到零中断滚动升级。完整设计见 Graceful restarts RFC。在源码中这一能力对应 storage_controller/src/background_node_operations.rs 的Drain、Fill、Delete操作及OperationHandler。main.rs中--max-secondary-lag-bytes参数排空 pageserver 时 secondary 位置允许的最大落后字节数正是为优雅排空服务的排空前要确保 shard 的新位置已跟上主位置的写入进度。3.7 Safekeeper 的 timeline 调度开发中RFC 最后说明safekeeper 的 timeline 调度正在开发中详见 Safekeeper dynamic membership change RFC。不过从当前源码可以看到其雏形已经相当完整safekeeper.rssafekeeper 节点模型service/safekeeper_reconciler.rs 与 service/safekeeper_service.rssafekeeper 对账逻辑--timelines-onto-safekeepers、--safekeeper-reconciler-concurrency、--timeline-safekeeper-count默认 3且要求从不同可用区选择等 CLI 参数数据库迁移脚本 storage_controller/migrations/如2025-07-17-000001_hadron_safekeepers表明 safekeeper 成员关系已逐步纳入持久化模型。4. 结合仓库源码的实践指引4.1 从代码验证设计主张数据库仅用于必要处见 persistence.rs 顶部的大段文档注释其中明确写出不存储 most state durably并列出必须持久化的三类内容generation、PlacementPolicy/TenantConfig、node 调度策略与两个性能敏感点递增 generation、pageserver re-attach 时批量递增 generation 须避免 O(N) 查询。Reconciler 不持有 Service 锁见 reconciler.rs 的Reconciler结构——它是构造时的快照original_observed、intent、detach等并通过 channel 回传结果service::ReconcileResultRequest佐证 RFC 所述与 Service 状态解耦的设计。Scheduler 负责意图见 scheduler.rs 的SchedulerNode、NodeSchedulingScore与 AZ 匹配排序逻辑attached 偏好AzMatch::Yessecondary 反而偏好AzMatch::No即尽量避开偏好 AZ见AttachmentAzMatch与SecondaryAzMatch的Ord实现。Heartbeater 三态检测见 heartbeater.rs 的PageserverState::Available/WarmingUp/Offline。Leader 协调见 leadership.rs 与 peer_client.rs 的step_down_current_leader、become_leader、GlobalObservedState。4.2 数据库 schemadiesel 迁移controller 的所有持久化表由 storage_controller/migrations/ 下的 diesel 迁移脚本管理从迁移历史可看出系统的演进脉络create_tenant_shards、create_nodes、tenant_policies、pageserver_az、safekeepers、safekeeper_timelines、create_metadata_health、create_leader、timeline_imports等。RFC 2.1 节提到的controllers表用于高可用 leader 记录正是通过这类迁移建立并维护的。4.3 本地运行与测试单机/开发环境可用--dev模式配合--database-url指向本地 Postgres 启动 controller此时认证与严格校验被放宽参见 main.rs 的StrictMode::Dev分支。集成测试仓库的 test_runner 通过neon_local拉起整套环境controller 的--neon-local-repo-dir、--use-local-compute-notifications参数即为本地测试而设main.rs 注释明确标注 Only relevant for testing。混沌测试--chaos-interval与--chaos-exit-crontab可周期性地强制触发 tenant 迁移或进程立即退出用于验证对账与恢复路径的健壮性对应实现见 service/chaos_injector.rs。HTTP API 路由由 storage_controller/src/http.rs 的make_router构建并通过--listen/--listen-https暴露pageserver_api::controller_api见 libs/pageserver_api定义了 tenant 注册、描述、迁移、分片拆分等请求/响应类型是理解 API 契约的入口。5. 小结一套平凡组件 严格纪律的控制面纵观整个 RFC 与源码storage controller 的工程哲学非常鲜明不引入复杂的分布式原语无自研 raft、无独立强一致存储而是用内存快照 外部 Postgres 事务 generation number 纪律拼出一套可扩展、可容错的控制面。它的关键约束可以总结为三条内存是主战场ServiceInner的 RwLock 保证状态变更可串行化Reconciler 通过快照 channel 与主状态解耦从而不阻塞调度。数据库是最后的防线只有 generation、外部真值PlacementPolicy/TenantConfig、node 策略等无法以其他方式恢复的状态才落库任何破坏性决策都必须经过数据库事务的 generation 校验。脑裂被容忍但被约束无强共识换来部署成本与实现复杂度的大幅下降而失控 controller 无法持久损害系统的保证来自 generation 的数据库事务属性。对于希望深入 Neon 存储侧代码的读者建议按此路线图阅读RFC 本文 → 分片拆分 032-shard-splitting → 在线迁移 028-pageserver-migration → 重启与优雅排空 037-storage-controller-restarts 与 033-storage-controller-drain-and-fill随后对照 storage_controller/src/ 下的service.rs、scheduler.rs、reconciler.rs、heartbeater.rs、persistence.rs逐个模块验证即可建立起从设计到实现的完整认知。【免费下载链接】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),仅供参考
返回列表