在 Django 编程中实践 Work at the appropriate level
记录一次 Django 性能优化
起因
今天收到反馈,测试环境一个接口很慢,从 Chrome 显示的响应时间将近 10s。
遂开始优化。
经过
首先使用 Django debug tool 来获取性能量化指标。发现一个 limit=10, page=1
的接口,出发了 2000ms 的 query,同时 cpu time 消耗了 7000ms,有些离谱。
将 Django setting 文件里的 debug 设置为 False,此时请求接口速度稳定在 2s 左右。
确定为使用了 Django debug toolbar 消耗了大部分性能,因为设置了当 debug 为 True 时,使用 debug toolbar。
继续测试。
我的方法是给该 filter 中的封装的函数做 timing。使用装饰器实现测试函数时间。
def timethis(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
r = func(*args, **kwargs)
end = time.perf_counter()
print('handle {} items in {}.{} : {}'.format(r, func.__module__, func.__name__, end - start))
return r
return wrapper
根据打印信息得出,整体耗时 2s 多。显然很慢了。第一反应是考虑到因为有一些外键查询导致 n+1
,采用 queryset 的 select_related
方法对外键查询进行优化。效果不明显。
review 代码,希望能找出具体耗时的代码逻辑。同样使用 start end 进行 timing,最终定位到下面这行代码:
product_list = [item.product.id for item in queryset]
这行逻辑意图想要获取一个查询集中所有 product 外键的 id,于是写了一段列表推导。做了个 count 显示 list 长度 1w 多,显然问题出在这里。根据 Django 定义,此处触发大量查询,并 hit 到了数据库。
Work at the appropriate level
根据 Django 优化文档提示。不应该在服务层去做 product id 集合的查询,可以直接调用数据库方法来实现。
product_list = queryset.values('product_id')
对新逻辑进行 timing,实际消耗时间 0.02s。
优化效果从 2s 到 0.02s,只改了一行代码,效果显著。
本文参考
- Django 优化文档
- 封面摄于 2021 bw
作者:hayato
文章版权:本站所有文章版权依赖于 CC BY-NC-SA 3.0 Unported License
本文链接:https://blog.axis-studio.org/2021/09/15/在-Django-编程中实践-Work-at-the-appropriate-level/