En primer lugar, debe decidir si desea mantener una conexión persistente con MySQL. Este último funciona mejor, pero necesita un poco de mantenimiento.
Predeterminado wait_timeout
en MySQL es de 8 horas. Siempre que una conexión esté inactiva más de wait_timeout
está cerrado. Cuando se reinicia el servidor MySQL, también cierra todas las conexiones establecidas. Por lo tanto, si usa una conexión persistente, debe verificar antes de usar una conexión si está viva (y si no, vuelva a conectarse). Si usa la conexión por solicitud, no necesita mantener el estado de la conexión, porque la conexión siempre está actualizada.
Conexión por solicitud
Una conexión de base de datos no persistente tiene una sobrecarga evidente de apertura de conexión, protocolo de enlace, etc. (tanto para el servidor de la base de datos como para el cliente) por cada solicitud HTTP entrante.
Aquí hay una cita del tutorial oficial de Flask sobre conexiones de bases de datos :
Tenga en cuenta, sin embargo, que contexto de aplicación se inicializa por solicitud (que está un poco velada por preocupaciones de eficiencia y la jerga de Flask). Y por lo tanto, sigue siendo muy ineficiente. Sin embargo, debería resolver su problema. Aquí hay un fragmento despojado de lo que sugiere aplicado a pymysql
:
import pymysql
from flask import Flask, g, request
app = Flask(__name__)
def connect_db():
return pymysql.connect(
user = 'guest', password = '', database = 'sakila',
autocommit = True, charset = 'utf8mb4',
cursorclass = pymysql.cursors.DictCursor)
def get_db():
'''Opens a new database connection per request.'''
if not hasattr(g, 'db'):
g.db = connect_db()
return g.db
@app.teardown_appcontext
def close_db(error):
'''Closes the database connection at the end of request.'''
if hasattr(g, 'db'):
g.db.close()
@app.route('/')
def hello_world():
city = request.args.get('city')
cursor = get_db().cursor()
cursor.execute('SELECT city_id FROM city WHERE city = %s', city)
row = cursor.fetchone()
if row:
return 'City "{}" is #{:d}'.format(city, row['city_id'])
else:
return 'City "{}" not found'.format(city)
Conexión persistente
Para una conexión de base de datos de conexión persistente, hay dos opciones principales. O tiene un grupo de conexiones o asigna conexiones a procesos de trabajo. Debido a que normalmente las aplicaciones WSGI de Flask son atendidas por servidores de subprocesos con un número fijo de subprocesos (por ejemplo, uWSGI), el mapeo de subprocesos es más fácil y eficiente.
Hay un paquete, DBUtils
, que implementa ambos y PersistentDB
para conexiones mapeadas por subprocesos.
Una advertencia importante para mantener una conexión persistente son las transacciones. La API para la reconexión es ping
. Es seguro para la confirmación automática de declaraciones individuales, pero puede interrumpirse entre una transacción (un poco más de detalles aquí
). DBUtils se encarga de esto y solo debe volver a conectarse en dbapi.OperationalError
y dbapi.InternalError
(por defecto, controlado por failures
al inicializador de PersistentDB
) generado fuera de una transacción.
Así es como se verá el fragmento anterior con PersistentDB
.
import pymysql
from flask import Flask, g, request
from DBUtils.PersistentDB import PersistentDB
app = Flask(__name__)
def connect_db():
return PersistentDB(
creator = pymysql, # the rest keyword arguments belong to pymysql
user = 'guest', password = '', database = 'sakila',
autocommit = True, charset = 'utf8mb4',
cursorclass = pymysql.cursors.DictCursor)
def get_db():
'''Opens a new database connection per app.'''
if not hasattr(app, 'db'):
app.db = connect_db()
return app.db.connection()
@app.route('/')
def hello_world():
city = request.args.get('city')
cursor = get_db().cursor()
cursor.execute('SELECT city_id FROM city WHERE city = %s', city)
row = cursor.fetchone()
if row:
return 'City "{}" is #{:d}'.format(city, row['city_id'])
else:
return 'City "{}" not found'.format(city)
Micro-benchmark
Para dar una pequeña pista de las implicaciones de rendimiento en números, aquí hay un micro-benchmark.
Corrí:
uwsgi --http :5000 --wsgi-file app_persistent.py --callable app --master --processes 1 --threads 16
uwsgi --http :5000 --wsgi-file app_per_req.py --callable app --master --processes 1 --threads 16
Y los probó con concurrencia 1, 4, 8, 16 a través de:
siege -b -t 15s -c 16 http://localhost:5000/?city=london
Observaciones (para mi configuración local):
- Una conexión persistente es aproximadamente un 30 % más rápida,
- En la simultaneidad 4 y superior, el proceso de trabajo de uWSGI alcanza un máximo de más del 100 % de la utilización de la CPU (
pymysql
tiene que analizar el protocolo MySQL en Python puro, que es el cuello de botella), - En la concurrencia 16,
mysqld
La utilización de la CPU es ~55 % por solicitud y ~45 % para conexión persistente.