【问题标题】:Django Rest Framework / Django performance issueDjango Rest Framework / Django 性能问题
【发布时间】:2013-02-01 07:31:18
【问题描述】:

我目前正在我的 EC2 小型实例服务器上部署带有 Django-Rest-Framework 的 Django,以为几个 Android 应用程序提供一组 API。

问题是我面临着一个严重的性能问题,我必须对其进行分析。我发现单个请求的大部分时间都花在了 DRF 的核心中。

很抱歉让这篇文章变得很长,但我认为我必须展示所有内容,以便我能够对正在发生的事情得到正确的答案。让我继续:

我的设置是 nginx/uwsgi。以下是我使用 upstart 运行 uwsgi 的方式:

description "pycms"
start on [2345]
stop on [06]

respawn

# start from virtualenv path
chdir /www/python/apps/pycms/

exec uwsgi -b 25000 --chdir=/www/python/apps/pycms --module=wsgi:application --env DJANGO_SETTINGS_MODULE=settings --socket=127.0.0.1:8081 --processes=5  --harakiri=20  --max-requests=5000  --vacuum --master --pidfile=/tmp/pycms-master.pid 

假设我请求以下 API:

http://IP_ADDRESS/api/nodes/mostviewed/9/

符合以下规则:

url(r'^nodes/mostviewed/(?P<category>\d+)/$', MostViewedNodesList.as_view(), name='mostviewed-nodes-list'),

这是基于类的视图:

class MostViewedNodesList(generics.ListAPIView):
    """
    API endpoint that lists featured nodes
    """
    model = ObjectStats
    serializer_class = NodeSerializer
    permission_classes = (permissions.AllowAny,)

    def get_queryset(self):
        if(self.kwargs.has_key('category')):
            category_id = self.kwargs.get('category')
            return ObjectStats.get_most_viewed(category_id)
        else:
            return []

序列化器类:

class NodeSerializer(serializers.ModelSerializer):
    images = ImageSerializer()
    favorite = ObjectField(source='is_favorite')
    rating = ObjectField(source='get_rating')
    meta = ObjectField(source='get_meta')
    url = ObjectField(source='get_absolute_url')
    channel_title = ObjectField(source='channel_title')

    class Meta:
        model = Node
        fields = ('id', 'title', 'body', 'images', 'parent', 'type', 'rating', 'meta', 'favorite', 'url', 'channel_title')

最后是类方法'get_most_viewed'(我知道使用类方法而不是管理器方法是错误的)

@classmethod
    def get_most_viewed(cls, category_id):
        return list(Node.objects.extra(
            where=['objects.id=content_api_objectviewsstats.node_id', 'content_api_objectviewsstats.term_id=%s'],
            params=[category_id],
            tables=['content_api_objectviewsstats'],
            order_by=['-content_api_objectviewsstats.num_views']
        ).prefetch_related('images', 'objectmeta_set').select_related('parent__parent'))

从这一切可以看出,这是一个正常的请求,被重定向到指定的视图,从 MySQL 获取数据,然后返回序列化结果。没有任何异常或任何复杂的处理。

执行:

ab -c 500 -n 5000 http://IP_HERE/api/nodes/mostviewed/9/

请注意,这是没有缓存的。经常记录以下内容:

HARAKIRI: --- uWSGI worker 5 (pid: 31015) WAS managing request /api/nodes/mostviewed/9/ since Sat Feb 16 13:07:21 2013 ---
DAMN ! worker 2 (pid: 31006) died, killed by signal 9 :( trying respawn ...
Respawned uWSGI worker 2 (new pid: 31040)

测试期间的平均负载上升到 ~ 5。这是 ab 结果:

Concurrency Level:      500
Time taken for tests:   251.810 seconds
Complete requests:      1969
Failed requests:        1771
   (Connect: 0, Receive: 0, Length: 1771, Exceptions: 0)
Write errors:           0
Non-2xx responses:      1967
Total transferred:      702612 bytes
HTML transferred:       396412 bytes
Requests per second:    7.82 [#/sec] (mean)
Time per request:       63943.511 [ms] (mean)
Time per request:       127.887 [ms] (mean, across all concurrent requests)
Transfer rate:          2.72 [Kbytes/sec] received

首先,每秒 7 个请求非常令人失望。 ~ 1700 由于超时错误而失败的请求也是因为我在这里面临的性能滞后。

说实话。我预计每秒约 60 - 70 个请求而无需缓存。我知道缓存加速了这个过程,但它也隐藏了我遇到的性能问题,这就是为什么我要在缓存之前解决这个问题。

然后我决定使用 django-profiling 在 vmware CentOS 机器上对此进行分析,通过在请求中添加 ?prof 来显示调用堆栈:

Instance wide RAM usage

Partition of a set of 373497 objects. Total size = 65340232 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0   2270   1  7609040  12   7609040  12 dict of django.db.models.sql.query.Query
     1  19503   5  6263400  10  13872440  21 dict (no owner)
     2  63952  17  5739672   9  19612112  30 str
     3  51413  14  5090344   8  24702456  38 list
     4  58435  16  4991160   8  29693616  45 tuple
     5  24518   7  4434112   7  34127728  52 unicode
     6   8517   2  2384760   4  36512488  56 dict of django.db.models.base.ModelState
     7   2270   1  2378960   4  38891448  60 dict of django.db.models.query.QuerySet
     8   2268   1  2376864   4  41268312  63 dict of 0x14d6920
     9   6998   2  2088304   3  43356616  66 django.utils.datastructures.SortedDict
<619 more rows. Type e.g. '_.more' to view.>



CPU Time for this request

         663425 function calls (618735 primitive calls) in 2.037 CPU seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.037    2.037 /usr/lib/python2.6/site-packages/django/views/generic/base.py:44(view)
        1    0.000    0.000    2.037    2.037 /usr/lib/python2.6/site-packages/django/views/decorators/csrf.py:76(wrapped_view)
        1    0.000    0.000    2.037    2.037 /usr/lib/python2.6/site-packages/rest_framework/views.py:359(dispatch)
        1    0.000    0.000    2.036    2.036 /usr/lib/python2.6/site-packages/rest_framework/generics.py:144(get)
        1    0.000    0.000    2.036    2.036 /usr/lib/python2.6/site-packages/rest_framework/mixins.py:46(list)
        1    0.000    0.000    2.029    2.029 apps/content_api/views.py:504(get_queryset)
        1    0.000    0.000    2.029    2.029 apps/objects_stats/models.py:11(get_most_viewed)
    23/21    0.000    0.000    2.028    0.097 /usr/lib/python2.6/site-packages/django/db/models/query.py:92(__iter__)
      4/2    0.003    0.001    2.028    1.014 /usr/lib/python2.6/site-packages/django/db/models/query.py:77(__len__)
        1    0.000    0.000    1.645    1.645 /usr/lib/python2.6/site-packages/django/db/models/query.py:568(_prefetch_related_objects)
        1    0.002    0.002    1.645    1.645 /usr/lib/python2.6/site-packages/django/db/models/query.py:1596(prefetch_related_objects)
        2    0.024    0.012    1.643    0.822 /usr/lib/python2.6/site-packages/django/db/models/query.py:1748(prefetch_one_level)
     2288    0.007    0.000    1.156    0.001 /usr/lib/python2.6/site-packages/django/db/models/manager.py:115(all)
     6252    0.019    0.000    0.762    0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:231(iterator)
     4544    0.025    0.000    0.727    0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:870(_clone)
     4544    0.109    0.000    0.694    0.000 /usr/lib/python2.6/site-packages/django/db/models/sql/query.py:235(clone)
     2270    0.004    0.000    0.619    0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:619(filter)
     2270    0.013    0.000    0.615    0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:633(_filter_or_exclude)
     1144    0.019    0.000    0.581    0.001 /usr/lib/python2.6/site-packages/django/db/models/fields/related.py:456(get_query_set)
     1144    0.019    0.000    0.568    0.000 /usr/lib/python2.6/site-packages/django/db/models/fields/related.py:560(get_query_set)
55917/18180    0.192    0.000    0.500    0.000 /usr/lib64/python2.6/copy.py:144(deepcopy)
     2270    0.003    0.000    0.401    0.000 /usr/lib/python2.6/site-packages/django/db/models/query.py:820(using)

如您所见,大部分时间都花在了 django 视图和 DRF 视图上。

如果我在这里做错了什么,有人可以指出吗?为什么请求这么慢? python / Django 可以扩展吗?我读到它确实可以,但是对于一个简单的数据库读取和渲染操作,例如我正在做的那个,我应该期望每秒多少个请求?

【问题讨论】:

    标签: python django profiling mysql-python django-rest-framework


    【解决方案1】:

    python/django 是否缩放

    Django 支持一些相当庞大的服务,例如 Disqus 和 Instagram,所以是的,它可以很好地扩展。

    然后我决定对此进行分析

    如您所见,几乎所有时间都花在 .get_most_viewed() 方法中, 所以这看起来像你遇到的问题。 (那里可能是错误的,但您的个人资料表明,有 2.028 时间中有 2.037 时间花在了那里,所以大约有 99.6% 的时间)

    我的 ORM 技能并不能准确地弄清楚您应该如何处理那个相当复杂的查询,并且无论如何都需要查看模型定义,但是您需要研究调试和简化该查询,而不是查看视图的其他部分。

    您可能希望使用 manage.py shell 进入 Django shell,以便您可以分析该特定查询以排除其余视图。

    也可以尝试在此处或在 Django IRC 频道或 django 邮件列表上获取有关如何简化查询的帮助(如果您特别询问该查询,您可能会更幸运,而不是此处使用更一般的 Django/REST 框架措辞问题,其中大部分问题实际上似乎与您看到的问题无关。

    希望这有助于为您指明解决此问题的正确方向。

    【讨论】:

    • 谢谢。实际上它是相关的,因为 prefetch_related 似乎从 mysql 加载了大约 7Mb 的数据。此处描述了问题:github.com/tomchristie/django-rest-framework/issues/656,您还可以查看 restframework 邮件列表上的对话以及 Xavier 对这个完全相同的问题的评论。
    【解决方案2】:

    您使用早期评估(如列表(查询集))。当许多并发工作人员为您的请求提供服务时,您可能只是内存不足?所以你必须做出决定:更快的结果还是更多的内存使用(每个请求 65MB?这可能会在几秒钟内耗尽所有内存)

    并且分析器说您的代码使用 QuerySet.all() :

    返回当前 QuerySet(或 QuerySet 子类)的副本。

    这调用了需要大量时间的 deepcopy。

    所以找到调用 all() 的地方,不要使用它。

    【讨论】:

      【解决方案3】:

      【讨论】:

        猜你喜欢
        • 2020-11-15
        • 1970-01-01
        • 2020-12-27
        • 2018-07-31
        • 2017-03-24
        • 2021-03-06
        • 2019-02-03
        • 2021-06-17
        • 2013-01-03
        相关资源
        最近更新 更多