web-dev-qa-db-fra.com

Mauvaises performances Django / uwsgi

J'utilise une application Django avec nginx et uwsgi. Voici comment exécuter 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

Configurations de & 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;
}

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

version nginx: version nginx: 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

Lors de l'exécution au niveau de concurrence 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

Comme vous pouvez le voir ... toutes les demandes sur le serveur échouent avec des erreurs de dépassement de délai 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 j'obtiens 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 les éléments suivants:

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 que le benchmark Apache que j'ai exécuté à la fois sur le serveur local et à partir d'une machine distante sur le réseau montre que je ne traite 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é "afficher les variables globales" sur MySQL et a 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 demande que je présente est la plus lourde. Il exécute 4 requêtes selon Django-debug-toolbar. Ce qui se passe, c'est que toutes les requêtes s'exécutent en: 3,71, 2,83, 0,88, 4,84 ms respectivement.

(4) Ici, vous parlez de pagination mémoire? si oui, comment savoir?

(5) Sur 16 employés, 100 taux de simultanéité, 1000 demandes, la moyenne de charge monte à ~ 12 J'ai effectué les tests sur différents nombres de travailleurs (le niveau de concurrence est de 100):

  1. 1 travailleur, charge moyenne ~ 1,85, 19 demandes/seconde, temps par demande: 5229,520, 0 non-2xx
  2. 2 travailleurs, charge moyenne ~ 1,5, 19 demandes/seconde, temps par demande: 516 520, 0 non-2xx
  3. 4 employés, charge moyenne ~ 3, 16 demandes/seconde, temps par demande: 5929.921, 0 non-2xx
  4. 8 travailleurs, charge moyenne ~ 5, 18 demandes/seconde, temps par demande: 5301.458, 0 non-2xx
  5. 16 travailleurs, charge moyenne ~ 19, 15 demandes/seconde, temps par demande: 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 de uwsgi que le temps de réponse en millisecondes augmente lorsque j'augmente le nombre de travailleurs.

Sur 16 travailleurs, l'exécution de 500 demandes de niveau d'accès simultané uwsgi démarre la journalisation des erreurs:

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

La charge monte également à ~ 10 également. 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 n ° 4 dans le résumé.

En supposant que ce à quoi je fais face 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 augmenter cette échelle? nouveau matériel? plus grand serveur?

Merci

47
Maverick

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

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 requêtes uwsgi et les options de mise à l'échelle. Amélioration de la grammaire.

EDIT 4 Mises à jour de Maverick et améliorations mineures

Les commentaires sont trop petits, alors voici quelques réflexions:

  1. La moyenne de charge est essentiellement le nombre de processus en cours d'exécution ou en attente d'attention du processeur. Pour un système parfaitement chargé avec 1 cœur de processeur, la moyenne de charge doit être de 1,0; pour un système à 4 cœurs, il devrait être de 4,0. Au moment où vous exécutez le test Web, les fusées de filetage et vous avez un beaucoup 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. La première valeur de "temps par demande" de 4 s correspond à la longueur de la file d'attente des demandes - 1000 demandes vidées sur Django presque instantanément et a pris en moyenne 4 s au service, dont 3,4 s environ étaient en attente dans une file d'attente, en raison de la très forte disparité entre le nombre de requêtes (100) et le nombre de processeurs (16), ce qui fait que 84 des requêtes 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 demande est traitée environ 700 ms. Compte tenu de votre type de transaction, cela représente long fois par demande. Cela peut être dû au fait que:

    1. Le coût CPU de chaque requête est élevé en 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 a pas suffisamment de connexions de base de données desservant les 16 processus, de sorte que les processus attendent d'en avoir un disponible. Avez-vous au moins une connexion disponible par processus?
    4. Il y a considérable latence autour de la base de données, soit:

      1. Des dizaines de petites demandes prenant chacune, disons, 10 ms, la plupart étant des frais généraux de mise en réseau. Si c'est le cas, pouvez-vous introduire la mise en cache ou réduire les appels SQL à un nombre plus petit. Ou
      2. Une ou deux requêtes prennent des centaines de ms. Pour vérifier cela, exécutez le profilage sur la base de données. Si tel est le cas, vous devez optimiser cette demande.
  4. La répartition entre les coûts du système et du processeur utilisateur est inhabituellement élevée dans le système, bien que le processeur 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 de réseau (par exemple, recevoir et envoyer des requêtes HTTP et recevoir et envoyer des requêtes à DB). Parfois, ce sera élevé en raison de paging. S'il n'y a pas de pagination en cours, vous n'avez probablement pas à vous en préoccuper du tout.

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

Donc, en résumé:

enter image description here

  1. Django semble bien
  2. Inadéquation entre la simultanéité du test de charge (100 ou 500) par rapport aux processus (16): vous introduisez beaucoup trop de demandes simultanées dans le système pour le nombre de processus à gérer. Une fois que vous êtes au-dessus du nombre de processus, il ne vous reste plus qu'à allonger la file d'attente des requêtes HTTP sur le serveur Web
  3. Il y a une grande latence, donc soit

    1. Inadéquation entre les processus (16) et les cœurs de processeur (1): si la moyenne de charge est> 3, c'est probablement trop de processus. Réessayez avec un plus petit nombre de processus

      1. Charge moyenne> 2 -> essayez 8 processus
      2. Charge moyenne> 4 -> essayez 4 processus
      3. Charge moyenne> 8 -> essayez 2 processus
    2. Si la moyenne de charge <3, elle peut être dans la base de données, alors profilez la base de données pour voir s'il y a des charges de petites requêtes (provoquant additivement la latence) ou une ou deux instructions SQL sont le problème

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

Développer des idées

Votre charge moyenne> 10 sur une seule machine à noyau est vraiment désagréable et (comme vous l'observez) entraîne beaucoup de changements de tâches et un comportement lent général. Personnellement, je ne me souviens pas avoir vu une machine avec une charge moyenne 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, je dirais donc que tout est clair maintenant.

Pagination : 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 manières. Par exemple, en haut, l'en-tête comporte des entrées et des sorties de page (voir la dernière ligne):

Processus: 170 au total, 3 en cours d'exécution, 4 bloqués, 163 en veille, 927 threads 15:06:31 
 Charge moyenne: 0,90, 1,19, 1,94 Utilisation du processeur: 1,37% utilisateur, 2,97% sys, 95,65% SharedLibs inactifs: 144M résidents, 0B données, 24M linkedit. 
 MemRegions: 31726 au total, 2541M résidents, 120M privés, 817M partagés. PhysMem: 1420M câblé, 3548M actif, 1703M inactif, 6671M utilisé, 1514M libre. 
 VM: 392G vsize, 1286M framework vsize, 1534241 (0) pageins, 0(0) Réseaux: paquets: 789684/288M en entrée, 912863/482M en sortie. Disques: 739807/15G en lecture, 996745/24G en écriture.

Nombre de processus : Dans votre configuration actuelle, le nombre de processus est façon trop élevé. Redimensionnez le nombre de processus à 2 . Nous pourrions augmenter cette valeur plus tard, en fonction du décalage de la charge de ce serveur.

Emplacement d'Apache Benchmark : La moyenne de charge de 1,85 pour un processus me suggère que vous exécutez le générateur de charge sur la même machine que uwsgi - est-ce que correct?

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 la mémoire et le CPU des processus Web pour les utiliser dans le générateur de charge. De plus, les 100 ou 500 threads 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 l'ensemble du test échoue.

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

Si je ne me trompe pas sur la base de données, la première et meilleure façon de commencer la mise à l'échelle consiste à déplacer la base de données vers une autre machine. Nous le faisons 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: la base de données a besoin de beaucoup de disque rapide, redondant et sauvegardé, et un nœud de traitement n'a besoin que d'un disque de base
    2. CPU: Un nœud de traitement a besoin du CPU le plus rapide que vous pouvez vous permettre, tandis qu'une machine DB peut souvent s'en passer (souvent ses performances sont gated sur disque et RAM)
    3. RAM: une machine DB a généralement besoin d'autant RAM que possible (et la base de données la plus rapide a tout 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. Mise à l'échelle: Les bases de données atomiques évoluent mieux en ayant des machines monstres avec de nombreux processeurs tandis que le niveau Web (sans état) peut évoluer en branchant de nombreux petits box identiques.
  2. Affinité CPU: Il est préférable que le CPU 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 de tâches. En séparant la base de données et les nœuds de traitement, vous appliquez cette affinité dans HW.

500 accès concurrents avec des exceptions La file d'attente des demandes dans le diagramme ci-dessus est au plus 100 - si uwsgi reçoit une demande lorsque la file d'attente est pleine, la demande est rejetée avec une erreur 5xx. Je pense que cela se produisait dans votre test de charge de 500 concurrents - essentiellement la file d'attente remplie avec les 100 premiers threads, puis les 400 autres threads ont émis les 900 demandes restantes et ont reçu immédiatement des erreurs 5xx.

Pour traiter 500 demandes par seconde, vous devez vous assurer de deux choses:

  1. La taille de la file d'attente des demandes est configurée pour gérer la rafale: utilisez le --listen argument à 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 plus petit nombre pour mieux gérer les attaques DDoS; si elles sont placées sous une charge énorme, la plupart des demandes échouent immédiatement avec presque aucun traitement permettant à la boîte dans son ensemble de toujours répondre 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 n'en discuterai pas ici. En maximisant le débit, vous essayez de maximiser le système, pas les composants individuels; certaines diminutions locales pourraient améliorer le débit global du système (par exemple, effectuer un changement qui ajoute une latence au niveau Web afin d'améliorer les performances de la base de données est un gain net).

Sur les spécificités:

  1. Déplacez la base de données vers une machine distincte . Ensuite, 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 pouvoir profiler. Le déplacement de la base de données vers une machine distincte introduira une latence supplémentaire (plusieurs ms) par demande, 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 de demandes uswgi est suffisamment grande pour gérer une rafale de trafic à l'aide de --listen argument. Cela devrait être plusieurs fois le nombre maximal de requêtes par seconde en régime permanent que votre système peut gérer.
  3. Au niveau web/application: Équilibrez le nombre de processus avec le nombre de cœurs CPU et la latence inhérente au processus. Trop de processus ralentissent les performances, trop peu signifie que vous n'utiliserez jamais pleinement les ressources du système. Il n'y a pas de point d'équilibrage fixe, car chaque application et modèle d'utilisation est différent, alors comparez et ajustez. À titre indicatif, utilisez la latence des processus, si chaque tâche a:

    • 0% de latence, alors vous avez besoin d'un processus par cœur
    • Latence de 50% (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 votre utilisation du processeur est supérieure à 90% (pour chaque cœur) et vous avez une moyenne de charge légèrement supérieure à 1,0. Si la moyenne de charge est plus élevée, réduisez les processus. Si tout se passe bien, à un moment donné, vous ne pourrez pas 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 donc 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
133
Andrew Alcock

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

Lorsque vous envoyez 500 demandes 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 demandes par défaut), vous voudrez peut-être augmenter cela, mais si les travailleurs ne peuvent pas traiter demande que la file d'attente rapide et d'écoute (également connue sous le nom de backlog) soit pleine, la pile réseau linux abandonnera la demande et vous commencerez à obtenir des erreurs.

Votre premier test de référence indique que vous pouvez traiter une seule demande en ~ 42 ms, de sorte qu'un seul travailleur pourrait traiter au plus 1000 ms/42 ms = ~ 23 demandes par seconde (si la base de données et les autres parties de la pile d'applications ne ralentissaient pas à mesure que la concurrence augmentait) . 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 n'en avez que 16, pas étonnant que cela casse sous une telle charge.

EDIT: J'ai un taux mixte avec simultanéité - au moins 21 employés vous permettront de traiter 500 demandes par seconde, pas 500 demandes simultanées. Si vous voulez vraiment traiter 500 demandes simultanées, vous avez simplement besoin de 500 employés. Sauf si vous exécutez votre application en mode asynchrone, consultez la section "Gevent" dans les documents uWSGI.

PS. uWSGI est livré avec un excellent équilibreur de charge avec autoconfiguration backend (lire les documents sous "Subscription Server" et "FastRouter"). Vous pouvez le configurer d'une manière qui vous permet de connecter à chaud un nouveau backend selon vos besoins, vous démarrez simplement les travailleurs sur un nouveau nœud et ils s'abonnent à FastRouter et commencent à recevoir des demandes. Il s'agit de la meilleure méthode de mise à l'échelle horizontale. Et avec les backends sur AWS, vous pouvez automatiser cela afin que de nouveaux backends soient démarrés rapidement en cas de besoin.

6
Łukasz Mierzwa

Ajouter plus de travailleurs et obtenir moins de r/s signifie que votre demande "est purement CPU" et qu'il n'y a pas IO attend qu'un autre travailleur puisse utiliser pour servir une autre demande.

Si vous souhaitez évoluer, vous devrez utiliser un autre serveur avec plus (ou plus) de processeurs.

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

1
barracel