Neon Pageserver Storage Shard 扩容分析

发布时间:2026/7/23 15:17:19
Neon Pageserver Storage Shard 扩容分析 Neon pageserver 的扩容通过 Storage Shard 机制实现。这不是传统意义上对单个 pageserver 水平扩展而是两层扩容策略1. Tenant Sharding分片 —将租户数据打散到多个 pageserver 节点上2. Node Scale-out节点伸缩 —增减 pageserver 物理节点两者由 storage controller 统一管理。---一、架构总览架构总览Neon Storage Shard 架构概览用户/控制面neon_local CLI / storcon_cli / REST API↓ HTTP callsStorage Controller (Rust)Scheduler(AZ感知)Reconciler(执行差异)TenantShard(Intent/Observed)DB: PostgreSQL (tenant_shards, nodes 表)↓ gRPC/HTTPPageserver(shard 0)Pageserver(shard 1)Pageserver(shard N)关键角色组件位置职责Storage Controllerstorage_controller/src/service.rs (~10K行)核心编排器管理所有 shard placement、split、migrationPageserverpageserver/数据平面存储实际 data layersTenant Managerpageserver/src/tenant/mgr.rs管理本地 shard 集合DBstorage_controller/migrations/持久化 tenant_shards nodes---二、Shard 概念模型核心类型定义在 libs/utils/src/shard.rs 和 libs/pageserver_api/src/shard.rs┌────────────────────────────────────────────────────────┬────────────────────────────────────────────────────────────┐│ 类型 │ 说明 │├────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────┤│ TenantShardId {tenant_id, shard_number, shard_count} │ 全局唯一标识 │├────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────┤│ ShardCount (u8) │ 总分片数0unsharded │├────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────┤│ ShardNumber (u8) │ 0-based 索引最多 255 │├────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────┤│ ShardStripeSize │ 默认 4096 页16 MiB决定连续 block 同放一个 shard │├────────────────────────────────────────────────────────┼────────────────────────────────────────────────────────────┤│ ShardIdentity │ {number, count, stripe_size, layout} —包含 key→shard映射 │└────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────────┘Key 到 Shard 的映射用 MurmurHash32与 Postgres smgr 兼容hash murmurhash32(rel_node) murmurhash32(block_num / stripe_size)shard_number hash % shard_count数据分三类- local —只存一个 shard- global —存所有 shard如 rel_size- disposable —split 后可丢弃不属于本 shard 的数据---三、三种扩容操作1. Tenant Shard Split租户分片拆分将租户从一个 shard 拆成多个 shards分布到更多 pageserver 上。API: PUT /control/v1/tenant/:tenant_id/shard_split触发方式:# CLIstorcon_cli tenant-shard-split --tenant-idid --new-shard-countN# APIcurl -X PUT http://controller:PORT/control/v1/tenant/tid/shard_split \-d {new_shard_count: N}执行流程 (service.rs →tenant_shard_split()):1. 标记父 shard 为 Splitting数据库锁2. 创建 child shard 行继承 generation3. 对每个 pageserver 上的 parent shard 调用 PUT /v1/tenant/id/shard_split4. Pageserver 侧执行 (mgr.rs →do_shard_split()):- Phase 1: Prepare —下载 index_part.json上传到各 child remote path- Phase 2: Hardlink —硬链接 resident layer files零拷贝冷分片- Phase 3: Spawn children —创建 child location- Phase 4: WAL catch-up —等 children 追平 WAL- Phase 5: Shutdown parent —关闭 parent shard- Phase 6: Release lock —清理 splitting flag5. 完成 —DB 中删除 parent 行child 行 splitting06. 后台 warmup —child shard 在 secondary mode 下预热限制:- 只能 2 的幂次扩展1→2,2→4,4→8...- stripe_size 只在第一次 split (1→N)时能设之后不可改2. Node Drain/Fill/Delete节点级别伸缩Drain —把所有 shard 从一个 pageserver 迁出curl -X PUT http://controller:PORT/control/v1/node/node_id/drain- 最多并发 64 个 reconcile- 优先用 warm secondary 做 cutover零停机迁移- 用于维护/退役节点Fill —把 shard 迁回到新节点curl -X PUT http://controller:PORT/control/v1/node/node_id/fill反向操作配合 drain 用于滚动升级Delete —物理删除节点curl -X DELETE http://controller:PORT/control/v1/node/node_id/delete先 drain 完再 delete3. Single Shard Migrate单 shard 迁移手动指定某个 shard 迁到特定 pageservercurl -X PUT http://controller:PORT/control/v1/tenant/shard_id/migrate \-d {node_id: target}---四、自动扩缩容HSCC (Hyper Scalable Compute Controller) 利用以下指标驱动 autoscalingsql_exporter_autoscaling 暴露的指标端口 9499从 neon_collector_autoscaling.jsonnet 引入全部跟 LFC 相关┌────────────────────────────────────────────────────────────┬────────────────────────────────────────────┐│ 指标名 │ 说明 │├────────────────────────────────────────────────────────────┼────────────────────────────────────────────┤│ lfc_approximate_working_set_size_seconds{duration_seconds} │ 过去 1~60 分钟每个窗口的工作集大小核心 │├────────────────────────────────────────────────────────────┼────────────────────────────────────────────┤│ lfc_cache_size_limit │ LFC 缓存上限 │├────────────────────────────────────────────────────────────┼────────────────────────────────────────────┤│ lfc_hits / lfc_misses │ 缓存命中/未命中计数 │├────────────────────────────────────────────────────────────┼────────────────────────────────────────────┤│ lfc_used / lfc_writes │ 缓存使用量和写入量 │└────────────────────────────────────────────────────────────┴────────────────────────────────────────────┘其中 lfc_approximate_working_set_size_windows 是最关键的 —看历史 working set 趋势来判断是否需要 split。Auto-Split 机制见 test_runner/performance/test_sharding_autosplit.py —storage controller 监控 tenant size / working set超过阈值时自动触发 split。---五、关键文件清单┌──────────────────────────────────────────────────────┬──────────────────────────────────────────────────┐│ 文件 │ 内容 │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ storage_controller/src/service.rs │ 核心编排split/migrate/drain/fill/reconcile_all │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ storage_controller/src/tenant_shard.rs │ TenantShard 状态机 (Intent/Observed/Policy) │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ storage_controller/src/scheduler.rs │ 节点打分/AZ感知调度 │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ storage_controller/src/reconciler.rs │ 将 intent diff 转化为 pageserver API 调用 │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ storage_controller/src/http.rs │ REST API 端点注册 │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ storage_controller/src/background_node_operations.rs │ Drain/Fill/Delete │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ storage_controller/src/persistence.rs │ DB CRUD (tenant_shards/nodes 表) │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ storage_controller/src/schema.rs │ Diesel table definitions │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ storage_controller/migrations/* │ DB schema evolution │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ libs/utils/src/shard.rs │ ShardCount/ShardNumber/TenantShardId │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ libs/pageserver_api/src/shard.rs │ ShardIdentity/key_to_shard_number │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ pageserver/src/tenant/mgr.rs │ do_shard_split() —pageserver 侧 split 逻辑 │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ pageserver/src/http/routes.rs │ pageserver API endpoint │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ control_plane/storcon_cli/src/main.rs │ CLI 入口 │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ compute/vm-image-spec-bookworm.yaml │ VM 中 sql_exporter 启动配置 │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ docs/rfcs/2025-02-14-storage-controller.md │ Storage Controller RFC │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ docs/rfcs/028-pageserver-migration.md │ Zero-downtime migration procedure │├──────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤│ test_runner/regress/test_sharding.py │ 分片功能测试 │└──────────────────────────────────────────────────────┴──────────────────────────────────────────────────┘---六、验证方案1. 查看当前 shard 分布: curl http://controller:PORT/debug/v1/tenant/tenant_id2. 定位 shard 在哪台 pageserver: curl http://controller:PORT/debug/v1/tenant/tid/locate3. 手动 split: 用 storcon_cli tenant-shard-split 或 HTTP API4. 观察 reconciliation: 看 /debug/v1/tenant/tid/shards 的 intent vs observed5. 跑 test_sharding.py 确认 split 后数据正确性