2013-02-16 159 views
6

我目前在我的EC2小型实例服务器上部署Django w/Django-Rest-Framework,为几个Android应用程序提供一组API。Django Rest框架/ Django性能问题

问题是我面临一个严重的性能问题,我不得不进行配置。我发现单个请求的大部分时间都花在DRF的核心中。

对不起,这是一个很长的文章,但我想我必须展示一切,以便我可以得到正确的答案。让我继续:

我的设置是nginx/uwsgi。以下是我使用的新贵运行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”(我知道这是错用classmethods而不是经理法)

@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的分析来分析这是VMware的CentOS机器上教授请求显示调用堆栈:

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会缩放吗?我读过它,但是我应该期待在一个简单的数据库中读取多少个请求/秒?&渲染操作,例如我正在执行的操作?

回答

2

确实蟒蛇/ Django的规模

Django的权力,一些相当大规模的服务,例如类似Disqus和Instagram,所以是的,它缩放就好了。

于是我决定资料这个

正如你可以看到几乎整个时间是.get_most_viewed()方法中度过的,所以 看起来像你有问题。 (可能在那里是错误的,但是你的个人资料表明,在2.037的时间内有2.028被用在那里,所以大约有99.6%的时间)

我的ORM技能并不是真的需要弄清楚你应该怎么做在处理那个相当复杂的查询时,无论如何都需要查看模型定义,但是您需要查看调试和简化查询,而不是查看视图的其他部分。

您可能想使用manage.py shell将其放入Django shell中,以便您可以将该特定查询排除在其他视图之外。

而且也许尝试获得一些帮助,你怎么可以在这里简化查询,或Django的IRC频道或者Django的邮件列表(你很可能有更多的运气,如果你问特别有关的查询,而不是更一般的Django的/ REST框架措辞的问题在这里,其中大部分实际上并不似乎是与问题相关的您的视宁度。

希望帮助你指出正确的方向得到这个解决。

+0

感谢上。实际上它是相关的,因为看起来prefetch_related正在加载大约7Mb的来自mysql的数据。问题在这里描述:https://github.com/tomchristie/django-rest-framework/issues/656,您也可以检查restframework邮件列表上的对话,Xavier对这个完全相同的问题发表评论。 – Maverick 2013-02-16 19:11:39

0

你使用早期评估(如列表(queryset))。当许多协同工作者服务于你的请求时,你可能只是用完了内存?所以你必须决定:快速呃结果或更多的内存使用情况(每个请求65MB?这可能吃秒所有内存)

和探查说,你的代码使用QuerySet.all()其中:

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

而这就需要花费很长时间的深层复制。

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