Fehler 2006: Der MySQL-Server ist verschwunden

8

Ich führe eine Python Pyramid-App auf einem CentOS-Server mit uWSGI und nginx aus. Ich verwende SQLAlchemy als ORM, MySQLdb als API und MySQL als Datenbank. Die Website ist noch nicht online gegangen, daher sind nur ich und einige andere Mitarbeiter des Unternehmens betroffen. Wir haben einige Daten gekauft, um die Datenbank zu füllen, sodass die größte (und am häufigsten abgefragte) Tabelle ~ 150.000 Zeilen umfasst.

Gestern habe ich schnell hintereinander vier neue Registerkarten der Website geöffnet und ein paar 502 Bad Gateway-Fehler erhalten. Ich habe im uWSGI-Protokoll nachgesehen und Folgendes festgestellt:

sqlalchemy.exc.OperationalError: (OperationalError) (2006, 'MySQL server has gone away') 'SELECT ge...

Wichtiger Hinweis: Dieser Fehler ist nicht auf das wait_timeout von MySQL zurückzuführen. Kenne ich schon.

Ich fragte mich, ob das Problem durch gleichzeitige Anfragen verursacht wurde, die gleichzeitig bearbeitet wurden. Ich habe mich zum Lasttester eines armen Mannes gemacht:

for i in {1..10}; do (curl -o /dev/null http://domain.com &); done;

Sicher genug, innerhalb dieser zehn Anfragen würde mindestens eine einen Fehler von 2006 auslösen, oft mehr. Manchmal wurden die Fehler sogar noch seltsamer, zum Beispiel:

sqlalchemy.exc.NoSuchColumnError: "Could not locate column in row for column 'table.id'"

Wenn die Spalte definitiv existiert und bei allen anderen identischen Anforderungen einwandfrei funktioniert hat. Oder dieses:

sqlalchemy.exc.ResourceClosedError: This result object does not return rows. It has been closed automatically.

Als es wieder einmal für alle anderen Anfragen gut funktionierte.

Um weiter zu überprüfen, ob das Problem auf gleichzeitige Datenbankverbindungen zurückzuführen ist, habe ich uWSGI auf einen einzelnen Worker festgelegt und Multithreading deaktiviert, sodass die Anforderungen einzeln verarbeitet werden müssen. Sicher genug, die Probleme verschwanden.

Um das Problem zu finden, habe ich ein Fehlerprotokoll für MySQL eingerichtet. Mit Ausnahme einiger Hinweise beim Start von MySQL bleibt es leer.

Hier ist meine MySQL-Konfiguration:

[mysqld]
default-storage-engine = myisam
key_buffer = 1M
query_cache_size = 1M
query_cache_limit = 128k
max_connections=25
thread_cache=1
skip-innodb
query_cache_min_res_unit=0
tmp_table_size = 1M
max_heap_table_size = 1M
table_cache=256
concurrent_insert=2
max_allowed_packet = 1M
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
thread_stack = 64K
innodb_file_per_table=1
log-error=/var/log/mysql/error.log

Starkes Googeln über den Fehler ergab wenig, schlug aber vor, dass ich max_allowed_packet erhöhe. Ich habe es auf 100 Millionen erhöht und MySQL neu gestartet, aber das hat überhaupt nicht geholfen.

Zusammenfassend: Gleichzeitige Verbindungen zu MySQL verursachen 2006, 'MySQL server has gone away'und einige andere seltsame Fehler. Das MySQL-Fehlerprotokoll enthält keine relevanten Informationen.

Ich habe stundenlang daran gearbeitet und keine Fortschritte gemacht. Kann mir jemand helfen?

Theron Luhn
quelle
Stellt jeder Thread (oder Prozess oder was auch immer) bei gleichzeitigen Anforderungen eine eigene Verbindung zur Datenbank her?
DerfK
Jeder Prozess verfügt über einen Verbindungspool, der von SQLAlchemy verwaltet wird. Daher sollte jede Anforderung eine eigene Verbindung haben.
Theron Luhn
Noch ein Hinweis: Der Lasttest verursacht keine Probleme auf meinem lokalen Entwicklungsserver, nämlich Kellnerin für den Server und MySQL für die Datenbank.
Theron Luhn

Antworten:

18

Ich bin auch darauf gestoßen und habe den Grund und die Lösung gefunden.

Der Grund dafür ist, dass das Python-Uwsgi-Plugin (oder wahrscheinlich alle Uwsgi-Plugins) neue Worker gibt, nachdem die Anwendung in das übergeordnete Element geladen wurde. Infolgedessen erben die untergeordneten Elemente alle Ressourcen (einschließlich Dateideskriptoren wie die Datenbankverbindung) vom übergeordneten Element.

Sie können dies kurz im uwsgi-Wiki nachlesen :

uWSGI versucht, die Kopie von fork () beim Schreiben zu missbrauchen, wann immer dies möglich ist. Standardmäßig wird es nach dem Laden Ihrer Anwendungen gegabelt. Wenn Sie dieses Verhalten nicht möchten, verwenden Sie die Option --lazy. Wenn Sie es aktivieren, wird uWSGI angewiesen, die Anwendungen nach dem Fork jedes Arbeitnehmers zu laden ()

Und wie Sie vielleicht wissen, sind Pythons mysqldb-Verbindungen und -Cursor nur dann threadsicher, wenn Sie sie ausdrücklich schützen. Daher beschädigen mehrere Prozesse (wie die uwsgi-Worker), die dieselbe MySQL-Verbindung / denselben Cursor verwenden, diese gleichzeitig.

In meinem Fall (für die Gold- API von King Arthur ) funktionierte dies einwandfrei, als ich die MySQL-Verbindung pro Anforderung im Bereich eines anderen Moduls erstellte. Wenn ich jedoch dauerhafte Verbindungen zur Leistungssteigerung wünschte, habe ich die Datenbankverbindung und den Cursor auf den globalen Bereich in verschoben das übergeordnete Modul. Infolgedessen traten meine Verbindungen so aufeinander wie Ihre.

Die Lösung hierfür besteht darin, das Schlüsselwort "faul" (oder die Befehlszeilenoption --lazy) zu Ihrer uwsgi-Konfiguration hinzuzufügen. Infolgedessen wird die Anwendung für jedes Kind neu gegabelt, anstatt vom Elternteil gegabelt zu werden und die Verbindung freizugeben (und irgendwann darauf zu treten, sodass der MySQL-Server das Schließen aufgrund einer beschädigten Anforderung irgendwann erzwingt).

Wenn Sie dies tun möchten, ohne Ihre uwsgi-Konfiguration zu ändern, können Sie wahrscheinlich den @ postfork-Dekorator verwenden, um unmittelbar nach dem Verzweigen eines Arbeitsprozesses eine neue Datenbankverbindung ordnungsgemäß zu erstellen. Sie können darüber lesen Sie hier .

Ich sehe aus Ihrem Follow-up, dass Sie bereits zu pgsql gewechselt sind, aber hier ist die Antwort, damit Sie nachts besser schlafen können und für jeden wie Sie und mich, der versucht, die Antwort darauf zu finden!

PS Nachdem ich das Problem verstanden hatte (der Cursor war beschädigt, weil die Arbeiter aufeinander traten), aber das Problem mit fork () und --lazy nicht erkannte, überlegte ich, meinen eigenen Pool dort zu implementieren, wo die Arbeiter " Überprüfen Sie "eine MySQL-Verbindung aus einem Pool im globalen Bereich und dann" erneut einchecken ", bevor Sie application () beenden. Es ist jedoch wahrscheinlich viel besser, --lazy zu verwenden, es sei denn, Ihre Web- / Anwendungslast variiert so stark, dass Sie ständig sind Schaffung neuer Arbeitskräfte. Selbst dann würde ich vielleicht --lazy bevorzugen, weil es wesentlich sauberer ist als die Implementierung eines eigenen Datenbankverbindungspools.

Bearbeiten: Hier finden Sie eine ausführlichere Beschreibung dieses Problems + der Lösung, da es für andere, die darauf gestoßen sind, an Informationen mangelt: http://tns.u13.net/?p=190

FliesLikeABrick
quelle
Es ist auf jeden Fall gut zu wissen, was das verursacht hat. Vielen Dank!
Theron Luhn
Wirf einfach raus, dass dieser Beitrag genau das gleiche Problem war, das ich auch hatte, und deine Lösung hat es behoben :) Danke!
MasterGberry
"Daher werden mehrere Prozesse (wie die uwsgi-Worker), die dieselbe MySQL-Verbindung / denselben Cursor verwenden, diese gleichzeitig beschädigen." Das war sehr lehrreich. Ich hatte zwei Verbindungen zu derselben Datenbank auf meiner lokalen (eine von einer Shell, eine von meiner wsgi-Anwendung) und bekam diesen Fehler. Die Datenbank meldete sich lebend an pingund andere mysqladminAnfragen. Es war wahrscheinlich, weil ich versucht habe, die Datenbank aus der Shell zu löschen ... aber es gab immer wieder den Fehler "Server ist weg" für diesen Befehl. Trotzdem danke!
Brian Peterson
du hast mein Leben gerettet.
Blutegel