6

私は現在、EC2小規模インスタンスサーバーにDjango w/Django-Rest-Frameworkをデプロイして、いくつかのAndroidアプリケーション用のAPIセットを提供しています。Django Rest Framework/Djangoパフォーマンスの問題

問題は、私がプロファイルしなければならない重大なパフォーマンス上の問題に直面していることです。 1回のリクエストでほとんどの時間が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」それは普通のことです、あなたはこのすべてから見ることができるように

@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/ 

お知らせ。以下は、頻繁に記録されます。テスト中

負荷平均は〜5まで行くと、ここでAB結果だ

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) 
:すべての

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件のリクエストが失敗した理由は、ここで直面しているパフォーマンスの遅れによるものです。

完全に正直である。私はキャッシングなしで1秒あたり〜60 - 70要求を期待しています。私はキャッシングがそのプロセスをスピードアップすることを知っていますが、キャッシュの前にこれを解決しようとしている理由がパフォーマンス上の問題を隠しています。

私は、コールスタックを示して要求に教授を追加することにより、ジャンゴ・プロファイリング使用して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

は、Python/Djangoの規模に

Djangoの力などディスカスやInstagramのなど、いくつかのかなり大規模なサービスを、ないので、ええ、それはうまくスケール。

私はあなたがほぼ全体の時間が.get_most_viewed()メソッド、 の内側に費やされているので、それはあなたが持っている問題のように見える見ることができるように、この

をプロファイルするために、その後を決めました。 (間違っている可能性がありますが、あなたのプロフィールは、2.037のうち2.028がその時間に費やされていることを示唆していますので、約99.6%です)

私のORMのスキルは、そのかなり複雑なクエリに対処する必要があり、いずれの場合もモデル定義を参照する必要がありますが、ビューの他の部分を見るのではなく、そのクエリをデバッグして単純化する必要があります。

manage.pyシェルを使用してDjangoシェルにドロップすると、その特定のクエリを除外して他のビューにプロファイルすることができます。

また多分あなたはではなく、そのクエリについて、具体的をを頼む場合は、ここにクエリを簡素化することができ、またはDjangoのIRCチャンネルやDjangoのメーリングリスト上で(あなたはおそらくより多くの運を持っていますどのようにいくつかの助けを得てみてくださいより一般的なジャンゴ/ RESTフレームワークが、ここで質問を言葉で表現、そのほとんどが実際にあなたのシーイングの問題に関連すると表示されません。

希望これが解決取得するための正しい方向を指し示すことができますこと。

+0

おかげDRFのメーリングリストで継続しました。実際には、prefetch_relatedがmysqlから約7Mbのデータをロードしているように見えます。問題はhttps://github.com/tomchristie/django-rest-framework/issues/656で説明されています。また、まったく同じ問題についてXavierのコメントを付けてrestframeworkメーリングリストの会話をチェックすることもできます。 – Maverick

0

ます(リスト(queryset)のような)早期の評価を使用します。多くの並行作業者がリクエストを処理するときに、メモリが不足する可能性がありますか?結果またはそれ以上のメモリ使用量(要求ごとに65MB?これは、秒単位ですべてのメモリを食べることができる)

とプロファイラは、あなたのコードがQuerySet.all()を使用していることを述べている:

は、現在のクエリセット(またはクエリセットサブクラスのコピーを返しますが)。

これは、時間がかかるdeepcopyを呼び出します。

したがって、all()が呼び出された場所を見つけて使用しないでください。