まず、MySQLへの永続的な接続を維持するかどうかを決定する必要があります。後者の方がパフォーマンスは優れていますが、少しメンテナンスが必要です。
デフォルトwait_timeout
MySQLでは8時間です。接続がwait_timeout
より長くアイドル状態になっているときはいつでも 閉まっています。 MySQLサーバーを再起動すると、確立されたすべての接続も閉じます。したがって、持続的接続を使用する場合は、接続を使用する前に、接続が有効かどうかを確認する必要があります(有効でない場合は再接続します)。リクエストごとの接続を使用する場合、接続は常に新しいため、接続の状態を維持する必要はありません。
リクエストごとの接続
非永続的なデータベース接続には、着信HTTP要求ごとに、接続を開く、ハンドシェイクなど(データベースサーバーとクライアントの両方)の明らかなオーバーヘッドがあります。
これは、Flaskの公式チュートリアルデータベース接続について からの引用です。 :
ただし、アプリケーションコンテキスト リクエストごとに初期化されます(これは、効率の懸念とフラスコの用語によって覆い隠されています)。したがって、それはまだ非常に非効率的です。しかし、それはあなたの問題を解決するはずです。これは、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)
持続的接続
持続的接続データベース接続には、2つの主要なオプションがあります。接続のプールがあるか、接続をワーカープロセスにマップします。通常、Flask WSGIアプリケーションは、スレッド数が固定されたスレッドサーバー(uWSGIなど)によって提供されるため、スレッドマッピングはより簡単で効率的です。
パッケージ、DBUtils
があります 、と PersistentDB
の両方を実装します。
スレッドマップ接続用。
持続的接続を維持する上での重要な注意点の1つは、トランザクションです。再接続のAPIは、ping
。単一ステートメントの自動コミットには安全ですが、トランザクションの合間に中断する可能性があります(詳細こちら
)。 DBUtilsがこれを処理し、dbapi.OperationalError
でのみ再接続する必要があります およびdbapi.InternalError
(デフォルトでは、failures
によって制御されます PersistentDB
のイニシャライザーへ )トランザクション外で発生しました。
上記のスニペットは、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)
マイクロベンチマーク
パフォーマンスへの影響が数値にどのような影響を与えるかについて少し手がかりを与えるために、ここにマイクロベンチマークがあります。
実行しました:
-
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
そして、以下を介して同時実行1、4、8、16でそれらをロードテストしました:
siege -b -t 15s -c 16 http://localhost:5000/?city=london
観察(私のローカル構成の場合):
- 持続的接続は最大30%高速です
- 同時実行性4以上では、uWSGIワーカープロセスはCPU使用率の100%以上でピークに達します(
pymysql
MySQLプロトコルを純粋なPythonで解析する必要があります。これがボトルネックです)、 - 同時実行16では、
mysqld
のCPU使用率は、リクエストごとに最大55%、持続的接続に最大45%です。