一份云账单被折成纸船放进水里,船底接着长出五根柱子,每根都要有人常年站在上面

一、把省下的钱记成收益,是把长期占用记成了一次性投入

据公开报道,有搜索公司把核心搜索服务层从托管键值存储迁到自研的分布式键值存储,动机是成本与性能,公开报道给出的投入是两名工程师、两个月、四万行代码。这几个数字只能说明“这件事做成了”,它们不是下面这些判断的依据,也不适用于任何别团队的处境。我们要讨论的是另一个更常见也更少被问的问题:这类决定在什么条件下才是对的。

自研存储的真实成本不是代码量。代码是一次性的,运维负荷是持续的,而且它占的是人不是机器。备份与恢复、扩容与再平衡、故障定位、版本升级、值班响应——这五项每一项都要长期有人负责,任何一项没人接,整套系统就变成了定时故障源。把它记进选型账本才叫公平比较,否则“省下的账单”只是把成本从财务科目转到了人力科目,还转得无声无息。

反过来,也确实存在自研划算的条件,而且条件相当苛刻,通常要同时满足三条。第一,访问模式高度特定:键的分布、读写比、值的大小、查询形态都是稳定的,通用引擎为那些“通用能力”付的代价在我们的负载上拿不到回报。第二,托管方案在成本或延迟上成了可量化的瓶颈:不是“感觉贵”,而是能在请求级别算出每一笔差价,并且知道差价来自哪个计费维度。第三,也是最容易被忽略的一条:有能长期负责这套代码的人,且这个负责关系在组织里是可延续的,不挂在某两个人身上。三条里少一条,自研的账就要重算。

二、动手前先算三笔账

账目 怎么算 最容易漏的部分
单位请求成本 按同等负载折算每千次请求的成本,而不是比月账单总额 请求形状变化带来的计费维度跳档;读写配额与容量档位的关系
运维负荷折进成本 把运维五项各自的占用工时估出来,按人力单价折进长期成本 值班与告警响应是全天候占用,不是工作时段占用;知识只在少数人手里时的等待成本
故障损失差值 比较两边出问题时的发现时间、影响范围与恢复路径 托管方案自带的多副本、自动故障转移与厂商侧响应,这些自研要自己重新长出来

第二笔账是最常在评审会上缺席的一笔。它的估法不需要精确,但必须写进决策记录:这套自研存储上线后需要几个人常态承担什么。第三笔账则要诚实到难堪的地步——少了托管兜底,同样的硬件故障会演变成多大的事故、要多长时间恢复,这个差值常常比账单差值大得多。

要提前说清:这三笔账不构成“自研更贵”的结论。有些负载算完是自研明显划算,有些算完连第一笔账都不成立。账的作用是决定条件,而不是决定方向。

三、可行性判据:模型映射与隐藏依赖

数据模型能不能一一映射,是第一道关。看起来同为键值存储,两边就必然可迁,这个判断太粗。要逐个核对的是分区键与排序键的组合语义、单值大小上限、批量操作的上限、条件表达式能写到多复杂。

更要紧的是第二道关:现有代码里那些“托管方案顺手给的能力”,自研要不要重做。这一项是迁移中途才发现漏掉、进而导致方案回滚的头号原因。

  • 二级索引:现在有没有靠非键字段查数据的访问路径;如果有,自研侧要不要建索引、索引与主数据的写入顺序由谁保证、索引落后时读到旧结果怎么处理
  • 条件写:更新依赖“旧值必须等于某值”这种前置断言的地方有多少;这是幂等和并发控制的基础,缺了它,上层代码的并发保护就落空
  • 生存时间:靠字段过期自动清理的数据有哪些;自研没有等价能力时,清理任务、删除风暴与数据留存边界都要重新设计
  • 流式变更:有没有下游靠变更流消费数据;变更流的顺序保证、重试语义与消费位点管理,是一条完整的产品线,不是一个回调
  • 事务边界:跨分区或跨键的原子写有没有在用;托管方案里可能表现为可选能力,自研侧则是分布式一致性问题
  • 容量与吞吐的弹性:现在靠调一个参数就扩的能力,自研是不是需要走一次再平衡

这份清单要在评审前由业务代码里扫出来,而不是由存储团队凭印象列——洹拓接手的几个存量系统评估里,凡是靠印象列的清单都漏了东西。扫法很朴素:把所有构造存储客户端调用的位置列出来,逐个标注用到的参数名,出现频率高的那些参数就是迁移的真实工作量所在。

四、双写与影子读:顺序错了,验收就失去意义

正确的顺序是四段,不能跳,也不能调。

阶段 动作 验收指标 回退点
阶段一 双写加影子读 写两边,读仍以托管为准,自研侧只影子读并比结果 结果一致率、差异样本可解释、自研侧读延迟与错误率 关掉影子读,业务零影响
阶段二 灰度切读 按流量比例把真实读请求切到自研 分位数延迟、错误分类、业务侧指标不劣化 读流量一键切回托管
阶段三 切写 写主库换成自研,托管降级为镜像写 写成功率、写入顺序核对、双写期间的差异收敛速度 写主切回托管,需要补偿窗口
阶段四 断开镜像 停止向托管写入,保留只读与快照 完整性校验通过、恢复演练做过一次 依赖快照与变更日志回补,越晚执行代价越大

顺序之所以不能反,是因为写主一旦切换,两边就开始各自演化,比对从“差异分析”退化成“事故复盘”。阶段一的差异样本必须逐类归档:是语义差异、时序差异,还是自研侧的 bug。

数据完整性校验要三层配合。抽样比对常态化跑,按访问模式分层抽而不是随机抽;全量校验放在低峰窗口,窗口长度和校验期间的读放大要提前评估,不能把在线业务拖垮;校验失败要有明确处置档位——能自动重放的走重放,涉及业务口径的进人工队列并阻断下一阶段,绝不允许“差异在容忍范围内”这种含糊结论进评审。

五、一致性与时序语义:托管方案替你藏起来的那部分

迁移里最难写进验收标准的是语义。三类问题最常见。

最终一致读到的旧值:托管方案可能本来就有这种读模式,业务代码在某个地方默默依赖了它,或者默默忍受了它。换到自研,如果默认语义变成强一致,读放大和延迟会变;如果自研这边比原来更松,某些“写后立刻读”的路径就会读到旧数据。判据是把访问路径按“是否要求读己之写”分类,逐类对齐语义,而不是全局选一个挡位。

写入顺序:同一键的多次写入,旧值覆盖新值还是反过来,取决于有没有版本与序列号。带时间戳的覆盖写在两边实现细节不同就会产生“同样的数据、不同的结果”。切写之前要把这个语义写成测试用例。

跨分区事务:托管方案里可能被封装成一个可调用的能力,到了自研侧就是需要自己实现的协调协议。多数团队的正确选择不是把它造出来,而是把上层设计改成单分区可完成的形式——改业务比造协议便宜。

六、运维五项的最小实现、组织风险与退出策略

上线门槛应该是这五项都有最小可用实现,缺一项就不该有生产流量。

  1. 备份与恢复:备份产物按可验证的恢复演练算数。只做过备份不算,把备份拉到新集群跑起来并核对数据才算。恢复要有目标时长和上一次演练的记录两个字段
  2. 扩容与再平衡:节点加入退出后的数据搬迁是否自动、搬迁期间的读写影响、能否暂停与续跑、再平衡速度上限
  3. 故障定位工具与最小视图:至少能一眼看出集群拓扑与健康、按键定位所在分片与副本、单请求耗时分解;这三样齐了值班才敢接手
  4. 版本升级与回滚:升级顺序、兼容性窗口、回滚是否可行、数据格式变更要不要双写过渡
  5. 值班手册与告警口径:每类告警对应的影响面判断、该停哪些流量、什么条件必须拉人;口径要写死到“谁在多少分钟内响应”

组织风险要在决策当天就处理,而不是等出事。只有少数作者掌握的代码,知识转移是硬要求:设计文档、内部评审记录、安全审计结论、以及至少两名能独立完成一次故障处置的人。自研不等于可以绕过评审,恰恰相反,它绕过了厂商那层保护,自己的评审就要更严。

退出策略是最不体面也最该早写的一条。要回答:这套自研不再维护时数据怎么迁走、能不能迁回托管、接口抽象层留不留。我们的做法倾向于保留一层薄的存储接口,只在被两处以上实现验证过之后才承认它有抽象价值——过早抽象会把托管方案特有的能力(条件写、流式变更这些)抹平成最小公分母,反而让迁移更难。最后留一句提醒:把账单降下来是好事,但要清楚降下来的那部分钱换了个名字继续存在,名字通常叫“值班表”。