AI 应用后端工程化:从原型到可交付系统 · 第 25 篇 · 第七章 · 生产化收束
从日志初始化、向量数据库替换、关系库索引和依赖兼容收束系统,建立持续维护的判断顺序。
功能逐渐齐全后,系统会出现两类信号:查询越来越慢,升级越来越怕。前者要求用日志与指标定位瓶颈,再选择数据库索引或存储调整;后者要求把框架版本差异隔离在边界,避免一次依赖更新席卷全部业务代码。
最后阶段不应该是“到处优化”,而是建立观察、假设、验证和回滚的维护循环。
先观察,再动结构
flowchart LR
Symptom[慢请求/兼容错误] --> Observe[日志、指标、Trace]
Observe --> Hypothesis[形成可证伪假设]
Hypothesis --> Change[索引/存储/适配修改]
Change --> Measure[压测与回归]
Measure -->|改善| Release[小范围发布]
Measure -->|无效| Revert[撤销修改]
Release --> Observe
日志初始化必须稳定。重复添加 Handler 会让每条日志打印多次,错误级别配置不当会让生产缺少关键信息。库代码不应随意修改根 Logger,应用入口统一设置格式、级别与输出目标。
数据库索引服务查询,不服务模型图
给所有外键和时间字段加索引不一定更快。索引占空间并拖慢写入,应根据真实查询条件、排序和选择性设计。常见会话列表查询是 WHERE account_id=? ORDER BY updated_at DESC,联合索引通常比两个独立索引更合适。
CREATE INDEX ix_conversation_account_updated
ON conversation (account_id, updated_at DESC);
EXPLAIN ANALYZE
SELECT id, title, updated_at
FROM conversation
WHERE account_id = 42
ORDER BY updated_at DESC
LIMIT 20;
必须查看执行计划与真实数据量。低选择性的状态字段单列索引可能几乎无效;分页使用大 offset 仍会扫描大量行,可以改用 (updated_at, id) 游标。
向量存储替换意味着数据迁移
从本地 FAISS 切换到独立向量数据库,不只是换一个客户端。需要定义集合 Schema、租户隔离、批量写入、过滤能力、备份与监控。嵌入维度、距离度量和模型版本必须匹配。
迁移可以双写或离线重建:先建立新索引,抽样比较召回,逐步切换读流量,保留回退窗口。只把配置指向新服务而不迁移数据,会得到一个健康但空的数据库。
独立服务提高并发与过滤能力,也增加网络故障和运维成本。小规模单机应用未必需要它,选择应由数据量、延迟、可用性和团队能力驱动。
兼容性修复要集中
框架升级常出现 model_dump 与 dict、新旧 Pydantic 导入路径等差异。如果每个实体各自判断版本,分支会不断复制。集中到序列化适配层,并用相同样本在支持版本上测试。
def serialize_entity(entity) -> dict:
dump = getattr(entity, "model_dump", None)
if callable(dump):
return dump(exclude_none=True)
legacy_dump = getattr(entity, "dict", None)
if callable(legacy_dump):
return legacy_dump(exclude_none=True)
raise TypeError(f"cannot serialize {type(entity).__name__}")
适配层是过渡设施,要记录最低支持版本和删除计划。更理想的是锁定依赖后一次性升级,但外部框架生态有时要求短期双版本兼容。
优化中最常见的误判
页面慢就加缓存,结果根因是 N+1 查询;检索慢就换向量库,结果大部分时间花在模型生成;CPU 高就增加机器,结果日志重复序列化超大响应。没有分阶段耗时,优化只能靠猜。
请求 trace 至少拆出数据库、向量检索、模型首 Token 和总生成时间。慢查询日志与执行计划定位关系库问题,向量服务指标观察召回延迟,模型用量记录成本。每种工具回答不同问题。
索引变更也要测写入代价。为消息表增加多个索引可能让高频流式持久化更慢。基准应包含读写混合和接近生产的数据分布。
长期维护需要负面知识
记录“不采用某方案”的原因同样重要:为何没有全量缓存,为何选择游标分页,为何暂不升级某框架。几个月后团队才能避免重复试错。架构决策记录不必长,只需背景、选择、替代方案和复查条件。
依赖升级使用小步变更:先更新适配层和测试,再升级锁文件,运行检索与会话回归,观察告警后扩大流量。一次同时更换模型 SDK、向量库和 ORM,会让故障无法归因。
从系列终点回看
最初的脚本只有 HTTP 与模型调用,后来出现数据库、文档管道、工具、认证、版本、工作流和多供应商。每新增一层,性能和兼容面都会扩大。维护不是最后清理,而是承认系统已经拥有生命周期。
练习:选择一个真实慢接口,记录数据库、外部服务和序列化耗时,提出一个可证伪假设;添加或调整索引后保存 EXPLAIN ANALYZE 前后结果。再用两种实体 API 运行序列化测试,确保适配输出一致。
最后五次提交对应四类维护动作
99e1e1c:调整日志初始化与级别,先改善观察入口。2b0fec2:接入 Weaviate,向量存储从本地能力走向独立服务。1353628:为多个模型增加索引,针对真实查询优化关系库。a2183f3:调整 Pydantic 导入路径,处理框架版本差异。06f4634:统一实体序列化方式,继续收束兼容边界。
顺序值得保留:先让系统更可观察,再改变存储和索引,最后处理依赖兼容。没有观察就无法判断优化是否有效,没有适配边界就无法安全吸收生态变化。这也是一个后端从原型走向长期可维护系统的最后一课。