2012-05-02 22 views
15

Ich habe einige Management-Befehle, die auf Gevent basieren. Da mein Verwaltungsbefehl tausende Anfragen enthält, kann ich alle Socket-Anrufe mit Gevent in nicht blockierende Anrufe umwandeln. Das beschleunigt meine Bewerbung wirklich, da ich gleichzeitig Anfragen stellen kann.Wie hilft PgBouncer, Django zu beschleunigen

Derzeit scheint der Engpass in meiner Anwendung Postgres zu sein. Das liegt daran, dass die Psycopg-Bibliothek, die für die Verbindung mit Django verwendet wird, in C geschrieben ist und asynchrone Verbindungen nicht unterstützt.

Ich habe auch gelesen, dass die Verwendung von pgBouncer Postgres um 2X beschleunigen kann. Das klingt großartig, aber es wäre toll, wenn jemand erklären könnte, wie pgBouncer funktioniert und hilft?

Dank

+0

Es besteht auch die Möglichkeit, dass Ihr Datenbankmodell nicht mit den Abfragen übereinstimmt, die Sie auslösen. Normalerweise ist der Netzwerk-Overhead im Vergleich zu der Arbeit, die zum Abrufen von Datenblöcken von der Festplatte benötigt wird, ebenfalls sehr klein: Dies kostet nicht die Leistung, sondern nur die Latenz. (außer vielleicht für den Fall sehr häufiger Verbindungen/Disconnect) – wildplasser

Antwort

65

Neben dem Speichern des Overheads von connect & trennen Sie, wo dies sonst bei jeder Anfrage getan wird, kann eine Verbindung Pooler eine große Anzahl von Client-Verbindungen auf eine kleine Anzahl von tatsächlichen Datenbankverbindungen Trichter. In PostgreSQL liegt die optimale Anzahl aktiver Datenbankverbindungen normalerweise irgendwo ((2 * core_count) + effective_spindle_count). Über dieser Zahl werden sowohl der Durchsatz als auch die Latenz schlechter.

Manchmal werden Leute sagen "Ich möchte 2000 Benutzer mit schneller Reaktionszeit unterstützen." Es ist ziemlich sicher, dass, wenn Sie versuchen, das mit 2000 tatsächlichen Datenbankverbindungen zu tun, Leistung wird schrecklich sein. Wenn Sie über einen Computer mit vier Quad-Core-Prozessoren verfügen und der aktive Datensatz vollständig zwischengespeichert ist, können Sie für diese 2000 Benutzer eine wesentlich bessere Leistung erzielen, indem Sie die Anforderungen über etwa 35 Datenbankverbindungen verwalten.

Um zu verstehen, warum das stimmt, sollte dieses Gedankenexperiment helfen. Stellen Sie sich einen hypothetischen Datenbankserver mit nur einer Ressource vor - einen einzelnen Kern. Dieser Kern wird gleichmäßig unter allen gleichzeitigen Anfragen ohne Overhead aufgeteilt. Nehmen wir an, dass 100 Anfragen alle gleichzeitig eintreffen, wobei jede eine Sekunde CPU-Zeit benötigt. Der Kern arbeitet auf allen von ihnen, zerteilt unter ihnen, bis sie alle 100 Sekunden später beenden. Betrachten Sie nun, was passiert, wenn Sie einen Verbindungspool vorgeben, der 100 Client-Verbindungen akzeptiert, aber jeweils nur eine Anfrage an den Datenbankserver stellt und alle Anfragen, die während der Verbindung in einer Warteschlange eingehen, anfordert. Wenn nun 100 Anfragen zur selben Zeit eintreffen, erhält ein Client eine Antwort in 1 Sekunde; ein anderer erhält eine Antwort in 2 Sekunden, und der letzte Client erhält eine Antwort in 100 Sekunden. Niemand musste länger warten, um eine Antwort zu erhalten, der Durchsatz ist der gleiche, aber die durchschnittliche Latenz beträgt 50,5 Sekunden statt 100 Sekunden.

Ein realer Datenbankserver hat mehr Ressourcen, die parallel verwendet werden können, aber das gleiche Prinzip gilt, sobald sie gesättigt sind, verletzen Sie nur die Dinge, indem Sie mehr gleichzeitige Datenbankanforderungen hinzufügen. Es ist tatsächlich schlimmer als das Beispiel, denn mit mehr Aufgaben haben Sie mehr Task-Switches, erhöhte Konflikte für Sperren und Cache, L2- und L3-Cache-Zeilenkonflikte und viele andere Probleme, die sowohl den Durchsatz als auch die Latenz verringern. Obendrein, während eine hohe work_mem Einstellung kann eine Abfrage auf eine Reihe von Möglichkeiten helfen, ist diese Einstellung das Limit pro Plan Knoten für jede Verbindung, so mit einer großen Anzahl von Verbindungen müssen Sie diese sehr klein zu vermeiden Leeren des Caches oder sogar das Auslagern, was zu langsameren Plänen oder zu Hash-Tabellen führt, die auf die Festplatte ausgelaufen sind.

Einige Datenbankprodukte bauen effektiv einen Verbindungspool in den Server auf, aber die PostgreSQL-Gemeinschaft hat die Position eingenommen, dass das beste Verbindungs-Pooling der Client-Software überlassen wird. Die meisten Pooler haben eine Möglichkeit, die Datenbankverbindungen auf eine feste Nummer zu begrenzen, während sie mehr gleichzeitige Clientanforderungen zulassen und sie in der Warteschlange anordnen. Dies ist, was Sie wollen, und es sollte auf einer Transaktion Basis, nicht pro Anweisung oder Verbindung erfolgen.

+1

Ausgezeichnete Antwort. Ich könnte nicht mehr zustimmen. – wildplasser

+0

Diese Front-End-Hippies wollen alle Verbindungen so schnell wie möglich machen und brechen und Verbindungspooler in den Vordergrund stellen, wenn sie ihren natürlichen Hochzustand nicht erreichen können. Ich mag die 2 * ncore + nspindel-Formel. Es wird davon ausgegangen, dass jeder Prozess beim Lesen einer Festplatte blockiert wird. – wildplasser

+0

@kgrittn Ich nehme an, in Ihrem Gedankenexperiment oben dauert jede Abfrage eine Sekunde, um in Abwesenheit anderer Anfragen ausgeführt zu werden? –

9

PgBouncer reduziert durch den Dienst als Proxy die Latenz Verbindungen bei der Einrichtung, die eine Verbindung Pool unterhält. Dies kann helfen, Ihre Anwendung zu beschleunigen, wenn Sie viele kurzlebige Verbindungen zu Postgres öffnen. Wenn Sie nur eine kleine Anzahl von Verbindungen haben, werden Sie keinen großen Gewinn sehen.

+0

Wenn ich diese Korrektheit verstanden habe - Django schafft immer noch Verbindungen, aber pgBouncer reduziert die Zeit, die benötigt wird, um diese Verbindung herzustellen. Ich habe gehört, dass Django für jede Anfrage eine neue Verbindung erstellt. Auf Anfrage meinen die Leute eine Web-Anfrage, um eine Seite zu holen (was bedeutet, dass jeder einzelne im Zyklus einer Ansicht ausgeführte Befehl eine einzige Datenbankverbindung durchläuft) oder eine Anfrage bedeutet jeden einzelnen Datenbanktreffer (SELECT, INSERT, UPDATE und DELETE)) In diesem Fall würde jeder einzelne Befehl innerhalb einer neuen Verbindung ausgeführt werden, obwohl sie im selben Ansichtszyklus wären. –

+2

Ja, Django erstellt eine neue Verbindung, aber die Verbindung wird schneller hergestellt, da sie zu einer lokalen PgBouncer-Instanz gehört. Django verwendet für jede Webanfrage eine neue Verbindung, keine Datenbankabfrage. –

+1

Sie finden vielleicht [diese Frage] (http://stackoverflow.com/questions/1125504/django-persistent-database-connection) hat einige weitere interessante Informationen. Beachten Sie jedoch, dass neue Verbindungen bei jeder Anfrage geöffnet werden. Wenn eine Anfrage auf einen Fehler stößt, ist es möglich, dass eine Transaktion (unter anderem) nicht ordnungsgemäß geschlossen wird und zu unerwarteten Ergebnissen führt. –