多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Neon Pageserver Storage Shard 扩容分析

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 后数据正确性
返回列表