web-dev-qa-db-ja.com

悪いDjango / uwsgiパフォーマンス

私はDjangoアプリをnginxとuwsgiで実行しています。uwsgiを実行する方法は次のとおりです。

Sudo uwsgi -b 25000 --chdir=/www/python/apps/pyapp --module=wsgi:application --env Django_SETTINGS_MODULE=settings --socket=/tmp/pyapp.socket --cheaper=8 --processes=16  --harakiri=10  --max-requests=5000  --vacuum --master --pidfile=/tmp/pyapp-master.pid --uid=220 --gid=499

&nginx設定:

server {
    listen 80;
    server_name test.com

    root /www/python/apps/pyapp/;

    access_log /var/log/nginx/test.com.access.log;
    error_log /var/log/nginx/test.com.error.log;

    # https://docs.djangoproject.com/en/dev/howto/static-files/#serving-static-files-in-production
    location /static/ {
        alias /www/python/apps/pyapp/static/;
        expires 30d;
    }

    location /media/ {
        alias /www/python/apps/pyapp/media/;
        expires 30d;
    }

    location / {
        uwsgi_pass unix:///tmp/pyapp.socket;
        include uwsgi_params;
        proxy_read_timeout 120;
    }

    # what to serve if upstream is not available or crashes
    #error_page 500 502 503 504 /media/50x.html;
}

ここに問題があります。サーバーで「ab」(ApacheBenchmark)を実行すると、次の結果が得られます。

nginxバージョン:nginxバージョン:nginx/1.2.6

uwsgiバージョン:1.4.5

Server Software:        nginx/1.0.15
Server Hostname:        pycms.com
Server Port:            80

Document Path:          /api/nodes/mostviewed/8/?format=json
Document Length:        8696 bytes

Concurrency Level:      100
Time taken for tests:   41.232 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      8866000 bytes
HTML transferred:       8696000 bytes
Requests per second:    24.25 [#/sec] (mean)
Time per request:       4123.216 [ms] (mean)
Time per request:       41.232 [ms] (mean, across all concurrent requests)
Transfer rate:          209.99 [Kbytes/sec] received

500同時実行レベルで実行中

oncurrency Level:      500
Time taken for tests:   2.175 seconds
Complete requests:      1000
Failed requests:        50
   (Connect: 0, Receive: 0, Length: 50, Exceptions: 0)
Write errors:           0
Non-2xx responses:      950
Total transferred:      629200 bytes
HTML transferred:       476300 bytes
Requests per second:    459.81 [#/sec] (mean)
Time per request:       1087.416 [ms] (mean)
Time per request:       2.175 [ms] (mean, across all concurrent requests)
Transfer rate:          282.53 [Kbytes/sec] received

ご覧のように、サーバー上のすべてのリクエストは、タイムアウトエラーまたは「クライアントが途中で切断されました」または次のいずれかで失敗します。

writev(): Broken pipe [proto/uwsgi.c line 124] during GET /api/nodes/mostviewed/9/?format=json

私のアプリケーションについてもう少し説明します。基本的に、すべてのコンテンツを含むMySQLテーブルを反映するモデルのコレクションです。フロントエンドには、クライアントにjsonコンテンツを提供するDjango-rest-frameworkがあります。

Djangoプロファイリングをインストールしました&Djangoデバッグツールバーで何が起こっているかを確認します。

Instance wide RAM usage

Partition of a set of 147315 objects. Total size = 20779408 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  63960  43  5726288  28   5726288  28 str
     1  36887  25  3131112  15   8857400  43 Tuple
     2   2495   2  1500392   7  10357792  50 dict (no owner)
     3    615   0  1397160   7  11754952  57 dict of module
     4   1371   1  1236432   6  12991384  63 type
     5   9974   7  1196880   6  14188264  68 function
     6   8974   6  1076880   5  15265144  73 types.CodeType
     7   1371   1  1014408   5  16279552  78 dict of type
     8   2684   2   340640   2  16620192  80 list
     9    382   0   328912   2  16949104  82 dict of class
<607 more rows. Type e.g. '_.more' to view.>



CPU Time for this request

         11068 function calls (10158 primitive calls) in 0.064 CPU seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.064    0.064 /usr/lib/python2.6/site-packages/Django/views/generic/base.py:44(view)
        1    0.000    0.000    0.064    0.064 /usr/lib/python2.6/site-packages/Django/views/decorators/csrf.py:76(wrapped_view)
        1    0.000    0.000    0.064    0.064 /usr/lib/python2.6/site-packages/rest_framework/views.py:359(dispatch)
        1    0.000    0.000    0.064    0.064 /usr/lib/python2.6/site-packages/rest_framework/generics.py:144(get)
        1    0.000    0.000    0.064    0.064 /usr/lib/python2.6/site-packages/rest_framework/mixins.py:46(list)
        1    0.000    0.000    0.038    0.038 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:348(data)
     21/1    0.000    0.000    0.038    0.038 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:273(to_native)
     21/1    0.000    0.000    0.038    0.038 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:190(convert_object)
     11/1    0.000    0.000    0.036    0.036 /usr/lib/python2.6/site-packages/rest_framework/serializers.py:303(field_to_native)
    13/11    0.000    0.000    0.033    0.003 /usr/lib/python2.6/site-packages/Django/db/models/query.py:92(__iter__)
      3/1    0.000    0.000    0.033    0.033 /usr/lib/python2.6/site-packages/Django/db/models/query.py:77(__len__)
        4    0.000    0.000    0.030    0.008 /usr/lib/python2.6/site-packages/Django/db/models/sql/compiler.py:794(execute_sql)
        1    0.000    0.000    0.021    0.021 /usr/lib/python2.6/site-packages/Django/views/generic/list.py:33(paginate_queryset)
        1    0.000    0.000    0.021    0.021 /usr/lib/python2.6/site-packages/Django/core/paginator.py:35(page)
        1    0.000    0.000    0.020    0.020 /usr/lib/python2.6/site-packages/Django/core/paginator.py:20(validate_number)
        3    0.000    0.000    0.020    0.007 /usr/lib/python2.6/site-packages/Django/core/paginator.py:57(_get_num_pages)
        4    0.000    0.000    0.020    0.005 /usr/lib/python2.6/site-packages/Django/core/paginator.py:44(_get_count)
        1    0.000    0.000    0.020    0.020 /usr/lib/python2.6/site-packages/Django/db/models/query.py:340(count)
        1    0.000    0.000    0.020    0.020 /usr/lib/python2.6/site-packages/Django/db/models/sql/query.py:394(get_count)
        1    0.000    0.000    0.020    0.020 /usr/lib/python2.6/site-packages/Django/db/models/query.py:568(_prefetch_related_objects)
        1    0.000    0.000    0.020    0.020 /usr/lib/python2.6/site-packages/Django/db/models/query.py:1596(prefetch_related_objects)
        4    0.000    0.000    0.020    0.005 /usr/lib/python2.6/site-packages/Django/db/backends/util.py:36(execute)
        1    0.000    0.000    0.020    0.020 /usr/lib/python2.6/site-packages/Django/db/models/sql/query.py:340(get_aggregation)
        5    0.000    0.000    0.020    0.004 /usr/lib64/python2.6/site-packages/MySQLdb/cursors.py:136(execute)
        2    0.000    0.000    0.020    0.010 /usr/lib/python2.6/site-packages/Django/db/models/query.py:1748(prefetch_one_level)
        4    0.000    0.000    0.020    0.005 /usr/lib/python2.6/site-packages/Django/db/backends/mysql/base.py:112(execute)
        5    0.000    0.000    0.019    0.004 /usr/lib64/python2.6/site-packages/MySQLdb/cursors.py:316(_query)
       60    0.000    0.000    0.018    0.000 /usr/lib/python2.6/site-packages/Django/db/models/query.py:231(iterator)
        5    0.012    0.002    0.015    0.003 /usr/lib64/python2.6/site-packages/MySQLdb/cursors.py:278(_do_query)
       60    0.000    0.000    0.013    0.000 /usr/lib/python2.6/site-packages/Django/db/models/sql/compiler.py:751(results_iter)
       30    0.000    0.000    0.010    0.000 /usr/lib/python2.6/site-packages/Django/db/models/manager.py:115(all)
       50    0.000    0.000    0.009    0.000 /usr/lib/python2.6/site-packages/Django/db/models/query.py:870(_clone)
       51    0.001    0.000    0.009    0.000 /usr/lib/python2.6/site-packages/Django/db/models/sql/query.py:235(clone)
        4    0.000    0.000    0.009    0.002 /usr/lib/python2.6/site-packages/Django/db/backends/__init__.py:302(cursor)
        4    0.000    0.000    0.008    0.002 /usr/lib/python2.6/site-packages/Django/db/backends/mysql/base.py:361(_cursor)
        1    0.000    0.000    0.008    0.008 /usr/lib64/python2.6/site-packages/MySQLdb/__init__.py:78(Connect)
  910/208    0.003    0.000    0.008    0.000 /usr/lib64/python2.6/copy.py:144(deepcopy)
       22    0.000    0.000    0.007    0.000 /usr/lib/python2.6/site-packages/Django/db/models/query.py:619(filter)
       22    0.000    0.000    0.007    0.000 /usr/lib/python2.6/site-packages/Django/db/models/query.py:633(_filter_or_exclude)
       20    0.000    0.000    0.005    0.000 /usr/lib/python2.6/site-packages/Django/db/models/fields/related.py:560(get_query_set)
        1    0.000    0.000    0.005    0.005 /usr/lib64/python2.6/site-packages/MySQLdb/connections.py:8()

..等

ただし、Django-debug-toolbarは次を表示します。

Resource Usage
Resource    Value
User CPU time   149.977 msec
System CPU time 119.982 msec
Total CPU time  269.959 msec
Elapsed time    326.291 msec
Context switches    11 voluntary, 40 involuntary

and 5 queries in 27.1 ms

問題は、「top」は負荷平均が急速に上昇することを示し、ローカルサーバーとネットワーク内のリモートマシンの両方で実行したApacheベンチマークは、1秒あたりのリクエスト数が少ないことを示していることです。何が問題ですか?これは、コードをプロファイリングするときに到達できる範囲であるため、誰かが私がここで何をしているかを指摘できれば幸いです。

編集(23/02/2013):Andrew Alcockの回答に基づいて詳細を追加:私の注意/回答が必要なポイントは(3)(3)で「グローバル変数の表示」を実行しましたMySQLと、MySQL構成には、max_connections設定に151があり、これはuwsgiで開始するワーカーにサービスを提供するのに十分すぎることがわかりました。

(3)(4)(2)プロファイリングしている単一のリクエストが最も重いリクエストです。 Django-debug-toolbarに従って4つのクエリを実行します。発生するのは、すべてのクエリがそれぞれ3.71、2.83、0.88、4.84ミリ秒で実行されることです。

(4)ここでメモリページングを参照していますか?もしそうなら、どうすればわかりますか?

(5)16のワーカー、100の同時実行率、1000のリクエストで負荷平均が最大12になります。異なる数のワーカーでテストを実行しました(同時実行レベルは100)

  1. ワーカー1人、負荷平均〜1.85、19リクエスト/秒、リクエストあたりの時間:5229.520、0以外2xx
  2. ワーカー2人、負荷平均〜1.5、19リクエスト/秒、リクエストあたりの時間:516.520、0以外2xx
  3. 4ワーカー、負荷平均〜3、16リクエスト/秒、リクエストごとの時間:5929.921、0 2xx以外
  4. 8ワーカー、負荷平均〜5、18リクエスト/秒、リクエストあたりの時間:5301.458、0 2xx以外
  5. 16ワーカー、負荷平均〜19、15リクエスト/秒、リクエストごとの時間:6384.720、0非2xx

ご覧のとおり、従業員が多いほど、システムにかかる負荷が大きくなります。 uwsgiのデーモンログで、ワーカーの数を増やすと、ミリ秒単位の応答時間が増加することがわかります。

16人のワーカーで、500の同時実行レベル要求を実行すると、uwsgiはエラーのログ記録を開始します。

 writev(): Broken pipe [proto/uwsgi.c line 124] 

負荷も最大10になります。また、2xx以外の応答は1000のうち923であるため、テストにそれほど時間はかかりません。このため、ここでの応答はほとんど空であるため非常に高速です。これは、要約のポイント4への返信でもあります。

ここで私が直面しているのは、I/Oとネットワークに基づくOSレイテンシーであると仮定すると、これをスケールアップするための推奨されるアクションは何ですか?新しいハードウェア?より大きなサーバー?

ありがとう

47
Maverick

ベンチマークを1分よりも長く実行してください(少なくとも5〜10)。このような短いテストでは、あまり多くの情報を取得できません。そして、uWSGIのCarbonプラグインを使用して、統計をCarbon/Graphite Serverにプッシュします(1つ必要です)。デバッグには、さらに多くの情報が必要です。

500の同時リクエストをアプリに送信し、そのような負荷を処理できない場合、各バックエンドのリッスンキューは非常にすばやくいっぱいになります(デフォルトでは100リクエストです)。それを増やしたい場合がありますが、高速でリッスンキュー(バックログとも呼ばれる)がいっぱいになると、Linuxネットワークスタックはリクエストをドロップし、エラーが発生し始めます。

最初のベンチマークでは、単一のリクエストを〜42ミリ秒で処理できるため、単一のワーカーが最大で毎秒1000ミリ秒/ 42ミリ秒=〜23のリクエストを処理できることが示されています(同時実行性が向上してもdbおよびアプリスタックの他の部分が遅くならない場合) 。したがって、500の同時要求を処理するには、少なくとも500/23 = 21のワーカーが必要です(実際には少なくとも40と言います)。このような負荷で壊れるのは当然16です。

編集:レートと同時実行性を混合しました-少なくとも21人のワーカーが、500の同時リクエストではなく、1秒あたり500のリクエストを処理できます。 500の同時要求を本当に処理したい場合は、500人のワーカーが必要です。アプリを非同期モードで実行しない限り、uWSGIドキュメントの「イベント」セクションを確認してください。

PS。 uWSGIには、バックエンド自動構成を備えた優れたロードバランサーが付属しています(「サブスクリプションサーバー」および「FastRouter」のドキュメントを参照してください)。必要に応じて新しいバックエンドをホットプラグできるようにセットアップできます。新しいノードでワーカーを開始するだけで、FastRouterにサブスクライブしてリクエストの取得を開始できます。これは、水平方向に拡大縮小する最良の方法です。また、AWSのバックエンドを使用すると、必要に応じて新しいバックエンドがすぐに開始されるように、これを自動化できます。

6
Łukasz Mierzwa

ワーカーを追加してr/sを減らすと、リクエストは「純粋なCPU」であり、IOは別のワーカーが別のリクエストを処理するために使用できる待機時間はありません。

スケーリングしたい場合は、より多くの(またはより高速な)CPUを備えた別のサーバーを使用する必要があります。

ただし、これは模擬テストです。取得するr/sの数は、テストする正確なリクエストの上限です。本番環境では、パフォーマンスに影響する可能性のある変数がさらに多くあります。

1
barracel