(转)优化DRF的性能
看似简单直观的 Django REST Framework 及其嵌套序列化可能会大大降低你的 API 端的性能。你的服务器的其他部分的响应能力也会被某一个低效的 REST API 影响
问题的根源就是 「N+1 selects problem」;首先查询数据库一次得到表中的数据(例如,Customers),然后每个用户的其他字段又需要循环不止一次地查询数据库(例如 customer.country.Name)。使用 Django 的 ORM,很容易造成这个问题,而使用 DRF,同样会造成这个问题。
幸运的是,目前有修复 Django REST Framework 性能问题的解决方法,而且不需要对代码进行重大重组。它只是需要使用未充分利用的 select_related 和 prefetch_related 方法来执行所谓的「预加载」。
为什么 Django REST Framework 那么容易造成这个问题
当你建立一个 DRF 视图时,你经常需要从多个相关表中返回相应的数据。写这样的功能是很简单的,DRF文档中有详细的介绍。不过不幸的是,只要你在序列化中使用嵌套关系,你就在拿你的性能开玩笑,像很多的性能问题一样,它往往只出现有大型数据集的真实生产环境中。
这种情况发生就是因为 Django 的 ORM 是惰性的,它只取出当前查询所需响应最小的数据。它不知道你是否有成百上千的相同或相似的数据也需要取出来。
况且如今,当我们谈到数据库型网站时,一般情况下,最重要的响应指标就是数据库的访问次数。
在 DRF 视图中,我们每次序列化有嵌套关系的数据时都会出现问题,如下面的例子:
CustomerSerializer 函数里面是这么运行的:
- 获取所有的 customers (需要往返到数据库)
- 对于第一个返回的客户,获取他们的 orders (又需要去往返一趟数据库)
- 对于第二个返回的客户,获取他们的 orders (又需要去往返一趟数据库)
- 对于第三个返回的客户,获取他们的 orders (又需要去往返一趟数据库)
- 对于第四个返回的客户,获取他们的 orders (又需要去往返一趟数据库)
- 对于第五个返回的客户,获取他们的 orders (又需要去往返一趟数据库)
- 对于第六个返回的客户,获取他们的 orders (又需要去往返一趟数据库)
- 。。。。终于意识到,千万不要有更多的用户
解决 Django 「懒惰」的基本方法
现在我们解决这个问题的方法就是「预加载」。从本质上讲,就是你提前警告 Django ORM 你要一遍又一遍的告诉它同样无聊的指令。在上面的例子中,在 DRF 开始获取前很简单地加上这句话就搞定了:
当 DRF 调用上述相同序列化 customers 时,出现的是这种情况:
- 获取所有 customers(执行两个往返数据库操作,第一个是获取 customers,第二个获取相关 customers 的所有相关的 orders。)
- 对于第一个返回的 customers,获取其 order(不需要访问数据库,我们已经在上一步中获取了所需要的数据)
- 对于第二个返回的 customers,获取其 order (不需要访问数据库)
- 对于第三个返回的 customers,获取其 order (不需要访问数据库)
- 对于第四个返回的 customers,获取其 order (不需要访问数据库)
- 对于第五个返回的 customers,获取其 order (不需要访问数据库)
- 对于第六个返回的 customers,获取其 order (不需要访问数据库)
- 你又意识到,你可以有了很多 customers,已经不需要再继续等待去数据库。
其实 Django ORM 的「预备」是在第1步进行请求,它在本地高速缓存的数据能够提供步骤2+所要求的数据。与之前往返数据库相比从本地缓存数据中读取数据基本上是瞬时的,所以我们在有很多 customers 时就获得了巨大的性能加速。
解决 Django REST Framework 性能问题的标准化模式
我们已经确定了一个优化 Django REST Framework 性能问题的通用模式,那就是每当序列化查询嵌套字段时,我们就添加一个新的 @staticmethod 名叫 setup_eager_loading,像这样:
这样,不管哪里要用到这个序列化,都只需在调用序列化前简单调用 setup_eager_loading ,就像这样:
或者,如果你有一个 APIView 或 ViewSet,你可以在 get_queryset 方法里调用 setup_eager_loading:
那么怎样编写 setup_eager_loading
想要解决 Django 的性能问题,最困难的部分就是要熟悉 select_related ,我们将详细介绍它们在 Django ORM 和 Django REST Framework 中怎样使用。
- select_related:Django ORM 最简单的预加载工具,对于所有一对一或多对一的数据关系,你都需要从同一个父对象获取数据,如客户的公司名称。这个会被翻译成 SQL 的 join 操作,这样父对象的数据就和子对象的数据一起取回来了。(参见官方文档)
- prefetch_related:对于更复杂的关系,即每个结果有多行(例如 many=True ),像多对一或多对多的数据关系,比如上述客户的订单,这转化一个二级 SQL 查询,通常有很长的 WHERE … IN ,从中只选择相关的行。(参见官方文档)
- Prefetch:用于复杂 prefetch_related 查询,例如过滤子集。它也可以嵌套setup_eager_loading 进行调用。 (参见官方文档)