Sharding & Resharding 在生产中的几个实际问题
关于 hot shard detection、scatter-gather tail latency、online resharding cutover、跨 shard 引用、rebalancing 运维成本
学校讲 sharding:数据按 hash(user_id) % N 均匀散到 N 个 shard,每个 shard 负载差不多,吞吐线性扩展。面试里大多数候选人也答到这一步就停了。
生产里不一样。hash 均匀的是 key space,不是 access pattern。你有那种一条推文被转发几百万次的用户,有大促当天写入量是平时几十倍的时间窗口,有因为 key 设计不当导致某个 range 集中到同一个 shard 的冷热分化。shard key 选完之后,才是真正开始。
01 hash 均匀不等于负载均匀 — hot shard 是怎么来的
即使你用了完美的 hash function,shard 之间的负载差异也可以到几万倍。
原因很简单。一个社交平台按 user_id shard。用户 A 有 180 万 follower,一条 post 产生 180 万个 fan-out write。用户 B 有 40 个 follower。两个 user_id hash 之后各占一条记录,数据量差异可能只有百分之几。但 A 所在 shard 的 write QPS 可以是 B 所在 shard 的好几万倍。
我之前在一个 feed service 里碰过:一个 KOL 发了条 viral post,fan-out 到 180 万 follower timeline,那个 shard 的 write latency P99 从 12ms 涨到 300+ms,持续了近10分钟,其他 shard 完全无感。cluster-level 的 avg latency 只涨了 3%——如果你没有 per-shard metric,根本看不出来有一个 shard 已经在水线上了。
怎么在 streaming 场景下低成本检测 hot key?业界用 Count-Min Sketch,一个 probabilistic data structure,用固定内存(通常几百 KB)估算每个 key 的访问频次。每个 shard 本地维护一个 sketch,每分钟上报 top-K heavy hitter。某个 key 频次超过threshold,自动触发 key salting:把那个 hot key 拆成 key_salt_0 到 key_salt_15,散到 16 个 shard。写入均匀了。但读那个用户的数据就变成 16 路 scatter-gather——你用读的复杂度tradeoff了写入的均匀性。
02 scatter-gather—sharding 模型的隐性税
你按 user_id 做了 shard。产品加了个需求:按 region 维度统计过去 24 小时订单量。region 不是 shard key。这个 query 必须打到所有 N 个 shard,各做一次 filter + aggregate,结果汇到 coordinator merge。
N = 8 的时候没什么感觉。N = 256 的时候,这个 query 的 P99 完全由最慢那个 shard 决定。256 个 shard 里只要有一个碰上 GC pause 或者 disk IO spike,整个 query 的 latency 就被钉在那个水平。我见过一个 analytics service,256 shard 的 scatter-gather query P50 是 50ms,P99 是 ~1.5秒。差了 近30倍。瓶颈不是数据量,是 256 个并发请求里的 tail latency 分布。
每个方案都有代价。加 secondary index shard,维护两套 shard topology,每次写入双写,consistency 问题全来了。CQRS——写 primary shard,async 投影到按 region 分片的 read model——projection pipeline 本身就是一个需要 monitor 的子系统,会 lag、会出错、会 on-call page 你。或者干脆接受 scatter-gather,在 coordinator 层做 hedged request + timeout cap,靠冗余请求对冲 tail latency。
没有哪个方案是完美的。选哪条路取决于 query pattern 有多 diverse、能容忍多少 eventual consistency、on-call 团队愿意多 own 几个 pipeline。
03 online resharding
从 8 个 shard 扩到 32 个。数据要迁移,流量要切换,全程不停服。
我面试里会这样问:
我:8 个 shard 变 32 个,怎么做?
候选人:新建 32 个 shard,copy 数据过去,切流量。
我:copy 那几个小时里旧 shard 还在写。增量怎么办?
候选人:copy 完再追增量。
我:追增量的时候又有新写入进来。你确定追得上?如果写入速率比 replay 速率还高呢?
候选人:……那停写一会?
我:线上服务停写,你打算停多久?
完整的 resharding 是一个 state machine:INIT → BULK_COPY → CDC_TAILING → SHADOW_DUAL_WRITE → VERIFY → CUTOVER → DRAIN_OLD → COMPLETED。BULK_COPY 做全量快照迁移;CDC_TAILING 用 binlog 追增量——这一步关键是 replay 速率必须快于写入速率,否则 lag 越追越大永远追不上;SHADOW_DUAL_WRITE 阶段新旧 shard 同时接受写入,读还走旧 shard,同时 shadow read 新 shard 做 diff 比对;VERIFY 确认 diff 率低于阈值再进入 CUTOVER。
CUTOVER 那一步 routing table 的切换不是 atomic 的。 不同 application server 缓存的 routing 版本不一样,中间有个窗口——几百 ms 到几秒——同一个 key 的写可能分别落在新旧两个 shard 上。split brain。标准缓解手段是 fencing token:routing table 带 version number,写入时校验 version,过期的写请求直接 reject + retry。代价是 cutover 期间有短暂的 write error spike,大概 0.1-0.5%,client 得有 retry 逻辑兜底。
04 跨 shard 的foreign key根本不存在
关系型数据库里你可以靠 FOREIGN KEY 约束一致性。shard 之后这个机制就没了。
一个订单系统按 user_id shard,但订单引用了 product_id。product 数据在另一套服务上。下单时你想校验”这个 product 还有没有库存”——跨 shard RPC,多 5-15ms latency。如果那边 timeout 呢?拒绝下单?还是先写再 async 校验?
一种做法是 denormalization。把 product 的关键字段——库存状态、价格、上下架标记——冗余到 order shard 里,写入时本地校验,不走 RPC。但你需要一条 CDC pipeline 把 product 变更同步过来。pipeline lag 期间冗余数据是 stale 的——卖出一个已经下架的商品,或者价格已经更新但订单里还是旧价,这种事就发生在 lag 的那几秒里。
另一种是 deferred consistency。先写订单不做校验,async 发 event 给 reconciliation service,它去查 product service 做核对,不一致就触发补偿——取消订单或退差价。代价是用户看到了”下单成功”,过几分钟可能收到一条”订单取消”。PM 得接受这个体验。
选哪种看业务容忍度。支付场景通常不能 defer,宁可多一次 RPC。social 和 feed 场景能容忍几秒 stale,denormalization 更合理。这个判断不是纯技术判断,是业务判断。
05 shard 的日常运维
shard 建好之后,日常维护要花多少精力?
先说 schema change。32 个 shard 各跑一次 migration。一个失败了,31 个已经变了,这 1 个 schema 和其他不一样。你的 ORM 能 handle 部分 shard schema 不一致吗?大多数不能——ORM 默认假设所有 shard schema 完全相同。你得有一套 migration orchestration 工具,按 shard 逐个滚,一个失败了暂停,不要连环炸。
然后是 capacity planning。shard 之间 QPS 不均匀(参考第一节的 hot shard)。你不能看 cluster-level 的 avg CPU 做扩容决策——某个 shard CPU 85% 了,其他可能还在 30%。32 个 shard 就是 32 套 per-shard dashboard、32 个 capacity forecast。
还有 rebalancing。某些 shard 长期比其他热,你得把部分 key range 从 hot shard 迁到 cold shard ,做一次 mini resharding。我之前在一个系统里,大概每个季度做一次,每次两个工程师差不多做一周——planning、执行、验证、更新 monitoring 配置。年化四周工程时间。
业界一些团队在往 auto-sharding 方向走——Vitess、CockroachDB、TiDB 这些系统把 shard management 下沉到 infrastructure 层,自动 split、merge、rebalance。省了运维,但 debug 时多了一层abstraction,shard 行为不再完全透明。
教科书讲完 consistent hashing 就画上句号了。但 shard key 选完之后——hot shard 怎么检测、cross-shard query 怎么代价最小、resharding 怎么不停服、外键关系怎么处理、 dashboard 谁来monitor——这些才是 sharding 在生产里真正占你 bandwidth 的东西。




