主题
05 - 部署运维、容量与成本
一句话:单机起栈和生产集群之间差的不是副本数,而是六件事——角色能不能真拆、状态是不是外置、探针会不会误杀、解析任务能不能重投、故障时丢什么是否已经决定过、换版本要重跑哪些回归;本栏不替这些决定填数字,只给出能算的方法:没有压测数据时不写结论,但必须写公式与代入方式,否则连该买多少卡、该找谁批预算都答不上来。
一、从单机到集群:六处真实差异
| 项 | 单机 | 生产 | 不做会怎样 | 当前状态 |
|---|---|---|---|---|
| 角色拆分 | 一个容器内同时跑接口与解析 | 接口与解析 worker 分开扩 | 同镜像拆两个 Deployment 不会自动分角色,必须按官方启动方式确认,否则只是复制两份同样的进程 | 官方角色启动参数未确认前,清单不可照抄 |
| 状态外置 | 本地盘与内置队列 | 数据库主从、对象存储纠删码、消息队列集群、缓存副本 | 接口副本用本地盘存状态,一旦漂移就丢任务 | 设计目标 |
| 解析吞吐 | 单进程串行 | 独立 worker 池,按队列积压扩 | 文档批量导入时把接口一起拖死 | 设计目标 |
| 探针与停机 | 无 | 就绪、存活、启动三探针 + 优雅停机 + 反驱逐预算 | 滚动发布时新副本未就绪就接流量,直接 5xx | 设计目标 |
| 备份与恢复 | 手动 dump | 定期备份 + 恢复演练,RPO/RTO 实测 | 只备份不演练,等于没备份 | 未演练 |
| 网络与凭证 | 端口直连 | 网络策略 + 密钥托管注入 | 客户端可绕过网关直达管理面 | 设计目标 |
“当前状态”这一列不是装饰:它决定这一篇讲的是已发生的运维事实还是运维设计。混在一起写,就会出现“上线前必须先演练”和“演练已完成”同时出现在一份文档里的自相矛盾。
二、向量引擎:先过硬门槛,再谈偏好
混合检索要求同一个引擎同时支持向量近邻与全文(短语)检索。这条门槛很硬:官方口径下只有两类引擎满足——与检索引擎同厂的内置引擎,以及成熟搜索引擎;专用向量数据库不在候选内(其向量能力可用,全文与短语检索能力不支持,社区有开放需求但官方未实现)。
| 选择 | 适用 | 代价 |
|---|---|---|
| 与引擎同厂的内置引擎 | 部署链路短、混合检索为原生设计、协议宽松 | 集群运维资料少,容量见顶时外部经验少 |
| 成熟搜索引擎 | 团队本来就会运维、生态与排障手册齐全 | 版本兼容、内存画像、与引擎的分数口径需要重新校准 |
判断标准不是“哪个更强”,而是两条:团队能不能运维它;换过去之后所有检索阈值要不要重新定标。换引擎等于换分数分布,历史阈值一律作废(机制见 07 §三)。
已有专用向量库时的正确用法:文档检索仍走内置引擎或搜索引擎,向量库留给记忆层——记忆检索只要求向量近邻,不要求全文匹配,两者不是一回事,不要因为“公司有现成的库”就把主链路塞进去。
三、容量怎么算:给公式,不给假数字
三项口径先定,再谈数值:
- 解析吞吐:按文档大小分档,承诺“九成任务在多少分钟内完成”,不用平均页数/秒——平均值会掩盖长尾;
- 检索延迟:分段看,且明确是否含生成;
- 并发:按在途连接数计,长连接问答下按每秒请求数限流等于没限。
3.1 并发与排队
| 量 | 关系 | 用途 |
|---|---|---|
| 在途并发 L | L = λ · W(到达率 × 平均驻留时间) | 网关按 L 限流,不按 λ |
| 队列利用率 ρ | ρ = λ_s · s̄ / (c · μ)(解析任务到达率 × 平均服务时长 ÷(worker 数 × 单 worker 处理能力)) | ρ 超过约 0.7 才可能承诺排队类 P90,超了先扩再说 |
| 排空时间 | B · s̄ / (c · μ − λ_s)(积压量 ÷ 净处理速率) | 批量导入后多久“全都生效”,这条业务最关心 |
| 所需 worker 数 | c ≥ N_d · s_pg / (η · T_日)(日均文档数 × 每页耗时 ÷(有效利用率 × 日可用时长)) | 扩容量的申请依据 |
3.2 时延分解:P95 不可加
一次查询的时延由鉴权、两路召回(并行)、融合、重排、生成串成:
T_q = T_auth + max(T_vec, T_bm25) + T_merge + k_r · t_ce + T_gen
要提醒一句:各段的 P95 相加只是上界,不是整体的 P95。承诺值要用同一请求标识把各段耗时分位对齐(直方图叠加或实测),否则很容易签出一个没人达得到的数。k_r 是重排候选条数、t_ce 是单次交叉编码耗时——这两个乘积就是“重排默认开”的真实代价。
3.3 资源与存储
| 项 | 关系 | 说明 |
|---|---|---|
| 每查询 GPU 秒 | g_q = k_r · L_ce / θ_ce(候选数 × 编码长度 ÷ 吞吐) | 重排与生成分开记 |
| 所需卡数 | ⌈Q_年 · g_q / (8760 · u)⌉ · r_ha(利用率 × 高可用副本系数) | u 取保守值,别拿单卡跑分当产能 |
| 向量内存 | C · dim · 4 · (1 + α) · 副本数(片段数 × 维度 × 字节 × 索引开销) | 换维度即换内存画像,不是等比替换 |
| 审计存储 | D = Q_d · s_rec · (1 + β) · m(日事件数 × 单条大小 × 索引膨胀 × 保留月数) | 只追加且不走采样,是唯一会稳定线性涨的一块,拒绝事件同样计入 |
| 全量重建 | T_rebuild = C / (w_emb · b_emb)(片段数 ÷(并发 × 单并发吞吐)),成本 = C · e_emb + 双份索引 + 窗口内双写 | 换 embedding 前必须先算这个数 |
演示代入(方法示例,不是实测值):年查询 Q = 1×10⁶、每查询重排 g_q = 0.02 GPU·秒、利用率 u = 0.5、高可用副本 3 → 约需 0.46 卡·年 × 3 = 至少 2 卡。这条推导可复核、可替换参数,比“经验值”三个字更能支撑一次扩容申请。
四、自建还是用托管:先算临界量
成本构成清单(托管按单价含走的部分,自建全部要自己养):解析(按页耗 CPU/GPU)、embedding(入库 + 每查询 + 重建)、重排(常驻资源池)、生成、向量与全文索引内存、对象存储与备份、审计只追加存储、密钥与网关等配套组件自身的 HA、运维人力(回归七项 × 每次升级 + 季度演练 + 值班)、内容治理人力(三角色不可合并、复审及时率)、转人工与隔离区判定量。
判定用可核实变量,不写绝对金额:
- 自建单位变动成本 c_q(每查询)= 生成 + 重排 + embedding + 网关与存储分摊;固定成本 F = 满足 HA 与峰值的最小单元年成本;
- 托管单价 π_q;年查询量 Q;
- 划算条件:π_q > c_q 且 Q ≥ Q* = F / (π_q − c_q)。若 π_q ≤ c_q(大规模 API 单价低于自建单位成本),自建在任何规模都是亏,扩量只会放大亏损;
- 配额是另一条硬线:托管侧有限流(分钟级请求配额与检索类更低的子配额),峰值并发落在配额内、且没有数据不出域要求时,自建省不了钱,只是多养一支队伍。
自建不是可选项而是只有两条理由:密级不允许出域;并发超出配额且议价无效。其余情况优先考虑托管。还有一笔常被漏的账:托管价格里已折进去的高可用、备份、探针、任务重投、故障语义、升级回归这六件事,在自建侧是一支常备队(见第一节)。
五、故障语义与降级
| 组件故障 | 必须不丢 | 允许丢 |
|---|---|---|
| 接口副本 | 元数据、原文对象、调用与计量事件 | 无 |
| 向量节点 | 已入库内容(可重建恢复) | 缓存、正在进行的一次合并 |
| 队列 / worker | 任务定义(可重投) | 排队中的一次尝试 |
| 缓存 | 权威数据 | 缓存本身 |
| 计量链路 | 原始调用事件 | 一段窗口内的实时看板 |
| Knowledge Broker / 权限源 | 判定链不得“默认放行”:拿不到快照一律拒绝(fail-closed),并记拒绝事件 | 缓存命中(版本失效后不得复用) |
| 审计存储 | 审计事件(宁可阻塞放行也不丢事件) | 实时看板 |
一句话:缓存与排队任务可以丢,计量与审计事件不能丢——它们是唯一能证明系统真实做过什么的记录。fail-closed 的代价也要如实告诉业务:权限源抖动会放大成全员不可用,所以恢复顺序、提示语和演练都要提前定(验证方式见 06 第 10 组)。
六、换版本与换模型
引擎版本升级:镜像固定到摘要,不用浮动标签;升级前锁版本清单,升级后按七项重跑——评测集配对跑、跨租户与文档权限负向用例、删除传播验证、引用回链抽检、工具面清单与传输、并发与故障恢复演练、密钥扫描。任一项不过即回滚,回滚方式是切回原摘要而不是热改配置。默认参数一变,历史证据全部作废,这比代码变更更危险。
换 embedding 模型:旧向量与新向量不在同一语义空间,分数不可比(07 §三)。三条路径按代价选:
| 路径 | 做法 | 适用 |
|---|---|---|
| 全量重建 | 双写新索引,按租户灰度,切标签回滚 | 旧域体量小或已明显退化 |
| 新库新模型 + 旧库冻结(推荐默认) | embedding 在数据集级绑定,新域用新模型建库,旧库不动、随文档改版自然迁移 | 大多数场景,避开一次性全量成本与降级窗口 |
| 不换 | 旧模型继续服役至生命周期结束 | 换模型收益未证实时 |
切换判据不是日历时间,而是“新索引在评测集各桶不低于旧索引”——分数不可比时,只有排序类指标与端到端答案质量可比。重建窗口要如实给量:用 §3.3 的 T_rebuild 算时长、用双份索引与双份 embedding 算成本,并明确这期间“新数据只进新索引、检索只命中部分内容”的降级范围。
七、看什么指标
| 指标 | 为什么单独看它 |
|---|---|
| 入库成功率与失败分类 | 只有分类才能区分文档问题与依赖抖动,重试策略完全不同 |
| 解析队列积压与排空时长 | 用户体感的“传上去却没生效”就是这一项 |
| 检索空返回率 | 突然上升通常是解析或索引坏了,而不是用户问得偏 |
| 引用缺失率 | 有答案没引用,等于不可采信,比慢更严重 |
| 越权拒绝数与真实探测占比 | 一直为零说明没有人在试,不代表没有洞(见 06 两级证据) |
| 累计取回量 Top 主体与文档 | 只读权限被当导出通道用的唯一可见信号(见 03) |
| 场景单位成本 c_q | 04/09 承诺的“值不值”视图,没有这条就是空头票 |
| P95 检索延迟 / 首字延迟 | 分段看才知道该扩引擎还是该扩网关 |
| 计量与审计落库延迟、缺口 | 断了之后成本与验收都失去依据 |
指标与事件分开存:指标进时序库供告警,事件与审计进可查询存储并按保留期归档,两边用同一个请求标识串联。“审计不走采样”是这条线的硬规则——采样对性能指标合理,对追责不合理。
八、日常动作清单
- 每日:对账三判据(版本、工具清单哈希、端点可达),队列积压与失败分类,删除任务是否停在中间态。
- 每周:越权拒绝抽样复核,隔离区待人工量,容量水位与 c_q 波动。
- 每次变更:变更清单留档,回归结论写“通过 / 不通过 / 未覆盖”三态,不许用“未覆盖”冒充“通过”。
- 每季度:恢复演练一次并记录实测 RPO/RTO;权限映射表抽审;临时授权与永久授权占比复查。
九、边界
- 本栏给部署差异、测算方法与成本判定式,不给容量结论:没有压测与演练数据前写出来的数字都是编的。
- 生产就绪依赖组件高可用与故障演练完成,单机 Docker 形态只用于功能验证,不能外推为可用性与容量证据。
- 与托管平台的对比只到能力面与经济性判定式,不比性能;要比也得在同一压测口径下重跑。
- 第一节的 SLO 与第五节的故障语义目前是口径与实测计划,不是实绩。