Python Django+;Django REST框架&x2B;PostgreSQL查询和序列化非常慢,但不是一个;N+;1“;案例

Python Django+;Django REST框架&x2B;PostgreSQL查询和序列化非常慢,但不是一个;N+;1“;案例,python,django,postgresql,django-rest-framework,Python,Django,Postgresql,Django Rest Framework,我喜欢Django+DRF组合,我已经使用它们很长时间了,但这个问题困扰了我一段时间。问题是当存在多个或一个多个关系和嵌套对象时,查询+序列化会花费大量时间。StackOverflow中有很多类似的问题,通常问题是某种形式的“N+1”问题(或未解决) e、 g 另外,建议不要一次加载那么多对象。。可能适用于这里,但我需要一次完成所有项目 在这种情况下,处理查询也是问题的一大部分,但是查询并不太多,而且查询本身也很快。我正在使用prefetch\u related来限制查询的数量,

我喜欢Django+DRF组合,我已经使用它们很长时间了,但这个问题困扰了我一段时间。问题是当存在多个或一个多个关系和嵌套对象时,查询+序列化会花费大量时间。StackOverflow中有很多类似的问题,通常问题是某种形式的“N+1”问题(或未解决)

e、 g

另外,建议不要一次加载那么多对象。。可能适用于这里,但我需要一次完成所有项目

在这种情况下,处理查询也是问题的一大部分,但是查询并不太多,而且查询本身也很快。我正在使用
prefetch\u related
来限制查询的数量,我从DB查询中看到的一切看起来都很正常..ish(?)。我为每个
prefetch\u相关的
property+序列化对象的原始查询得到一个查询。
prefetch\u相关的
查询中包含了很多ID,但我想这是不可避免的——ID的数量与原始项目的数量一样多

当按这里所示进行分析时,我得到的结果是,DB查找占用了大部分时间,而序列化也不会太快。例如,我在本地PostgreSQL数据库中为一个“项目”设置了大约12k个“项”。所有“项目”都有1-5个“事件”,大多数“项目”也有1-2个“照片”。在我的笔记本电脑上获取和序列化这些数据大约需要22秒。我正在使用AWS EC2+RDS进行部署,部署的时间也差不多。在较大的“项”集合上,序列化时间的增加超过了DB查找时间,但DB查找总是占用大部分时间。有了40k项,您将开始达到1分钟的执行时间,并且Nginx和堆栈的其他部分会出现不同的超时

带有12k项的示例(下面的模型、序列化程序和查询)

如果我不考虑照片和事件,结果是

Database lookup               | 1.2447s
Serialization                 | 3.9668s
Django request/response       | 0.2435s
API view                      | 0.1320s
Response rendering            | 0.8495s
因此,相关字段占用了大部分时间(many=True)。我用于测试的评测是在序列化之前从queryset中生成
list
。因此,延迟查询在序列化之前执行。如果我不这样做,它不会改变总体结果,但是在序列化时,DB查找会在大约相同的时间内进行评估

现在我的问题是,如果手动执行,所有完成的查询都很快(django.db.backends logs报告的时间大致相同)。它们列在下面。所以,我相信SQL查询速度很快,但从Django的角度来看,DB查询速度非常慢。我错过了什么?或者我应该如何继续调查?现在感觉好像需要Django认真地将SQL查询结果转换为Django模型实例。这意味着我的模型有问题,对吗

帮助调试此类案例的一般问题:

  • 获取和序列化10-50k个相关项为列表的对象(many=True)的合理时间是多少?即使数据库查找会更好,6秒的序列化听起来也是错误的,不是吗
  • SQL查询完成和DRF序列化开始之间会发生什么?有什么可以改进的吗
  • 最好不要从DB获取所有内容,而是使用
    values()
    。如果这样做了,是否仍有办法利用DRF序列化程序?我非常喜欢它们
  • 最后,我可以转向缓存,但我认为如果操作正确,处理<100k对象不应该是一个问题


    设置:Python 2.7.13、Django 1.10.7、DRF 3.6.3

    模型、视图和序列化程序的简化版本:

    class List(models.Model):
        ... CharFields, DateTimeFields, ForeignKeys etc. ...
    
    class Item(models.Model):
        list = models.ForeignKey(List, on_delete=models.CASCADE, db_index=True, null=True, related_name='items')
        deleted_at = models.DateTimeField(db_index=True, blank=True, null=True, default=None)
        created_by = models.ForeignKey(User, blank=False)
        project = models.ForeignKey('projects.Project', on_delete=models.CASCADE)
        ... other CharFields, DateTimeFields, ForeignKeys etc. ...
    
    class Event(models.Model):
        item = models.ForeignKey(Item, on_delete=models.CASCADE, db_index=True, null=True, related_name='events')
        created_by = models.ForeignKey(User, blank=False)
        deleted_at = models.DateTimeField(db_index=True, blank=True, null=True, default=None)
        ... other CharFields, DateTimeFields, ForeignKeys etc. ...
    
    class Photo(models.Model):
        item = models.ForeignKey(Item, on_delete=models.CASCADE, db_index=True, null=True, related_name='photos')
        created_by = models.ForeignKey(User, blank=False)
        deleted_at = models.DateTimeField(db_index=True, blank=True, null=True, default=None)
        ... other CharFields, DateTimeFields, ForeignKeys etc. ...
    
    
    class PhotoSerializer(serializers.ModelSerializer):
        ... other CharFields, DateTimeFields, ForeignKeys etc. ...
    
    class EventSerializer(serializers.ModelSerializer):
        createdBy = PrimaryKeyRelatedStringField(source='created_by', read_only=True)
        createdByFullName = serializers.CharField(source='created_by.get_full_name', read_only=True)
        ... other CharFields, DateTimeFields, ForeignKeys etc. ...
    
    class ItemSerializer(serializers.ModelSerializer):
        listName = serializers.CharField(source='list.name', read_only=True)
        createdBy = PrimaryKeyRelatedStringField(source='created_by', read_only=True)
        createdByFullName = serializers.CharField(source='created_by.get_full_name', read_only=True)
        photos = PhotoSerializer(many=True, read_only=True)
        events = EventSerializer(many=True, required=False, allow_null=True, queryset=Event.objects.all())
        ... other fields ...
    
    
    class ItemListAPIView(ListAPIView):
        model = Item
        serializer_class = ItemSerializer
    
        def get_queryset(self):
            return Item.objects.all().filter(project_id=...).filter(deleted_at__isnull=True).prefetch_related(
                'created_by',           # ID of user who created item
                'photos',               # Photo properties
                'event__created_by',    # Full name of the person who created the event
                'list',                 # Name of the related list
            )
    
    来自具有14s DB查找结果的测试的查询示例:

    django.db.backends: (0.196) SELECT "todo_item"."version", ... everything ... FROM "todo_item" WHERE ("todo_item"."project_id" = 1 AND "todo_item"."deleted_at" IS NULL) ORDER BY "todo_item"."created_at" DESC;
    django.db.backends: (0.001) SELECT "auth_user"."id", ... everything ... FROM "auth_user" WHERE "auth_user"."id" IN (1, 2, ... some IDs ...);
    django.db.backends: (0.148) SELECT "photos_photo"."version", ... everything ... FROM "photos_photo" WHERE ("photos_photo"."deleted_at" IS NULL AND "photos_photo"."item_id" IN (1, 2, ... lots of IDs... N)) ORDER BY "photos_photo"."created_at" DESC;
    django.db.backends: (0.078) SELECT "events_event"."created_at", ... everything ... FROM "events_event" WHERE ("events_event"."deleted_at" IS NULL AND "events_event"."item_id" IN (1, 2, ... lots of IDs... N)) ORDER BY "events_event"."created_at" DESC, "events_event"."created_at" DESC; 
    django.db.backends: (0.157) SELECT "todo_list"."created_at", ... FROM "todo_list" WHERE "todo_list"."id" IN (1, 2, ... lots of IDs... N)
    
    更新1 @pozs询问了计时测量:计时是通过
    \timing
    解释分析进行本地测试的。这里没有太详细的测量或调查,只是查询在一起不到1秒。与django.db.backends日志的建议大致相同。这些具有14秒DB查找时间的测试也在本地数据库中进行。套接字传输时间会影响性能,这是事实。物体是胖的,但不是那么胖。总共有大约15个字段用于“项目”和<10个字段用于“事件”和“照片”。这会生成大量的数据,但仍然生成足够的数据供本地传输>10秒听起来不太正确。不过,我可能错了。我会做更多的测试。谢谢您的回答@pozs

    更新2 为了检查传输的数据量是否减慢了速度,我使用psql从我的一个EC2实例运行相同的查询,使用相同的DB数据到RDS DB,并将数据写入文件。SQL查询执行时间通常比我在本地设置中得到的时间要短。例如,对于项目,在EC2+RDS设置中,本地耗时196毫秒的SELECT查询耗时65毫秒。数据传输加上文件写入,总共是131毫秒。文件写入是额外的,但我想确保我得到了我期望的所有数据

    我使用来自like的命令测试了EC2实例的计时:

    time psql -f fat-query.psql --host=rds_host --port=5432 --username=user --dbname=dbname > output.txt
    

    因此,在EC2+RDS设置中,SQL查询速度更快(有数据传输),但整个数据查询+序列化的速度大约与我的本地设置中一样慢。尽管有很多数据需要移动,但我仍然认为“问题”发生在“Django接收SQL查询响应”和“DRF开始序列化”之间的某个地方。

    这似乎不会像我希望的那样得到解决。由于仍然不知道为什么与预取相关的Python连接需要如此长的时间,我用单独的查询集(尽管是相同的SQL查询)和适合此用例的简单连接取代了与预取相关的
    prefetch

    替换了GET“Items”中与Django
    prefetch_相关的
    JOIN和DRF序列化:

  • 没有<
    time psql -f fat-query.psql --host=rds_host --port=5432 --username=user --dbname=dbname > output.txt