webentwicklung-frage-antwort-db.com.de

Schlechte Django / uwsgi Leistung

Ich starte eine Django App mit nginx & uwsgi. So starte ich 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-Konfigurationen:

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

Hier kommt das Problem. Wenn ich "ab" (ApacheBenchmark) auf dem Server mache, erhalte ich die folgenden Ergebnisse:

nginx-Version: Nginx-Version: Nginx/1.2.6

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

Während der Ausführung auf der Stufe 500 Parallelität

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

Wie Sie sehen können, schlagen alle Anfragen auf dem Server entweder mit Timeout-Fehlern oder "Client vorzeitig getrennt" fehl oder:

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

Hier ist ein bisschen mehr zu meiner Anwendung: Grundsätzlich handelt es sich um eine Sammlung von Modellen, die MySQL-Tabellen widerspiegeln, die den gesamten Inhalt enthalten. Am Frontend habe ich ein Django-Rest-Framework, das json-Content für die Kunden bereitstellt.

Ich habe die Django-Profiling & Django= debug toolbar installiert, um zu sehen, was los ist. Bei Django-Profiling bekomme ich Folgendes, wenn ich eine einzelne Anfrage starte:

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

Die Django-Debug-Toolbar zeigt jedoch Folgendes:

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

Das Problem ist, dass "top" zeigt, dass der Lastdurchschnitt schnell ansteigt und der Apache-Benchmark, den ich sowohl auf dem lokalen Server als auch von einem Remote-Computer im Netzwerk aus ausgeführt habe, zeigt, dass ich nicht viele Anfragen/Sekunde bediene. Worin besteht das Problem? Dies ist so weit ich erreichen könnte, wenn ich den Code profiliere. Es wäre also sehr dankbar, wenn jemand darauf hinweisen könnte, was ich hier tue.

Edit (23/02/2013): Hinzufügen weiterer Details basierend auf Andrew Alcocks Antwort: Die Punkte, die meine Aufmerksamkeit/Antwort erfordern, sind (3) (3) Ich habe "show global variables" für ausgeführt MySQL und fand heraus, dass MySQL-Konfigurationen 151 für die Einstellung max_connections hatten, was mehr als genug ist, um den Arbeitern zu dienen, die ich für uwsgi starte.

(3) (4) (2) Die einzelne Anfrage, die ich profiliere, ist die schwerste. Es führt 4 Abfragen gemäß Django-Debug-Toolbar aus. Was passiert ist, dass alle Abfragen ausgeführt werden in: 3,71, 2,83, 0,88, 4,84 ms.

(4) Hier beziehen Sie sich auf Memory Paging? wenn ja, wie könnte ich es sagen?

(5) Bei 16 Arbeitern, 100 Nebenläufigkeitsraten, 1000 Anfragen steigt der Lastdurchschnitt auf ~ 12. Ich habe die Tests mit einer unterschiedlichen Anzahl von Arbeitern durchgeführt (Nebenläufigkeitsstufe ist 100):

  1. 1 Arbeiter, durchschnittliche Beladung ~ 1,85, 19 Reqs/Sekunde, Zeit pro Anfrage: 5229,520, 0 nicht-2xx
  2. 2 Arbeiter, durchschnittliche Beladung ~ 1,5, 19 Reqs/Sekunde, Zeit pro Anfrage: 516,520, 0 Nicht-2xx
  3. 4 Arbeiter, durchschnittliche Beladung ~ 3, 16 Reqs/Sekunde, Zeit pro Anfrage: 5929.921, 0 nicht-2xx
  4. 8 Arbeiter, durchschnittliche Beladung ~ 5, 18 Reqs/Sekunde, Zeit pro Anfrage: 5301.458, 0 non-2xx
  5. 16 Arbeiter, durchschnittliche Beladung ~ 19, 15 Reqs/Sekunde, Zeit pro Anfrage: 6384.720, 0 nicht-2xx

Wie Sie sehen, haben wir umso mehr Systemlast, je mehr Mitarbeiter wir haben. Ich kann im Daemon-Protokoll von uwsgi sehen, dass sich die Antwortzeit in Millisekunden erhöht, wenn ich die Anzahl der Worker erhöhe.

Auf 16 Workern werden die Fehler protokolliert, wenn 500 Anfragen auf Parallelitätsebene ausgeführt werden:

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

Die Last steigt ebenfalls auf ~ 10. und die Tests dauern nicht lange, da Nicht-2xx-Antworten 923 von 1000 sind, weshalb die Antwort hier ziemlich schnell ist, da sie fast leer ist. Dies ist auch eine Antwort auf Ihren vierten Punkt in der Zusammenfassung.

Angenommen, ich stehe hier vor einer OS-Latenz, die auf E/A und Netzwerk basiert. Was ist die empfohlene Maßnahme, um dies zu skalieren? neue Hardware? größerer Server?

Vielen Dank

47
Maverick

BEARBEITEN 1 Sie haben den Kommentar gesehen, dass Sie 1 virtuellen Kern haben, der zu allen relavanten Punkten einen Kommentar hinzufügt

EDIT 2 Weitere Informationen von Maverick, damit ich ausgeschlossene Ideen eliminiere und die bestätigten Probleme weiterentwickle.

EDIT 3 Weitere Details zu Warteschlange und Skalierungsoptionen für uwsgi-Anforderungen wurden hinzugefügt. Verbesserte Grammatik.

EDIT 4 Updates von Maverick und kleinere Verbesserungen

Die Kommentare sind zu klein, deshalb hier ein paar Gedanken:

  1. Der Lastdurchschnitt gibt an, wie viele Prozesse ausgeführt werden oder auf die CPU-Aufmerksamkeit warten. Für ein perfekt geladenes System mit 1 CPU-Kern sollte der Lastdurchschnitt 1,0 betragen. Für ein 4-Kern-System sollte es 4.0 sein. In dem Moment, in dem Sie den Web-Test ausführen, werden die Threading-Raketen und Sie haben eine Menge Anzahl von Prozessen, die auf die CPU warten. Sofern der Lastdurchschnitt die Anzahl der CPU-Kerne nicht wesentlich überschreitet, ist dies kein Problem
  2. Der erste Wert für "Zeit pro Anforderung" von 4 Sekunden entspricht der Länge der Anforderungswarteschlange - 1000 Anforderungen wurden fast augenblicklich auf Django============================================================== Dies ist auf die sehr starke Abweichung zwischen der Anzahl der Anforderungen (100) und der Anzahl der Prozessoren (16) zurückzuführen, die dazu führt, dass 84 der Anforderungen zu einem beliebigen Zeitpunkt auf einen Prozessor warten.
  3. Bei einer Parallelität von 100 dauern die Tests 41 Sekunden bei 24 Anforderungen/Sekunde. Sie haben 16 Prozesse (Threads), sodass jede Anforderung ca. 700 ms lang verarbeitet wird. Bei Ihrer Art von Transaktion ist das eine lange Zeit pro Anfrage. Dies kann folgende Gründe haben:

    1. Die CPU-Kosten jeder Anforderung sind hoch in Django (was angesichts des niedrigen CPU-Werts in der Debug-Symbolleiste höchst unwahrscheinlich ist)
    2. Das Betriebssystem wechselt häufig Aufgaben (insbesondere wenn der Lastdurchschnitt höher als 4-8 ist), und die Latenz ist nur darauf zurückzuführen, dass zu viele Prozesse vorhanden sind.
    3. Es gibt nicht genügend DB-Verbindungen, die die 16 Prozesse bedienen, sodass Prozesse darauf warten, dass eine verfügbar ist. Haben Sie mindestens eine Verbindung pro Prozess?
    4. Es gibt beträchtlich Latenz um die DB auch:

      1. Dutzende kleiner Anfragen, die beispielsweise 10 ms dauern, wobei der größte Teil der Netzwerkkosten anfällt. Wenn ja, können Sie Caching einführen oder die SQL-Aufrufe auf eine kleinere Zahl reduzieren. Oder
      2. Eine oder mehrere Anfragen benötigen 100 ms. Führen Sie die Profilerstellung für die Datenbank aus, um dies zu überprüfen. In diesem Fall müssen Sie diese Anforderung optimieren.
  4. Die Aufteilung zwischen System- und Benutzer-CPU-Kosten ist im System ungewöhnlich hoch, obwohl die Gesamt-CPU niedrig ist. Dies impliziert, dass der Großteil der Arbeit in Django wie Netzwerk oder Festplatte kernelbezogen ist. In diesem Szenario kann es sich um Netzwerkkosten handeln (z. B. Empfangen und Senden von HTTP-Anforderungen und Empfangen und Senden von Anforderungen an die DB). Manchmal wird dies wegen Paging hoch sein. Wenn kein Paging stattfindet, müssen Sie sich darüber wahrscheinlich keine Gedanken machen.

  5. Sie haben die Prozesse auf 16 festgelegt, haben aber einen hohen Lastdurchschnitt (wie hoch du nicht angibst). Im Idealfall sollte immer mindestens one auf die CPU gewartet werden (damit sich die CPUs nicht im Leerlauf drehen). Die Prozesse hier scheinen nicht an die CPU gebunden zu sein, weisen jedoch eine erhebliche Latenz auf, sodass Sie mehr Prozesse als Kerne benötigen. Wieviele mehr? Versuchen Sie, das uwsgi mit einer unterschiedlichen Anzahl von Prozessoren (1, 2, 4, 8, 12, 16, 24 usw.) auszuführen, bis Sie den besten Durchsatz erzielen. Wenn Sie die Latenz des Durchschnittsprozesses ändern, müssen Sie diese erneut anpassen.
  6. Die 500-Parallelitätsstufe ist definitiv ein Problem, aber ist es der Client oder der Server? Der Bericht besagt, dass 50 (von 100) die falsche Inhaltslänge hatten, was ein Serverproblem impliziert. Der Nicht-2xx scheint auch dort zu zeigen. Ist es möglich, die Nicht-2xx-Antworten für das Debuggen zu erfassen - Stack-Traces oder die spezifische Fehlermeldung wären unglaublich nützlich (BEARBEITEN) und wird durch die uwsgi-Anforderungswarteschlange verursacht, die mit dem Standardwert 100 ausgeführt wird.

Also, zusammenfassend:

enter image description here

  1. Django scheint in Ordnung zu sein
  2. Nichtübereinstimmung zwischen Lasttest (100 oder 500) und Prozessen (16): Sie übertragen viel zu viele gleichzeitige Anforderungen in das System, als dass die Anzahl der zu verarbeitenden Prozesse ausreicht. Sobald Sie die Anzahl der Prozesse überschritten haben, wird lediglich die HTTP-Anforderungswarteschlange auf dem Webserver verlängert
  3. Es gibt also auch eine große Latenz

    1. Nichtübereinstimmung zwischen Prozessen (16) und CPU-Kernen (1): Wenn der Lastdurchschnitt> 3 ist, sind es wahrscheinlich zu viele Prozesse. Versuchen Sie es mit einer geringeren Anzahl von Prozessen erneut

      1. Lade Durchschnitt> 2 -> versuche 8 Prozesse
      2. Lade Durchschnitt> 4 -> versuche 4 Prozesse
      3. Lade Durchschnitt> 8 -> versuche 2 Prozesse
    2. Wenn der Lastdurchschnitt <3 ist, befindet er sich möglicherweise in der Datenbank. Profilieren Sie die Datenbank, um festzustellen, ob viele kleine Anforderungen (die zusätzlich die Latenz verursachen) vorliegen oder ein oder zwei SQL-Anweisungen das Problem sind

  4. Ohne die fehlgeschlagene Antwort zu erfassen, kann ich nicht viel über die Fehler bei 500 gleichzeitigen Zugriffen sagen

Ideen entwickeln

Ihre durchschnittliche Belastung> 10 auf einer Single-Cored-Maschine ist wirklich fies und führt (wie Sie beobachten) zu einer Menge Taskwechsel und allgemeinem langsamen Verhalten. Ich persönlich kann mich nicht erinnern, eine Maschine mit einer durchschnittlichen Auslastung von 19 gesehen zu haben (die Sie für 16 Prozesse haben) - herzlichen Glückwunsch, dass Sie so hoch gekommen sind;)

Die DB-Leistung ist großartig, deshalb würde ich das jetzt klarstellen.

Paging : Zur Beantwortung der Frage, wie Paging angezeigt wird, haben Sie verschiedene Möglichkeiten, OS-Paging zu erkennen. Zum Beispiel hat die Kopfzeile oben Ein- und Ausblendungen (siehe letzte Zeile):

Prozesse: 170 gesamt, 3 ausgeführt, 4 blockiert, 163 inaktiv, 927 Threads 15:06:31 
 Load Avg: 0,90, 1,19, 1,94 CPU-Auslastung: 1,37% Benutzer, 2,97% System, 95,65% inaktiv SharedLibs: 144 Millionen Einwohner, 0 Milliarden Daten, 24 Millionen Linkedit. 
 MemRegionen: 31726 insgesamt, 2541 Millionen Einwohner, 120 Millionen privat, 817 Millionen geteilt. PhysMem: 1420M verkabelt, 3548M aktiv, 1703M inaktiv, 6671M verwendet, 1514M frei. 
 VM: 392G vsize, 1286M Framework vsize, 1534241 (0) Pageins, 0(0) Pageouts. Netzwerke: Pakete: 789684/288M in, 912863/482M out. Datenträger: 739807/15G lesen, 996745/24G geschrieben.

Anzahl der Prozesse : In Ihrer aktuellen Konfiguration ist die Anzahl der Prozesse way zu hoch. Skaliere die Anzahl der Prozesse auf 2 zurück. Wir werden diesen Wert möglicherweise später ansprechen, je nachdem, wie weit die Last von diesem Server entfernt wird.

Position des Apache-Benchmarks : Der Lastdurchschnitt von 1,85 für einen Prozess deutet darauf hin, dass Sie den Lastgenerator auf demselben Computer wie uwsgi ausführen richtig?

Wenn ja, müssen Sie dies wirklich auf einem anderen Computer ausführen, da die Testläufe nicht für die tatsächliche Auslastung repräsentativ sind. Sie entnehmen den Webprozessen Speicher und CPU zur Verwendung im Lastgenerator. Darüber hinaus belasten die 100 oder 500 Threads des Lastgenerators Ihren Server im Allgemeinen auf eine Weise, die im wirklichen Leben nicht vorkommt. In der Tat könnte dies der Grund sein, warum der gesamte Test fehlschlägt.

Speicherort der Datenbank : Der Lastdurchschnitt für einen Prozess weist auch darauf hin, dass Sie die Datenbank auf demselben Computer wie die Webprozesse ausführen. Ist dies richtig?

Wenn ich in Bezug auf die Datenbank richtig bin, ist der erste und beste Weg, mit der Skalierung zu beginnen, die Datenbank auf einen anderen Computer zu verschieben. Wir tun dies aus mehreren Gründen:

  1. Ein DB-Server benötigt ein anderes Hardwareprofil als ein Verarbeitungsknoten:

    1. Festplatte: DB benötigt eine Menge schneller, redundanter, gesicherter Festplatten, und ein Verarbeitungsknoten benötigt nur eine Basisfestplatte
    2. CPU: Ein Verarbeitungsknoten benötigt die schnellste CPU, die Sie sich leisten können, wohingegen ein DB-Computer häufig darauf verzichten kann (häufig ist seine Leistung auf Festplatte und RAM beschränkt).
    3. RAM: Ein DB-Rechner benötigt im Allgemeinen so viel wie möglich RAM (und der schnellste DB hat alle seine Daten im RAM), während viele Verarbeitungsknoten viel weniger benötigen (Ihr benötigt ungefähr 20 MB pro Prozess - sehr klein
    4. Skalierung: Atomic DBs lassen sich am besten skalieren, indem sie über Monstermaschinen mit vielen CPUs verfügen, während die Webschicht (ohne Status) skaliert werden kann, indem viele identische kleine Boxen angeschlossen werden.
  2. CPU-Affinität: Es ist besser, wenn die CPU einen Lastdurchschnitt von 1,0 hat und Prozesse Affinität zu einem einzelnen Kern haben. Auf diese Weise wird die Verwendung des CPU-Cache maximiert und der Aufwand für das Umschalten von Aufgaben minimiert. Durch die Trennung von DB und Verarbeitungsknoten erzwingen Sie diese Affinität in HW.

500 gleichzeitig mit Ausnahmen Die Anforderungswarteschlange im obigen Diagramm ist höchstens 100 - Wenn uwsgi eine Anforderung empfängt, wenn die Warteschlange voll ist, wird die Anforderung mit abgelehnt ein 5xx Fehler. Ich denke, dies geschah in Ihrem 500-Parallelitätstest - im Grunde war die Warteschlange mit den ersten 100 oder so Threads gefüllt, dann gaben die anderen 400 Threads die verbleibenden 900 Anforderungen aus und erhielten sofort 5xx Fehler.

Um 500 Anfragen pro Sekunde zu bearbeiten, müssen Sie zwei Dinge sicherstellen:

  1. Die Größe der Anforderungswarteschlange ist für die Verarbeitung des Bursts konfiguriert: Verwenden Sie --listen Argument zu uwsgi
  2. Das System kann einen Durchsatz von über 500 Anforderungen pro Sekunde verarbeiten, wenn 500 ein normaler Zustand ist, oder ein bisschen darunter, wenn 500 eine Spitze ist. Siehe unten stehende Hinweise zur Skalierung.

Ich stelle mir vor, dass bei uwsgi die Warteschlange auf eine geringere Anzahl eingestellt ist, um DDoS-Angriffe besser handhaben zu können. Unter großer Last schlagen die meisten Anforderungen sofort fehl, und es wird fast keine Verarbeitung durchgeführt, sodass die Box als Ganzes weiterhin auf die Administratoren reagieren kann.

Allgemeine Hinweise zur Skalierung eines Systems

Ihre wichtigste Überlegung ist wahrscheinlich die Maximierung des Durchsatzes . Ein weiteres mögliches Bedürfnis, die Antwortzeit zu minimieren, wird hier jedoch nicht erörtert. Wenn Sie den Durchsatz maximieren, versuchen Sie, das system zu maximieren, nicht einzelne Komponenten. Einige lokale Abnahmen können den Gesamtdurchsatz des Systems verbessern (z. B. kann eine Änderung vorgenommen werden, die zu einer Verzögerung der Webschicht führt um die Leistung der Datenbank zu verbessern ist ein Nettogewinn).

Auf Besonderheiten:

  1. Verschieben Sie die Datenbank auf einen separaten Computer . Anschließend profilieren Sie die Datenbank während Ihres Auslastungstests, indem Sie top und Ihr bevorzugtes MySQL-Überwachungstool ausführen. Sie müssen sich profilieren können. Das Verschieben der Datenbank auf einen separaten Computer führt zu einer zusätzlichen Latenz (mehrere ms) pro Anforderung. Sie können also damit rechnen, die Anzahl der Prozesse auf der Webebene geringfügig zu erhöhen, um den gleichen Durchsatz zu erzielen.
  2. Stellen Sie sicher, dass die uswgi -Anforderungswarteschlange groß genug ist, um einen Datenverkehrsburst mit der --listen Streit. Dies sollte ein Mehrfaches der maximalen Dauerzustandsanforderungen pro Sekunde sein, die Ihr System verarbeiten kann.
  3. Auf der Web-/App-Ebene: Stimmen Sie die Anzahl der Prozesse mit der Anzahl der CPU-Kerne und der damit verbundenen Latenzzeit ab. Zu viele Prozesse verlangsamen die Leistung, zu wenig bedeutet, dass Sie die Systemressourcen nie vollständig auslasten. Es gibt keinen festen Abwägungspunkt, da jede Anwendung und jedes Verwendungsmuster unterschiedlich ist. Verwenden Sie als Richtlinie die Latenz der Prozesse, wenn jede Aufgabe Folgendes aufweist:

    • 0% Latenz, dann brauchen Sie 1 Prozess pro Kern
    • 50% Latenz (d. H. Die CPU-Zeit ist die Hälfte der tatsächlichen Zeit), dann benötigen Sie 2 Prozesse pro Kern
    • 67% Latenz, dann benötigen Sie 3 Prozesse pro Kern
  4. Überprüfen Sie top während des Tests, um sicherzustellen, dass Sie über 90% der CPU-Auslastung (für jeden Kern) liegen und Sie haben einen Lastdurchschnitt etwas über 1,0. Wenn der Lastdurchschnitt höher ist, reduzieren Sie die Prozesse. Wenn alles gut geht, können Sie dieses Ziel irgendwann nicht mehr erreichen, und die Datenbank ist möglicherweise der Engpass

  5. Irgendwann benötigen Sie mehr Leistung in der Webebene. Sie können entweder wählen, ob Sie der Maschine mehr CPU hinzufügen möchten (relativ einfach) und so weitere Prozesse hinzufügen möchten. und/oder , um weitere Verarbeitungsknoten hinzuzufügen ( horizontale Skalierbarkeit). Letzteres kann in uwsgi mit der Methode hier von Łukasz Mierzwa erreicht werden
133
Andrew Alcock

Bitte führen Sie Benchmarks viel länger als eine Minute durch (mindestens 5-10). Bei einem so kurzen Test werden Sie wirklich nicht viele Informationen erhalten. Verwenden Sie das Carbon-Plugin von uWSGI, um Statistiken auf den Carbon-/Graphit-Server zu übertragen (Sie benötigen eine). Sie erhalten viel mehr Informationen zum Debuggen.

Wenn Sie 500 gleichzeitige Anforderungen an Ihre App senden und diese nicht verarbeiten können, wird die Warteschlange für das Abhören in jedem Back-End ziemlich schnell gefüllt (standardmäßig sind es 100 Anforderungen). Sie können diese jedoch erhöhen, wenn die Mitarbeiter sie nicht verarbeiten können Anforderungen, bei denen die Warteschlange für schnelle und empfangene Nachrichten (auch als Backlog bezeichnet) voll ist, werden vom Linux-Netzwerkstapel Anforderungen gelöscht und es werden Fehler angezeigt.

Ihr erster Benchmark besagt, dass Sie eine einzelne Anforderung in ~ 42 ms verarbeiten können, sodass ein einzelner Worker maximal 1000 ms/42 ms = ~ 23 Anforderungen pro Sekunde verarbeiten kann (wenn die Datenbank und andere Teile des App-Stacks nicht langsamer werden, wenn die Parallelität zunimmt). . Um 500 gleichzeitige Anfragen zu bearbeiten, bräuchten Sie mindestens 500/23 = 21 Arbeiter (aber in Wirklichkeit würde ich sagen, mindestens 40). Sie haben nur 16, kein Wunder, dass es unter einer solchen Last kaputt geht.

BEARBEITEN: Ich habe Rate mit Parallelität gemischt - mindestens 21 Mitarbeiter ermöglichen es Ihnen, 500 Anforderungen pro Sekunde zu verarbeiten, nicht 500 gleichzeitige Anforderungen. Wenn Sie wirklich 500 gleichzeitige Anfragen bearbeiten möchten, benötigen Sie lediglich 500 Mitarbeiter. Wenn Sie Ihre App nicht im asynchronen Modus ausführen, überprüfen Sie den Abschnitt "Gevent" in den uWSGI-Dokumenten.

PS. uWSGI wird mit einem hervorragenden Load Balancer mit automatischer Konfiguration des Backends geliefert (siehe Dokumente unter "Subscription Server" und "FastRouter"). Sie können es so einrichten, dass Sie bei Bedarf ein neues Backend hot-plug-fähig machen. Sie starten nur Worker auf einem neuen Knoten, und sie abonnieren FastRouter und erhalten Anforderungen. Dies ist der beste Weg, um horizontal zu skalieren. Und mit Backends auf AWS können Sie dies automatisieren, sodass neue Backends bei Bedarf schnell gestartet werden.

6
Łukasz Mierzwa

Wenn Sie mehr Worker hinzufügen und weniger R/S erhalten, bedeutet dies, dass Ihre Anforderung "reine CPU" ist und keine Wartezeiten IO=========================================================.

Wenn Sie skalieren möchten, müssen Sie einen anderen Server mit mehr (oder schnelleren) CPUs verwenden.

Dies ist jedoch ein synthetischer Test. Die Anzahl der R/s, die Sie erhalten, ist die Obergrenze für die genaue Anforderung, die Sie testen. Sobald in der Produktion viele weitere Variablen vorhanden sind, die die Leistung beeinträchtigen können.

1
barracel