Auswählen von DB pool_size für eine Flask-SQLAlchemy-App, die auf Gunicorn ausgeführt wird

8

Ich habe eine Flask-SQLAlchmey-App in Gunicorn, die mit einer PostgreSQL-Datenbank verbunden ist, und ich habe Probleme herauszufinden, wie hoch der pool_sizeWert sein sollte und wie viele Datenbankverbindungen ich erwarten sollte.

So verstehe ich, wie die Dinge funktionieren:

  • Prozesse in Python 3.7 teilen NICHT den Speicher
  • Jeder Gunicorn-Arbeiter ist ein eigener Prozess
  • Daher erhält jeder Gunicorn-Mitarbeiter eine eigene Kopie des Datenbankverbindungspools und wird nicht mit anderen Mitarbeitern geteilt
  • Threads in Python teilen sich den Speicher
  • Daher teilen sich alle Threads in einem Gunicorn-Mitarbeiter einen Datenbankverbindungspool

Ist das soweit richtig? Wenn das richtig ist, dann für eine synchrone Flask-App, die in Gunicorn ausgeführt wird:

  • Ist die maximale Anzahl von Datenbankverbindungen = (Anzahl der Worker) * (Anzahl der Threads pro Worker)?
  • Und wird ein Arbeiter jemals mehr Verbindungen aus einem Pool verwenden, als es Arbeiter gibt?

Gibt es einen Grund, warum pool_sizegrößer als die Anzahl der Threads sein sollte? Also, für eine Gunicorn App gunicorn --workers=5 --threads=2 main:appsollte pool_sizemit 2 gestartet werden? Und wenn ich nur Arbeiter benutze und keine Threads, gibt es einen Grund, eine pool_sizegrößer als 1 zu haben?

Joshmaker
quelle

Antworten:

3

Meine 2 Cent hinzufügen. Ihr Verständnis ist richtig, aber einige Gedanken zu beachten:

  • Wenn Ihre Anwendung E / A-gebunden ist (z. B. mit der Datenbank spricht), möchten Sie wirklich mehr als einen Thread haben. Andernfalls wird Ihre CPU niemals 100% der Auslastung erreichen. Sie müssen mit der Anzahl der Threads experimentieren, um die richtige Menge zu erhalten, normalerweise mit dem Lasttest-Tool und dem Vergleich der Anforderungen pro Sekunde und der CPU-Auslastung.

  • Wenn Sie die Beziehung zwischen der Anzahl der Mitarbeiter und den Verbindungen berücksichtigen, können Sie sehen, dass Sie beim Ändern der Anzahl der Mitarbeiter die maximale Poolgröße anpassen müssen. Dies kann leicht vergessen werden. Daher ist es möglicherweise eine gute Idee, die Poolgröße etwas über der Anzahl der Mitarbeiter festzulegen, z. B. das Doppelte dieser Anzahl.

  • postgresql erstellt einen Prozess pro Verbindung und ist möglicherweise nicht gut skalierbar, wenn Sie viele Gunicorn-Prozesse haben. Ich würde mit einem Verbindungspool gehen, der sich zwischen Ihrer App und der Datenbank befindet (pgbouncer ist wohl der beliebteste).

Matino
quelle
3

Ich füge nur einige meiner jüngsten Erfahrungen zu @ matino hinzu Antwort hinzu . WSGI-Anwendungen können auch von asynchronen Mitarbeitern profitieren. Ich werde einige Punkte über async workersund connection poolshier hinzufügen .

Wir hatten kürzlich einige ähnliche Probleme bei unserer Produktion. Unser Verkehr sprang in 1-2 Tagen in den Himmel und alle Anfragen wurden aus irgendeinem Grund verstopft. Wir haben Gunicorn mit geventasynchronen Arbeitern für unsere djangoAnwendung verwendet. Es stellte sich heraus, dass psql-Verbindungen der Grund dafür waren, dass viele der Anfragen blockiert wurden (und schließlich eine Zeitüberschreitung auftraten).

Die vorgeschlagene Nummer gleichzeitiger Anforderungen beträgt (2*CPU)+1. In einem Synchronisierungsszenario lauten Ihre Berechnungen wie folgt:(workers_num * threads_num) <= (2 * cores_num) + 1

Und Sie erhalten (workers_num * threads_num)maximale Verbindungen zu Ihrer Datenbank. (sagen wir, alle Anfragen haben Datenbankabfragen). Daher müssen Sie Ihre psql- pool_sizeEinstellung auf einen Wert einstellen, der größer als diese Zahl ist. Wenn Sie jedoch asynchrone Worker verwenden, sind die Berechnungen etwas anders. Schauen Sie sich diesen Gunicorn-Befehl an:

gunicorn --worker-class=gevent --worker-connections=1000 --workers=3 django:app

In diesem Fall kann die maximale Anzahl gleichzeitiger Anforderungen bis zu 3000Anforderungen betragen . Sie sollten also Ihre Einstellungen vornehmen müssenpool_size etwas Größeres als3000 . Wenn Ihre Anwendung an E / A gebunden ist, erzielen Sie mit asynchronen Mitarbeitern eine bessere Leistung. Auf diese Weise können Sie Ihre CPU effizienter nutzen.

Wenn Sie eine Lösung wie das Pooling von Verbindungen verwenden PgBouncer, müssen Sie nicht mehr ständig Verbindungen öffnen und schließen. Es hat also keinen Einfluss auf Ihre Entscheidung, Ihre Einstellungen vorzunehmen pool_size. Die Auswirkungen sind bei geringem Verkehrsaufkommen möglicherweise nicht spürbar, es ist jedoch erforderlich, höhere Verkehrsraten zu bewältigen.

Nima
quelle
2

Ich würde sagen, dein Verständnis ist ziemlich gut. Threads innerhalb eines einzelnen WSGI-Workers teilen sich tatsächlich einen Verbindungspool. Theoretisch ist also die maximale Anzahl von Datenbankverbindungen (number of workers) * Nwo N = pool_size + max_overflow. (Ich bin nicht sicher, auf was Flask-SQLAlchemy max_overflow setzt, aber es ist ein wichtiger Teil der Gleichung hier - siehe Bedeutung in der QueuePool-Dokumentation .)

In der Praxis haben Sie maximal eine Verbindung pro Thread, wenn Sie immer nur die von Flask-SQLAlchemy bereitgestellte Sitzung mit Thread-Bereich verwenden. Wenn also Ihre Thread-Anzahl geringer ist als Ndann, wird Ihre Obergrenze tatsächlich sein (number of workers) * (number of threads per worker).

Anthony Carapetis
quelle