Para la prueba, modifico los parámetros de MYSQL (RDS) de la siguiente manera;
wait_timeout = 40 (el valor predeterminado era 28800)
max_allowed_packet = 1 GB (máximo: solo para asegurarse de que el problema no sea causado por paquetes pequeños)
net_read_timeout = 10
tiempo de espera interactivo sin cambios
Luego probé mi aplicación sin las opciones de pool_pre_ping configuradas (el valor predeterminado es Falso), mantuve la aplicación inactiva durante 40 segundos, intenté iniciar sesión y obtuve
Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: Traceback (most recent call last): Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: File "/var/www/api_server/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1193, in _execute_context Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: context) Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: File "/var/www/api_server/venv/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 507, in do_execute Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: cursor.execute(statement, parameters) Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: File "/var/www/api_server/venv/lib/python3.6/site-packages/MySQLdb/cursors.py", line 206, in execute Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: res = self._query(query) Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: File "/var/www/api_server/venv/lib/python3.6/site-packages/MySQLdb/cursors.py", line 312, in _query Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: db.query(q) Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: File "/var/www/api_server/venv/lib/python3.6/site-packages/MySQLdb/connections.py", line 224, in query Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: _mysql.connection.query(self, query) Nov 14 20:05:20 ip-172-31-33-52 gunicorn[16962]: MySQLdb._exceptions.OperationalError: (2013, 'Lost connection to MySQL server during query') Se agregó pool_pre_ping de esta manera (usando la versión 2.4.1 de flask_sqlalchamy);
import os from flask import Flask from flask_sqlalchemy import SQLAlchemy as _BaseSQLAlchemy class SQLAlchemy(_BaseSQLAlchemy): def apply_pool_defaults(self, app, options): super(SQLAlchemy, self).apply_pool_defaults(app, options) options["pool_pre_ping"] = True # options["pool_recycle"] = 30 # options["pool_timeout"] = 35 db = SQLAlchemy() class DevConfig(): SQLALCHEMY_ENGINE_OPTIONS = {'pool_recycle': 280, 'pool_timeout': 100, 'pool_pre_ping': True} # These configs doesn't get applied in engine configs :/ DEBUG = True # SERVER_NAME = '127.0.0.1:5000' SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI_DEV') SQLALCHEMY_TRACK_MODIFICATIONS = False config = dict( dev=DevConfig, ) app = Flask(__name__, instance_relative_config=True) app.config.from_object(config['dev']) # INIT DATABASE db.init_app(app) with app.app_context(): db.create_all() -----------run.py app.run(host='127.0.0.1', port=5000) Con esto, ahora la aplicación web logra obtener una nueva conexión incluso después de que el servidor MySQL haya cerrado la conexión anterior. Siempre funciona bien cuando accedo a la base de datos justo después de que el servidor la cerró (intenté un máximo de 50 segundos después) ... pero cuando mantengo la conexión inactiva durante mucho tiempo (no lo he notado, pero ~> 10-15 min), nuevamente Veo el mismo error.
De acuerdo con los documentos (especialmente la sección Tratar con desconexiones ), la opción pool_pre_ping debería manejar este tipo de escenario en segundo plano. ¿O hay alguna otra variable de tiempo de espera que deba cambiar en el servidor MySQL?
Desde Flask-SQLAlchemy
Documentos de configuración :
Ciertos backends de bases de datos pueden imponer diferentes tiempos de espera de conexión inactiva, lo que interfiere con la agrupación de conexiones de Flask-SQLAlchemy.
De forma predeterminada, MariaDB está configurada para tener un tiempo de espera de 600 segundos. Esto a menudo resulta difícil de depurar, el entorno de producción solo tiene excepciones como
2013: Lost connection to MySQL server during query.Si usa un backend (o una base de datos como servicio preconfigurada) con un tiempo de espera de conexión más bajo, se recomienda que establezca
SQLALCHEMY_POOL_RECYCLEen un valor menor que el tiempo de espera de su backend.
El script citado en la pregunta muestra discrepancias entre su MySQL
timeout-configs ( wait_timeout , net_read_timeout ) y su SQLAlchemy
( pool_recycle , pool_timeout ) y Flask-SQLAlchemy
tiempos de espera ( SQLALCHEMY_POOL_RECYCLE , SQLALCHEMY_POOL_TIMEOUT ).
Podemos resolver esto usando la clase auxiliar DevConfig para coordinar las constantes de configuración de la conexión db en toda la aplicación. Para hacer eso, asignamos nuestra configuración a atributos estáticos y nos referimos a ellos para que no haya expectativas de tiempo de espera en conflicto. Aquí hay una implementación:
import os from flask import Flask from flask_sqlalchemy import SQLAlchemy as _BaseSQLAlchemy # Coordinate DevConfig with SQLAlchemy and Flask-SQLAlchemy (don't repeat yourself!) class DevConfig(): SQLALCHEMY_POOL_RECYCLE = 35 # value less than backend's timeout SQLALCHEMY_POOL_TIMEOUT = 7 # value less than backend's timeout SQLALCHEMY_PRE_PING = True SQLALCHEMY_ENGINE_OPTIONS = {'pool_recycle': SQLALCHEMY_POOL_RECYCLE, 'pool_timeout': SQLALCHEMY_POOL_TIMEOUT, 'pool_pre_ping': SQLALCHEMY_PRE_PING} DEBUG = True # SERVER_NAME = '127.0.0.1:5000' SQLALCHEMY_DATABASE_URI = os.getenv('SQLALCHEMY_DATABASE_URI_DEV') SQLALCHEMY_TRACK_MODIFICATIONS = False class SQLAlchemy(_BaseSQLAlchemy): def apply_pool_defaults(self, app, options): super(SQLAlchemy, self).apply_pool_defaults(app, options) options["pool_pre_ping"] = DevConfig.SQLALCHEMY_PRE_PING # options["pool_recycle"] = 30 # options["pool_timeout"] = 35 db = SQLAlchemy() config = dict( dev=DevConfig, ) app = Flask(__name__, instance_relative_config=True) app.config.from_object(config['dev']) # INIT DATABASE db.init_app(app) with app.app_context(): db.create_all()Si lo desea, puede verificar la diferencia de los cambios que hice: diffchecker.com/Q1e85Hhc
Establecí los siguientes ajustes:
SQLALCHEMY_ENGINE_OPTIONS = { 'pool_size': 10, 'pool_recycle': 60, 'pool_pre_ping': True }Los últimos meses han dejado de caer...