libmysqlclient を利用した Python の MySQL ドライバである libmysqlclient-python を、 gevent, meinheld など非同期I/O + 軽量スレッド環境で使いやすいようにしました。
libmysqlclient の API について
libmysqlclient は基本的に同期IOのことしか考えられていません。
ですが、クエリを投げてそのステータスを返す mysql_real_query()
に限り、代替手段として mysql_send_query()
でクエリを送信してから、ステータスを待つ mysql_read_query_result()
を呼ぶという事ができます。
また、 MYSQL
型の net.fd
というメンバから fd を取得することができます。
これらを組み合わせて、
- mysql_send_query() でクエリを投げる
- fd を取得して、利用している非同期I/Oフレームワークの機能を使って、レスポンスが届き始めるのを待つ
- mysql_read_query_result() でステータスを確認する
- (成功なら) mysql_store_result() などで結果を利用する
という流れにすれば、クエリの結果を待つ時間をブロックせずに非同期I/Oフレームワークに戻ることができます。
接続時やクエリの送信中、結果の受信中はブロッキングI/Oになりますが
- 接続はプールすれば回数が減らせる
- クエリの送信はソケットバッファに乗る範囲ではブロックしない
- 結果の受信は、ステータスの受信ができた段階で MySQL はクエリを実行完了していてあとは全力で送信してくれるので、ブロッキングI/Oで受信してもあまり無駄にブロックする時間は無い
ので、クエリの実行時間を待てるだけでも十分効果はあります。
mysqlclient-python の使い方
connection クラスに、上で紹介した3つの低レベルAPIを追加し、それに加えて waiter という mysql_read_query_result() 前に呼ばれる callback を指定できるようにしました。
この waiter は同期的に呼ばれるので、非同期I/Oに greenlet を組み合わせた gevent や meinheld などで利用できます。
greenlet を使えば Tornado や asyncio でも使えるはずですが、そちらはまだ用意していません。