Zusammenfassung
Eines unserer Themen in der Produktion einen Fehler getroffen persistierenden und jetzt InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction.
Fehler erzeugt, bei jeder Anfrage mit einer Abfrage, die es für den Rest seines Lebens dient! Es ist dies für Tage getan, jetzt! Wie ist das möglich und wie können wir verhindern, dass es weitergeht?Ungültige Transaktion über Anfragen
Hintergrund
Wir sind mit einem Fläschchen App auf uwsgi (4 Prozesse, 2 Themen), mit Flask-SQLAlchemy uns DB-Verbindungen zu SQL Server bereitstellt.
Das Problem schien, als einer unserer Fäden in der Produktion zu beginnen wurde abzureißen seine Forderung, in dieser Flask-SQLAlchemy Methode:
@teardown
def shutdown_session(response_or_exc):
if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
if response_or_exc is None:
self.session.commit()
self.session.remove()
return response_or_exc
... und irgendwie geschaffen self.session.commit()
zu rufen, wenn die Transaktion ungültig . Dies führte dazu, dass sqlalchemy.exc.InvalidRequestError: Can't reconnect until invalid transaction is rolled back
trotz unserer Protokollierungskonfiguration in stdout ausgegeben wurde, was sinnvoll ist, da es während des Herunterfahrens des Anwendungskontextes passiert ist, was niemals Ausnahmen auslösen sollte. Ich bin mir nicht sicher, wie die Transaktion wurde ungültig ohne response_or_exec
bekommen, aber das ist eigentlich das kleinere Problem AFAIK.
Das größere Problem ist, dass, wenn die "vorbereiteten" Zustand Fehler begonnen haben, und seitdem nicht aufgehört haben. Jedes Mal, wenn dieser Thread eine Anfrage bedient, die die DB trifft, 500s. Jeder zweite Thread scheint in Ordnung zu sein: Soweit ich das beurteilen kann, macht sogar der Thread, der sich im selben Prozess befindet, OK.
wilde Vermutung
Die SQLAlchemy Mailingliste einen Eintrag über die „‚vorbereiten‘Zustand“ Fehler hat sagen passiert es, wenn eine Sitzung gestartet begehen und hat noch nicht fertig, und etwas anderes versucht, es zu benutzen. Meine Vermutung ist, dass die Sitzung in diesem Thread nie zum self.session.remove()
Schritt kam, und jetzt wird es nie.
Ich habe immer noch das Gefühl, dass nicht erklärt, wie diese Sitzung über Anfragen obwohl persistent bleibt. Wir haben Flask-SQLAlchemys Verwendung von Request-Scoped-Sitzungen nicht geändert, daher sollte die Sitzung an den SQLAlchemy-Pool zurückgegeben und am Ende der Anfrage zurückgerollt werden, selbst wenn sie fehlerhaft sind (obwohl zugegebenermaßen wahrscheinlich nicht die erste ist), seit dem während des app-kontextes abreißen). Warum passieren die Rollbacks nicht? Ich könnte es verstehen, wenn wir jedes Mal die "ungültige Transaktion" -Fehler auf stdout (in uwsgis Logbuch) sehen würden, aber wir sind es nicht: Ich habe es nur einmal gesehen, das erste Mal. Aber ich sehe den "vorbereiteten Status" Fehler (in unserem App-Log) jedes Mal, wenn die 500s auftreten.
Konfigurationsdetails
Wir haben expire_on_commit
im session_options
ausgeschaltet, und wir haben auf SQLALCHEMY_COMMIT_ON_TEARDOWN
gedreht. Wir lesen nur aus der Datenbank, schreiben noch nicht. Wir verwenden auch Dogpile-Cache für alle unsere Abfragen (mit der Memcached-Sperre, da wir mehrere Prozesse haben und eigentlich zwei Server mit Lastenausgleich). Der Cache läuft jede Minute für unsere Hauptanfrage ab.
Aktualisiert 2014.04.28: Auflösung Schritte
Neustart der Server das Problem behoben, die nicht ganz überraschend zu haben scheint. Das heißt, ich erwarte, es wieder zu sehen, bis wir herausfinden, wie man es stoppt.benselme (unten) schlug vor, unseren eigenen Teardown-Callback mit Exception-Handling um das Commit zu schreiben, aber ich glaube, das größere Problem ist, dass der Thread für den Rest seines Lebens durcheinander gebracht wurde. Die Tatsache, dass diese nicht weggehen nach einer Anfrage oder zwei wirklich macht mich nervös!
Der Thread wurde durcheinander gebracht ** weil ** die 'Session' nicht' remove'd war. Ich persönlich würde Flask-SQLAlchemy ablehnen: Dieser Bug wird zum Beispiel schwer zu umgehen sein, und wenn Sie sich den github Repo ansehen, werden Sie sehen, dass er nicht mehr wirklich gepflegt wird. Außerdem gibt es nicht viel mehr, als was SQLAlchemy bereits tut. – benselme
@benselme: Eine wichtige Sache, die Flask-SQLAlchemy * bietet, sind Request-Scoped-Sitzungen.Wenn eine neue Anforderung kommt, sollte eine neue Sitzung generiert werden, unabhängig davon, ob die alte Sitzung entfernt wurde oder nicht. Das ist ein Teil des Grundes, warum das so seltsam ist: Wenn irgend etwas hätte, hätte eine DB-Verbindung zu lange geöffnet bleiben müssen, kein Thread in einem permanenten Fehlerzustand. Ich stimme zu, es ist komisch, wie ruhig Flask-SQLAlchemy ist, aber es ist * vom Flask-Autor geschrieben, also denke ich, dass es ziemlich stabil ist. –
@benselme, ich habe das gleiche über Flask-SQLAlchemy gefühlt. Und ich habe meinen eigenen "SQLAlchemy to Flask" Integrationscode geschrieben. Und schließlich sah ich mich mit dem gleichen seltsamen Verhalten konfrontiert, wie es in dieser Frage beschrieben wurde. –