Mauvaise performance Django / uwsgi

Je cours une application django avec nginx & uwsgi. Voici comment je lance 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 configurations:

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;
}

Voici le problème. En faisant " ab " (ApacheBenchmark) sur le serveur, j'obtiens les résultats suivants:

NGINX version: NGINX version: nginx / 1.2.6

Version 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

En cours d'exécution sur 500 niveau de concurrence

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

Comme vous pouvez le voir... toutes les demandes sur le serveur échouent avec des erreurs de délai d'attente ou " Client déconnecté prématurément " ou:

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

Voici un peu plus sur mon application: Fondamentalement, c'est une collection de modèles qui reflètent les tables MySQL qui contiennent tout le contenu. Au frontend, j'ai django-rest-framework qui sert du contenu json aux clients.

J'ai installé django-profiling & django debug toolbar pour voir ce qui se passe. Sur django-profiling, voici ce que je reçois lors de l'exécution d'une seule requête:

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()

..etc

Cependant, django-debug-toolbar affiche ce qui suit:

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

Le problème est que " top " montre que la moyenne de charge augmente rapidement et apache benchmark que j'ai couru à la fois sur le serveur local et à partir d'une machine distante dans le réseau montre que je ne sers pas beaucoup de requêtes / seconde. Quel est le problème? c'est aussi loin que je pouvais atteindre lors du profilage du code, il serait donc apprécié que quelqu'un puisse indiquer ce que je fais ici.

Edit (23/02/2013): ajout de plus de détails basés sur La réponse d'Andrew Alcock: Les points qui nécessitent mon attention / réponse sont (3) (3) j'ai exécuté "show global variables" sur MySQL et j'ai découvert que les configurations MySQL avaient 151 pour le paramètre max_connections, ce qui est plus que suffisant pour servir les travailleurs que je commence pour uwsgi.

(3)(4)(2) la seule requête que je Profile est la plus lourde. Il exécute 4 requêtes selon django-debug-toolbar. Ce qui se passe est que toutes les requêtes s'exécutent: 3.71, 2.83, 0.88, 4.84 ms respectivement.

(4) Ici, vous faites référence à la pagination de la mémoire? si oui, comment pouvais-je dire?

(5) sur 16 travailleurs, 100 taux de concurrence, 1000 demandes, la moyenne de charge monte à ~ 12 J'ai couru les tests sur un nombre différent de travailleurs (le niveau de concurrence est de 100):

  1. 1 travailleur, charge moyenne ~ 1.85, 19 reqs / seconde, Temps par requête: 5229.520, 0 non-2xx
  2. 2 travailleur, charge moyenne ~ 1.5, 19 reqs / seconde, Temps par demande: 516.520, 0 non-2xx
  3. 4 travailleur, charge moyenne ~ 3, 16 reqs / seconde, Temps par demande: 5929.921, 0 non-2xx
  4. 8 travailleur, moyenne de charge ~ 5, 18 reqs / seconde, Temps par demande: 5301.458, 0 non-2xx
  5. 16 travailleur, moyenne de charge ~ 19, 15 reqs / seconde, Temps par requête: 6384.720, 0 non-2xx

Comme vous pouvez le voir, plus nous avons de travailleurs, plus nous avons de charge sur le système. Je peux voir dans le journal du démon d'uwsgi que le temps de réponse en millisecondes augmente lorsque j'augmente le nombre de travailleurs.

Sur 16 travailleurs, exécutant 500 demandes de niveau de concurrence uwsgi démarre la journalisation des erreurs:

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

Charge va jusqu'à ~ 10 ainsi. et les tests ne prennent pas beaucoup de temps car les réponses non-2xx sont 923 Sur 1000, c'est pourquoi la réponse ici est assez rapide car elle est presque vide. Ce qui est également une réponse à votre point # 4 dans le résumé.

En supposant que ce à quoi je suis confronté ici est une latence du système d'exploitation basée sur les E / S et la mise en réseau, Quelle est l'action recommandée pour l'étendre? nouveau matériel? plus le serveur?

Merci

46
demandé sur Maverick 2013-02-19 20:21:40

3 réponses

EDIT 1 vu le commentaire que vous avez 1 noyau virtuel, en ajoutant des commentaires sur tous les points relavant

EDIT 2 plus d'informations de Maverick, donc j'élimine les idées exclues et développe les problèmes confirmés.

EDIT 3 rempli plus de détails sur la file d'attente de demandes uwsgi et les options de mise à l'échelle. Amélioration de la grammaire.

Modifier 4 mises à jour de Maverick et améliorations mineures

Les commentaires sont trop petits, alors en voici quelques-uns pensées:

  1. la moyenne de charge est essentiellement le nombre de processus en cours d'exécution ou en attente de L'attention du processeur. Pour un système parfaitement chargé avec 1 noyau CPU, la moyenne de charge devrait être 1.0; Pour un système 4 core, il devrait être 4.0. Au moment où vous exécutez le test web, les fusées de threading et vous avez un lot {[28] } de processus en attente de CPU. À moins que la moyenne de charge dépasse le nombre de cœurs de processeur d'une marge significative, ce n'est pas un problème
  2. le premier "temps par requête" la valeur de 4s est corrélée à la longueur de la file d'attente de requêtes - 1000 requêtes déversées sur Django presque instantanément et ont pris en moyenne 4s en service, dont environ 3,4 s étaient en attente dans une file d'attente. Cela est dû à l'inadéquation très importante entre le nombre de demandes (100) et le nombre de processeurs (16), ce qui fait que 84 des demandes attendent un processeur à un moment donné.
  3. S'exécutant à une concurrence de 100, les tests prennent 41 secondes à 24 requêtes / sec. Vous avez 16 processus (threads), donc chaque requête est traitée environ 700ms. étant donné votre type de transaction, c'est un long Temps par requête. Cela peut être parce que:

    1. Le coût CPU de chaque requête est élevé dans Django (ce qui est hautement improbable étant donné la faible valeur CPU de la barre d'outils de débogage)
    2. le système d'exploitation change beaucoup de tâche (surtout si la moyenne de charge est supérieure à 4-8), et la latence est purement due à un trop grand nombre de processus.
    3. Il N'y en a pas assez de connexions DB desservant les 16 processus pour que les processus attendent d'en avoir un Disponible. Avez-vous au moins une connexion par processus?
    4. Il y a une latence considérable autour de la base de données, soit :

      1. des dizaines de petites demandes prenant chacune, disons, 10ms, dont la plupart sont des frais généraux de mise en réseau. Si oui, pouvez-vous introduire la mise en cache ou réduire les appels SQL à un nombre plus petit. Ou
      2. un ou deux les requêtes prennent 100 de ms. pour vérifier cela, exécutez le profilage sur la base de données. Si c'est le cas, vous devez optimiser cette demande.
  4. La répartition entre le coût du processeur du système et de l'utilisateur est inhabituellement élevée dans le système, bien que le CPU total soit faible. Cela implique que la plupart du travail dans Django est lié au noyau, comme la mise en réseau ou le disque. Dans ce scénario, il peut s'agir de coûts réseau (par exemple, recevoir et envoyer des requêtes HTTP et recevoir et envoyer des requêtes à la base de données). Parfois, cela sera élevé à cause de pagination . S'il n'y a pas de pagination en cours, alors vous n'avez probablement pas à vous soucier de cela du tout.

  5. Vous avez défini les processus à 16, mais vous avez une moyenne de charge élevée (à quelle hauteur vous n'indiquez pas) . Idéalement, vous devriez toujours avoir au moins un processus en attente de CPU (pour que les CPU ne tournent pas les bras croisés). Les processus ici ne semblent pas liés au processeur, mais ont une latence significative, vous avez donc besoin de plus de processus que de cœurs. Combien plus? Essayez d'exécuter l'uwsgi avec différents nombres de processeurs (1, 2, 4, 8, 12, 16, 24, etc) jusqu'à ce que vous ayez le meilleur débit. Si vous modifiez la latence du processus Moyen, vous devrez l'ajuster à nouveau.
  6. le niveau de concurrence 500 est certainement un problème, mais est-ce le client ou le serveur? Le rapport indique que 50 (sur 100) avaient la longueur de contenu incorrecte, ce qui implique un problème de serveur. Le non-2xx semble également pointer là. Est-il possible de capturer le non-2xx les réponses pour le débogage-les traces de pile ou le message d'erreur spécifique seraient incroyablement utiles (EDIT) et sont causées par la file d'attente de requêtes uwsgi s'exécutant avec sa valeur par défaut de 100.

Donc, en résumé:

entrez la description de l'image ici

  1. Django semble bien
  2. incompatibilité entre la concurrence du test de charge (100 ou 500) et les processus (16): vous poussez beaucoup trop de demandes simultanées dans le système pour le nombre de processus à gérer. Une fois que vous êtes au-dessus de la nombre de processus, tout ce qui se passera est que vous allongerez la file D'attente de requêtes HTTP dans le serveur web
  3. Il y a une grande latence, donc soit

    1. Discordance entre les processus (16) et les cœurs de processeur (1): si la moyenne de charge est >3, Il y a probablement trop de processus. Essayez à nouveau avec un plus petit nombre de processus

      1. moyenne de Charge > 2 - > Essayez 8 processus
      2. Charge moyenne > 4 -> essayez 4 processus
      3. charge moyenne > 8 -> essayez 2 processus
    2. si la moyenne de charge

  4. sans capturer la réponse échouée, il n'y a pas grand-chose que je puisse dire sur les échecs à 500 concurrence

Le Développement des idées

Votre charge moyenne >10 sur un seul noyau la machine estvraiment méchante et (comme vous l'observez) conduit à beaucoup de changement de tâche et à un comportement lent général. Personnellement, je ne me souviens pas avoir vu une machine avec une moyenne de charge de 19 (que vous avez pour 16 processus) - félicitations pour l'avoir si élevée ;)

Les performances de la base de données sont excellentes, donc je dirais que tout est clair en ce moment.

Paging : pour répondre à votre question sur la façon de voir la pagination - vous pouvez détecter la pagination du système D'exploitation de plusieurs façons. Par exemple, en haut, l'en-tête a la page-tenants et aboutissants (voir la dernière ligne):

Processes: 170 total, 3 running, 4 stuck, 163 sleeping, 927 threads                                                                                                        15:06:31
Load Avg: 0.90, 1.19, 1.94  CPU usage: 1.37% user, 2.97% sys, 95.65% idle  SharedLibs: 144M resident, 0B data, 24M linkedit.
MemRegions: 31726 total, 2541M resident, 120M private, 817M shared. PhysMem: 1420M wired, 3548M active, 1703M inactive, 6671M used, 1514M free.
VM: 392G vsize, 1286M framework vsize, 1534241(0) pageins, 0(0) pageouts. Networks: packets: 789684/288M in, 912863/482M out. Disks: 739807/15G read, 996745/24G written.

Nombre de processus: Dans votre configuration actuelle, le nombre de processus est chemin trop élevé. redimensionnez le nombre de processus à un 2 . Nous pourrions apporter cette valeur plus tard, en fonction du déplacement de la charge supplémentaire de ce serveur.

Emplacement du Benchmark Apache : la moyenne de charge de 1,85 pour un processus me suggère que vous utilisez le générateur de charge sur la même machine que uwsgi - est-ce exact?

Si c'est le cas, vous devez vraiment l'exécuter à partir d'une autre machine, sinon les tests ne sont pas représentatifs de la charge réelle - vous prenez de la mémoire et du processeur des processus web pour l'utiliser dans le générateur de charge. En outre, les threads 100 ou 500 du générateur de charge stresseront généralement votre serveur d'une manière qui ne se produit pas dans la vie réelle. En effet, cela pourrait être la raison pour laquelle tout le test échoue.

Emplacement de la base de données : la moyenne de charge pour un processus également suggérez que vous exécutez la base de données sur la même machine que les processus web - est-ce correct?

Si j'ai raison sur la base de données, la première et la meilleure façon de commencer à mettre à l'échelle est de déplacer la base de données vers une autre machine. Nous faisons cela pour deux raisons:

  1. Un serveur de base de données a besoin d'un profil matériel différent d'un nœud de traitement:

    1. Disque: DB a besoin de beaucoup de rapide, redondant, disque sauvegardé, et un nœud de traitement juste besoin d'un disque de base
    2. CPU: UN le nœud de traitement a besoin du processeur le plus rapide que vous pouvez vous permettre alors qu'une machine DB peut souvent se passer (souvent ses performances sont bloquées sur le disque et la RAM)
    3. RAM: une machine DB a généralement besoin d'autant de RAM que possible (et la DB la plus rapide a toutes les ses données en RAM), alors que de nombreux nœuds de traitement ont besoin de beaucoup moins (le vôtre a besoin d'environ 20 Mo par processus - très petit
    4. Scaling: Atomic DBS scale le mieux en ayant des machines monstres avec de nombreux processeurs alors que le niveau web (n'ayant pas état) peut évoluer en branchant de nombreux petits boxen identiques.
  2. Affinité du processeur: il est préférable que le processeur ait une moyenne de charge de 1.0 et que les processus aient une affinité pour un seul cœur. Cela maximise l'utilisation du cache du processeur et minimise les frais généraux de commutation des tâches. En séparant les nœuds de base de données et de traitement, vous appliquez cette affinité dans HW.

500 concurrence avec exceptions la file d'attente de requête dans le diagramme ci - dessus est au plus de 100-Si uwsgi reçoit une demande lorsque la file d'attente est pleine, la demande est rejetée avec un 5xx erreur. Je pense que cela se passait dans votre test de charge de concurrence 500-fondamentalement, la file d'attente s'est remplie avec les 100 premiers threads, puis les 400 autres threads ont émis les 900 requêtes restantes et ont reçu des erreurs 5xx immédiates.

Pour gérer 500 requêtes par seconde, vous devez vous assurer de deux choses:

  1. La Taille de la file D'attente de requête est configurée pour gérer la rafale: utilisez l'argument --listen pour uwsgi
  2. le système peut gérer un débit supérieur à 500 requêtes par seconde Si 500 est une condition normale, ou un peu inférieur si 500 est un pic. Voir les notes de mise à l'échelle ci-dessous.

J'imagine que uwsgi a la file d'attente définie sur un nombre plus petit pour mieux gérer les attaques DDoS; si elle est placée sous une charge énorme, la plupart des demandes échouent immédiatement avec presque aucun traitement permettant à la boîte dans son ensemble d'être toujours sensible aux administrateurs.

Conseils Généraux pour la mise à l'échelle d'un système

Votre considération la plus importante est probablement de maximiser le débit . Un autre besoin possible de minimiser le temps de réponse, mais je ne vais pas en discuter ici. En maximisant le débit, vous essayez de maximiser le système , pas les composants individuels; certaines diminutions locales peuvent améliorer le débit global du système (par exemple, apporter une modification qui ajoute de la latence dans le niveau web afin d'améliorer les performances de la base de données est un net gagner).

Sur les spécificités:

  1. déplacez la base de données sur une machine séparée . Après cela, profilez la base de données pendant votre test de charge en exécutant top et votre outil de surveillance MySQL préféré. Vous devez être en mesure de profil . Déplacer la base de données vers une machine séparée introduira une latence supplémentaire (plusieurs ms) par requête, alors attendez-vous à augmenter légèrement le nombre de processus au niveau web pour conserver le même débit.
  2. assurez - vous que la file d'attente des requêtes uswgi est grande assez pour gérer une rafale de trafic en utilisant l'argument --listen. Cela devrait être plusieurs fois le nombre maximal de demandes en état d'équilibre par seconde que votre système peut gérer.
  3. Sur le niveau web / app: Équilibrez le nombre de processus avec le nombre de cœurs de processeur et la latence inhérente au processus. Trop de processus ralentit les performances, trop peu signifie que vous n'utiliserez jamais pleinement les ressources système. Il n'y a pas de point d'équilibrage fixe, car chaque application et modèle d'utilisation est différent, donc benchmark et ajuster. Comme guide, utilisez la latence des processus, si chaque tâche a:

    • 0% de latence, alors vous avez besoin de 1 processus par cœur
    • 50% de latence (c'est-à-dire que le temps CPU est la moitié du temps réel), alors vous avez besoin de 2 processus par cœur
    • 67% de latence, alors vous avez besoin de 3 processus par cœur
  4. Vérifiez top pendant le test pour vous assurer que vous êtes au-dessus de 90% d'utilisation du processeur (pour chaque cœur) et Vous avez une moyenne de charge un peu au-dessus de 1.0. Si la moyenne de charge est plus élevée, réduisez les processus. Si tout va bien, à un moment donné, vous ne serez pas en mesure d'atteindre cet objectif, et DB pourrait maintenant être le goulot d'étranglement

  5. à un moment donné, vous aurez besoin de plus de puissance dans le niveau web. Vous pouvez soit choisir d'ajouter plus de CPU à la machine (relativement facile) et ainsi ajouter plus de processus, et/ou Vous pouvez ajouter plus de nœuds de traitement (évolutivité horizontale). Ce dernier peut être réalisé dans uwsgi en utilisant la méthode discutée ici par Łukasz Mierzwa
129
répondu Andrew Alcock 2017-05-23 12:34:24

Veuillez exécuter des benchmarks beaucoup plus longtemps qu'une minute( 5-10 au moins), vous n'obtiendrez vraiment pas beaucoup d'informations à partir d'un test aussi court. Et utilisez le plugin carbon d'uWSGI pour pousser les statistiques sur le serveur carbon / graphite (vous en aurez besoin), vous aurez beaucoup plus d'informations pour le débogage.

Lorsque vous envoyez 500 requêtes simultanées à votre application et qu'elle ne peut pas gérer une telle charge, la file d'attente d'écoute sur chaque backend sera remplie assez rapidement (c'est 100 requêtes par défaut) , vous voudrez peut-être augmenter cela, mais si les travailleurs ne peuvent pas traiter les demandes rapides et que la file d'attente d'écoute (également appelée backlog) est pleine, la pile réseau linux abandonnera la demande et vous commencerez à obtenir des erreurs.

Votre premier benchmark indique que vous pouvez traiter une seule requête en ~42 ms, de sorte qu'un seul travailleur peut traiter au plus 1000ms / 42ms = ~ 23 requêtes par seconde (si la base de données et d'autres parties de la pile d'applications ne ralentissent pas à mesure que la concurrence augmente). Donc, pour traiter 500 demandes simultanées, vous auriez besoin d'au moins 500 / 23 = 21 travailleurs (mais en réalité, je dirais au moins 40), Vous avez seulement 16, Pas étonnant qu'il se brise sous une telle charge.

EDIT: j'ai mélangé le taux avec la concurrence - au moins 21 travailleurs vous permettront de traiter 500 demandes par seconde, pas 500 demandes simultanées. Si vous voulez vraiment gérer 500 demandes simultanées, vous avez simplement besoin de 500 travailleurs. Sauf si vous exécutez votre application en mode asynchrone, vérifiez la section "Gevent" dans les documents uWSGI.

PS. uWSGI est livré avec un excellent équilibreur de charge avec backend autoconfiguration (lire les documents sous " Serveur D'Abonnement "et"FastRouter"). Vous pouvez le configurer d'une manière qui vous permet de brancher à chaud un nouveau backend au besoin, vous démarrez simplement les travailleurs sur un nouveau nœud et ils s'abonneront à FastRouter et commenceront à recevoir des demandes. C'est le meilleur moyen de l'échelle horizontale. Et avec les backends sur AWS, vous pouvez automatiser cela afin que les nouveaux backends soient démarrés rapidement en cas de besoin.

6
répondu Łukasz Mierzwa 2013-02-24 14:12:13

Ajouter plus de travailleurs et obtenir moins de r / S signifie que votre requête "est pure CPU" et qu'il n'y a pas D'attente D'E / S qu'un autre travailleur peut utiliser pour servir une autre requête.

Si vous voulez mettre à l'échelle, vous devrez utiliser un autre serveur avec plus de processeurs (ou plus rapides).

Cependant, il s'agit d'un test synthétique, le nombre de r/s que vous obtenez est la limite supérieure de la requête exacte que vous testez, une fois en production, il y a beaucoup plus de variables qui peuvent affecter les performances.

1
répondu barracel 2013-02-24 06:47:02