Резюме
Один из наших потоков в производстве попал в ошибку и теперь производит ошибки InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction.
по каждому запросу с запросом, который он обслуживает, на всю оставшуюся жизнь! Это делалось уже несколько дней! Как это возможно, и как мы можем предотвратить его продвижение?
Фон
Мы используем приложение Flask для uWSGI (4 процесса, 2 потока), а Flask-SQLAlchemy предоставляет нам подключения DB к SQL Server.
Проблема, казалось, начиналась, когда один из наших потоков в производстве срывал свой запрос внутри метода Flask-SQLAlchemy:
@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
... и каким-то образом удалось вызвать self.session.commit()
, когда транзакция была недействительной. Это привело к тому, что sqlalchemy.exc.InvalidRequestError: Can't reconnect until invalid transaction is rolled back
получал вывод в stdout, что противоречит нашей конфигурации ведения журнала, что имеет смысл, поскольку это произошло во время разрыва приложения, что никогда не должно приводить к исключениям. Я не уверен, как транзакция оказалась недействительной без response_or_exec
получения набора, но это на самом деле меньшая проблема AFAIK.
Большая проблема заключается в том, что когда началось "подготовленное состояние", ошибки и не прекратились. Каждый раз, когда этот поток обслуживает запрос, который попадает в БД, он 500s. Кажется, что каждый другой поток выглядит точным: насколько я могу судить, даже поток, который в том же процессе работает нормально.
Дикая догадка
В списке рассылки SQLAlchemy есть запись об ошибке "подготовленное состояние", в котором говорится, что это происходит, если сеанс начался и еще не закончен, а что-то еще пытается его использовать. Я предполагаю, что сеанс в этом потоке никогда не попадал на шаг self.session.remove()
, и теперь он никогда не будет.
Я все еще чувствую, что это не объясняет, как этот сеанс сохраняется через запросы. Мы не модифицировали использование флажков-SQLAlchemy сеансов с запросом, поэтому сеанс должен быть возвращен в пул SQLAlchemy и откат в конце запроса, даже те, которые являются ошибками (хотя, по общему признанию, вероятно, не первый, так как это увеличилось во время разговора приложения). Почему откаты не происходят? Я мог бы понять это, если бы каждый раз видели ошибки "недействительной транзакции" на stdout (в журнале uwsgi), но мы не: я видел это только один раз, в первый раз. Но я вижу ошибку "подготовленного состояния" (в нашем журнале приложений) каждый раз, когда происходят 500.
Сведения о конфигурации
Мы отключили expire_on_commit
в session_options
, и мы включили SQLALCHEMY_COMMIT_ON_TEARDOWN
. Мы только читаем из базы данных, а не пишем. Мы также используем Dogpile-Cache для всех наших запросов (с использованием блокировки memcached, так как у нас есть несколько процессов и, фактически, 2 сервера с балансировкой нагрузки). Кэш заканчивается каждую минуту для нашего основного запроса.
Обновлено 2014-04-28: Шаги разрешения
Перезапуск сервера, похоже, устранил проблему, что не удивительно. Тем не менее, я ожидаю увидеть это снова, пока мы не выясним, как остановить его. benselme (ниже) предложил написать наш собственный обратный вызов с разумом с обработкой исключений вокруг фиксации, но я чувствую, что большая проблема заключается в том, что поток был испорчен до конца своей жизни. Тот факт, что это не исчезло после того, как запрос или два действительно заставляет меня нервничать!