Django REST框架中的ModelSerializer非常缓慢

22

我正在使用Django REST框架编写API,昨天我想测试一下它在处理大数据时的工作方式。我找到了Tom Christie撰写的这篇教程,学会了如何对请求进行性能分析,发现对于10000个用户,我的请求需要2分20秒才能完成。

大部分时间都花费在对象序列化上(约65%),因此我想知道该怎么做才能加快速度呢?

我的用户模型实际上是扩展了默认的Django模型,所以使用.values()不能解决问题,因为我没有获取嵌套模型(即使它快得多)。

非常感谢任何帮助 :)

编辑

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

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

编辑#2

模型

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'))

序列化器

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',
        )

视图

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)

我们需要看到你的模型和序列化器,以便查看可能存在的缓慢问题。 - Kevin Brown-Silva
好的,所以我重新启动了我的服务器(因为它抛出了一些异常),并且还删除了一些不必要的字段,现在它运行得更好了(大约32秒)。这个时间对你来说可以接受吗,Kevin? - AdelaN
我不知道你在序列化器中做了什么,所以这可能是非常好的(如果你使用了SerializerMethodField或处理数据的属性),也可能是非常糟糕的(如果你只是从数据库中提取东西)。 - Kevin Brown-Silva
我添加了相关的代码片段。没有进行任何数据处理,只是计算一些ETag。我今天会重新运行测试,看看是否有相同的时间。 - AdelaN
转到Flask,你会很高兴的。 - JTW
3个回答

15
几乎总是性能问题来自N+1查询。这通常是因为您正在引用相关模型,并且为了获取信息,每个对象的每个关系都会生成一个单独的查询。您可以通过在get_queryset方法中使用.select_related.prefetch_related来改善此情况,如我在其他Stack Overflow答案中所述Django提供的数据库优化建议也适用于Django REST框架,因此我建议您也研究一下这些建议。
您之所以在序列化期间看到性能问题,是因为那时Django会向数据库发出查询。

3
我已经在使用.select_related(),所以不是这个问题。在发布问题之前,我确实查看了您的答案 :) 它有些帮助,但仍然非常慢。谢谢。 - AdelaN
1
似乎存在这样的情况,即使使用没有参数的select_related()(在生产中不是一个好主意,但对于调试此问题很有用),仍然会导致N+1个查询。 - GDorn
1
请记住,select_related() 仅适用于 ForeignKey 字段。如果您有 ManyToMany 字段并希望减少查询数量,则需要使用 prefetch_related。 - GDorn
1
请注意,prefetch_related 必须适当排序才能正常工作。如果没有正确排序,Django Debug Toolbar 将显示重复的查询。但是,这可能不是 N+1 的情况。我在这里是因为 SQL 花费了 1.5 秒,但仍然有额外的 5 秒 CPU 时间。 - dhill

13

你说过,ModelSerializers很慢。这里有更多关于为什么会出现这种情况以及如何提高速度的信息:https://hakibenita.com/django-rest-framework-slow

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

8

我知道这篇文章有些旧了,你可能已经解决了自己的问题...但是对于其他看到这篇文章的人...

问题出在你正在进行无意义的

select_related()

如果没有参数,这对你的查询几乎没有任何帮助。你真正需要做的是

prefetch_related('user_profile')

不深入细节,select_related 用于 "to one" 关系,而 prefetch_related 用于 "to many" 关系。 在您的情况下,您正在使用一个反向关系,这是一个 "to many" 查询。
您的另一个问题是您没有正确使用反向关系。将您的序列化器中的 get_queryset() 更改为以下内容,我认为您将获得所需的结果:
def get_queryset(self):
    return UserProfile.objects.prefetch_related('user_profile').all()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接