Em primeiro lugar, você precisa decidir se deseja manter uma conexão persistente com o MySQL. Este último tem melhor desempenho, mas precisa de um pouco de manutenção.
Padrão
wait_timeout
no MySQL é de 8 horas. Sempre que uma conexão estiver ociosa por mais de wait_timeout
ele está fechado. Quando o servidor MySQL é reiniciado, ele também fecha todas as conexões estabelecidas. Assim, se você usar uma conexão persistente, você precisa verificar antes de usar uma conexão se ela está ativa (e se não, reconecte). Se você usa conexão por solicitação, não precisa manter o estado de conexão, pois as conexões são sempre atualizadas. Conexão por solicitação
Uma conexão de banco de dados não persistente tem uma sobrecarga evidente de abertura de conexão, handshaking e assim por diante (para servidor de banco de dados e cliente) por cada solicitação HTTP recebida.
Aqui está uma citação do tutorial oficial do Flask sobre conexões de banco de dados :
Observe, no entanto, que o contexto do aplicativo é inicializado por solicitação (o que é meio que velado por preocupações de eficiência e a linguagem do Flask). E assim, ainda é muito ineficiente. No entanto, deve resolver o seu problema. Aqui está um trecho despojado do que sugere como aplicado ao
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)
Conexão persistente
Para uma conexão de banco de dados de conexão persistente, existem duas opções principais. Você tem um conjunto de conexões ou mapeia conexões para processos de trabalho. Como normalmente os aplicativos Flask WSGI são servidos por servidores encadeados com número fixo de encadeamentos (por exemplo, uWSGI), o mapeamento de encadeamentos é mais fácil e eficiente.
Há um pacote, DBUtils , que implementa ambos, e
PersistentDB
para conexões mapeadas por rosca. Uma advertência importante na manutenção de uma conexão persistente são as transações. A API para reconexão é
ping
. É seguro para auto-commit instruções únicas, mas pode atrapalhar entre uma transação (um pouco mais de detalhes aqui
). DBUtils cuida disso e só deve se reconectar em dbapi.OperationalError
e dbapi.InternalError
(por padrão, controlado por failures
para inicializador de PersistentDB
) levantadas fora de uma transação. Veja como o snippet acima ficará com o
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 uma pequena pista de quais são as implicações de desempenho em números, aqui está o micro-benchmark.
eu corri:
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
E testou-os com simultaneidade 1, 4, 8, 16 via:
siege -b -t 15s -c 16 http://localhost:5000/?city=london
Observações (para minha configuração local):
- Uma conexão persistente é aproximadamente 30% mais rápida,
- Na simultaneidade 4 e superior, o processo de trabalho do uWSGI atinge mais de 100% da utilização da CPU (
pymysql
tem que analisar o protocolo MySQL em Python puro, que é o gargalo), - Na simultaneidade 16,
mysqld
A utilização da CPU do é ~55% por solicitação e ~45% para conexão persistente.