ModelSerializer 在 Django REST 框架中非常慢

Posted

技术标签:

【中文标题】ModelSerializer 在 Django REST 框架中非常慢【英文标题】:ModelSerializer is extremely slow in Django REST framework 【发布时间】:2015-05-15 00:41:31 【问题描述】:

我正在为我的 API 使用 Django REST 框架,昨天我想看看它是如何处理大数据的。我找到了 this tutorial 关于如何分析您的请求(由 Tom Christie 撰写),我发现对于 10.000 个用户,我的请求花费了惊人的 2 分 20 分钟。

大部分时间都花在序列化对象上(大约 65%),所以我想知道我可以做些什么来加快速度?

我的用户模型实际上是在扩展默认的 django 模型,所以使用 .values() 不起作用,因为我也没有得到嵌套模型(即使它快很多)。

任何帮助将不胜感激:)

编辑

在检索我的查询集时,我已经在使用 .select_related(),它缩短了我的时间,但只有几秒钟。总查询数为 10,所以我的问题不在于数据库访问。

另外,我正在使用 .defer(),以避免在此请求中不需要的字段。这也提供了一个小的改进,但还不够。

编辑#2

Models

from django.contrib.auth.models import User
from django.db.models import OneToOneField
from django.db.models import ForeignKey

from userena.models import UserenaLanguageBaseProfile
from django_extensions.db.fields import CreationDateTimeField
from django_extensions.db.fields import ModificationDateTimeField

from mycompany.models import MyCompany


class UserProfile(UserenaLanguageBaseProfile):
    user = OneToOneField(User, related_name='user_profile')
    company = ForeignKey(MyCompany)
    created = CreationDateTimeField(_('created'))
    modified = ModificationDateTimeField(_('modified'))

Serializers

from django.contrib.auth.models import User

from rest_framework import serializers

from accounts.models import UserProfile


class UserSerializer(serializers.ModelSerializer):
    last_login = serializers.ReadOnlyField()
    date_joined = serializers.ReadOnlyField()
    is_active = serializers.ReadOnlyField()

    class Meta:
        model = User
        fields = (
            'id',
            'last_login',
            'username',
            'first_name',
            'last_name',
            'email',
            'is_active',
            'date_joined',
        )


class UserProfileSerializer(serializers.ModelSerializer):
    user = UserSerializer()

    class Meta:
        model = UserProfile
        fields = (
            'id',
            'user',
            'mugshot',
            'language',
        )

Views

class UserProfileList(generics.GenericAPIView,
                      mixins.ListModelMixin,
                      mixins.CreateModelMixin):

    serializer_class = UserProfileSerializer
    permission_classes = (UserPermissions, )

    def get_queryset(self):
        company = self.request.user.user_profile.company
        return UserProfile.objects.select_related().filter(company=company)

    @etag(etag_func=UserListKeyConstructor())
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

【问题讨论】:

我们将需要查看您的模型和序列化程序,以了解可能出现的缓慢情况。 好的,所以我重新启动了我的服务器(因为它抛出了一些异常)并删除了一些不必要的字段,现在它运行得更好了(大约 32 秒)。凯文,这似乎是你可以接受的时间吗? 我不知道你在序列化程序中做了什么,所以这可能非常好(如果你使用 SerializerMethodField 或处理数据的属性)或非常糟糕(如果你是只是从数据库中提取东西)。 我添加了相关代码sn-ps。不做任何数据处理,只是计算一些 ETag。我今天将重新运行测试,看看我是否有相同的时间。 【参考方案1】:

性能问题几乎总是来自 N+1 个查询。这通常是因为您正在引用相关模型,并且每个对象的每个关系都会生成一个查询来获取信息。您可以通过在您的get_queryset 方法as described in my other Stack Overflow answer 中使用.select_related.prefetch_related 来改进这一点。

Django provides on database optimization 的相同提示也适用于 Django REST 框架,因此我建议您也研究一下。

您在序列化过程中看到性能问题的原因是 Django 对数据库进行查询。

【讨论】:

我已经在使用 .select_related(),所以不是这样。在发布问题之前,我确实查看了您的答案 :) 它有所帮助,但仍然很慢。谢谢。 在某些情况下,即使使用不带参数的 select_related()(对于生产来说不是一个好主意,但对于调试这个问题很有用),仍然会导致 N+1 个查询。 请记住 select_related() 用于 ForeignKey 字段。如果您有 ManyToMany 字段并希望减少查询次数,则需要使用 prefetch_related 。 请注意,prefetch_related 必须正确排序才能工作。如果没有,Django 调试工具栏将显示重复的查询。但是,N+1 的情况可能并非如此。我在这里是因为 SQL 需要 1.5s,但还有 5s 额外的 CPU 时间。【参考方案2】:

我知道这已经过时了,你可能已经解决了你的问题......但是对于其他任何进入这篇文章的人......

问题是你瞎了眼

select_related()

没有参数,这对您的查询绝对没有任何作用。你真正需要做的是

prefetch_related('user_profile')

在不深入细节的情况下,select_related 用于“对一”关系,prefetch_related 用于“对多”关系。在您的情况下,您使用的是 反向关系,这是一个“多对数”查询。

您的另一个问题是您没有正确使用反向关系。将序列化程序中的 get_queryset() 更改为此,我认为您将拥有所需的内容:

def get_queryset(self):
    return UserProfile.objects.prefetch_related('user_profile').all()

【讨论】:

【参考方案3】:

ModelSerializer 很慢,你自己说的。以下是有关它发生的原因以及如何加快速度的更多信息:https://hakibenita.com/django-rest-framework-slow

在性能关键端点中,使用“常规”序列化程序,或者根本不使用。 不用于写入或验证的序列化器字段应该是只读的。

【讨论】:

以上是关于ModelSerializer 在 Django REST 框架中非常慢的主要内容,如果未能解决你的问题,请参考以下文章

Django Rest Framework - 如何在 ModelSerializer 中添加自定义字段

django drf 深入ModelSerializer

如何在 django rest 框架 ModelSerializer 中覆盖模型字段验证

ModelSerializer 在 Django REST 框架中非常慢

如何保存具有关系的modelSerializer? - django

Django Rest Frame API:ModelSerializer 中的附加字段