Mysql
 sql >> Database >  >> RDS >> Mysql

Errore durante l'utilizzo di pymysql in flask

Prima di tutto, devi decidere se vuoi mantenere una connessione persistente a MySQL. Quest'ultimo ha prestazioni migliori, ma necessita di un po' di manutenzione.

Predefinito wait_timeout in MySQL è di 8 ore. Ogni volta che una connessione è inattiva per più di wait_timeout è chiuso. Quando il server MySQL viene riavviato, chiude anche tutte le connessioni stabilite. Pertanto, se si utilizza una connessione persistente, è necessario verificare prima di utilizzare una connessione se è attiva (e in caso contrario, riconnettersi). Se utilizzi la connessione per richiesta, non è necessario mantenere lo stato della connessione, perché le connessioni sono sempre aggiornate.

Per richiesta di connessione

Una connessione al database non persistente ha un evidente sovraccarico di apertura della connessione, handshaking e così via (sia per il server di database che per il client) per ogni richiesta HTTP in arrivo.

Ecco una citazione dal tutorial ufficiale di Flask riguardo alle connessioni al database :

Si noti, tuttavia, che contesto dell'applicazione viene inizializzato per richiesta (che è un po' velata da problemi di efficienza e dal gergo di Flask). E quindi, è ancora molto inefficiente. Comunque dovrebbe risolvere il tuo problema. Ecco uno snippet spogliato di ciò che suggerisce applicato 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)

Connessione persistente

Per una connessione al database di connessione persistente sono disponibili due opzioni principali. O hai un pool di connessioni o mappa le connessioni ai processi di lavoro. Poiché normalmente le applicazioni Flask WSGI sono servite da server threaded con un numero fisso di thread (ad es. uWSGI), la mappatura dei thread è più semplice ed efficiente.

C'è un pacchetto, DBUtils , che implementa entrambi e PersistentDB per connessioni mappate.

Un avvertimento importante per mantenere una connessione persistente sono le transazioni. L'API per la riconnessione è ping . È sicuro per il commit automatico di singole dichiarazioni, ma può causare interruzioni tra una transazione e l'altra (un po' di dettagli in più qui ). DBUtils si occupa di questo e dovrebbe riconnettersi solo su dbapi.OperationalError e dbapi.InternalError (per impostazione predefinita, controllato da failures all'inizializzazione di PersistentDB ) sollevato al di fuori di una transazione.

Ecco come apparirà lo snippet sopra 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

Per dare un piccolo indizio sulle implicazioni numeriche sulle prestazioni, ecco il micro-benchmark.

Ho corso:

  • 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 li ha testati con la simultaneità 1, 4, 8, 16 tramite:

siege -b -t 15s -c 16 http://localhost:5000/?city=london

Osservazioni (per la mia configurazione locale):

  1. Una connessione persistente è circa il 30% più veloce,
  2. In simultaneità 4 e versioni successive, il processo di lavoro uWSGI raggiunge un picco di oltre il 100% dell'utilizzo della CPU (pymysql deve analizzare il protocollo MySQL in puro Python, che è il collo di bottiglia),
  3. In simultanea 16, mysqld L'utilizzo della CPU è del 55% circa per ogni richiesta e del 45% circa per la connessione persistente.