加载中...
  • 当系统开始变慢或变脆:日志、索引、向量库与依赖兼容loading

    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_dumpdict、新旧 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:统一实体序列化方式,继续收束兼容边界。

    顺序值得保留:先让系统更可观察,再改变存储和索引,最后处理依赖兼容。没有观察就无法判断优化是否有效,没有适配边界就无法安全吸收生态变化。这也是一个后端从原型走向长期可维护系统的最后一课。

    本文目录
    本文目录