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?
Antworten:
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 :
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
quelle
ping
und anderemysqladmin
Anfragen. 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!