ServiceFlow
我围绕这个 FastAPI 订单与工单 API,排查了报表查询缓慢、支付失败处理和订单金额不一致的问题。通过接口响应、请求日志与数据库记录定位原因,再用回归测试验证修改。
公开仓库 · 本地生成数据 · 无在线演示
PythonFastAPISQLitepytest应用支持
本地验证结果
服务层聚合耗时
90.26 ms → 2.11 ms
本次本地测量为 42.8 倍 · 预热 1 次,计时 5 次取中位数
生成的 SQLite 测试数据
500 名客户 / 2,000 笔订单
5,966 条订单明细
回归测试
28 项 pytest 测试
同一套测试在正常和故障模式下均通过
排查的问题
这些问题是在生成数据上模拟的。每份排查记录都附有复现步骤、相关代码和测试,便于核对处理过程。
- 发现的问题
- 报表返回 HTTP 200 和预期的 500 行,但逐客户查询的 N+1 循环,加上缺少客户索引,拖慢了查询。
- 修改方式
- 为客户字段添加索引,并用一条 JOIN … GROUP BY 查询替换逐客户循环。
- 发现的问题
- 支付客户端未设置超时,扣款失败后订单仍被标记为 paid。
- 修改方式
- 设置 3 秒客户端超时,成功扣款后才标记订单为 paid;失败时返回统一的 502 错误,不暴露内部信息,也不自动重试。
- 发现的问题
- 接口接受了数量为负数或未填写价格的明细,并保存了错误的订单总额。
- 修改方式
- 保存订单前检查数量和价格:qty 必须至少为 1,价格必填且不能为负数。无效请求不会新增订单。
订单报表为什么慢
- 先确认报表仍返回 HTTP 200 和预期的 500 行,将问题范围缩小到查询耗时,而不是结果缺失。
- 检查服务代码后发现,它先查询一次客户列表,再为每名客户分别执行 SUM 和 COUNT,总共需要 1 + 2N 次数据库查询。故障配置下,orders.customer_id 也没有索引。
- 修复同时处理了这两个问题:为 orders.customer_id 添加索引,并改为一次分组聚合。回归测试核对计算结果,以及两种模式下索引是否存在。
索引检查:单个客户的 SUM 查询 SCAN orders
→ SEARCH orders USING INDEX ix_orders_customer_id
这里用 EXPLAIN QUERY PLAN 检查了一条单独的客户 SUM 查询,确认索引如何改变查询方式。完整 JOIN … GROUP BY 报表查询的执行计划另存于仓库中。
预热 1 次、计时 5 次后,服务函数耗时的中位数由 90.26 ms 降至 2.11 ms。测量在同一进程内完成,不包含 HTTP 请求开销。基准文件保留了每次测量值,换一台机器运行时耗时会有变化。
范围与限制
- 项目使用生成数据,在本地模拟故障。这些结果不代表生产事故处理、SLA 达成情况或值班经历。
- 测量和测试使用 SQLite。PostgreSQL 可以配置,但尚未验证。API 没有身份验证或角色权限控制。
- 支付流程需要本地模拟支付 API。测试检查了 httpx 超时参数和抛出 TimeoutException 时的处理,未测量完整请求链路的超时时长。如果增加支付重试,需要先设计幂等键。