Born Too Late

Yuya's old tech blog.

PHP で Reactor パターンを使った非同期 MySQL 問い合わせ

2012-08-20 16:10:41

最近は非同期処理に興味があります。

Reactor パターンというのは、非同期処理の実装パターンのひとつで、例えば HTTP 問い合わせなんかで「読み込みが完了するのを監視しつつその他のタスクを進め、読み込みが完了したタイミングでその結果を処理する」みたいなヤツです。
例えば Node.js では Reactor パターンを使ったプログラミングが比較的簡単に行えます。

PHP でも同じことをやりたい、というときには React というフレームワークがあって、少し前に話題になりました。

PHPでもリアルタイムWeb。node.php「React」

現状 React ではファイル I/O やソケット通信を非同期に行えるようになっております。
が、MySQL で非同期処理を行う方法は今の所提供されていません。

PHP でも mysqli_poll を使えば非同期に MySQL にクエリを投げられるということを知りました。
以下の記事が詳しいです。

PHPの非同期クエリで並行処理をやってみる

ただマニュアルのままの使い方だとさすがに辛いだろうと思ったので、コールバックを使って書きやすくするためのライブラリを書いてみました。

yuya-takeyama/async_mysql

これを使うと、例えばこんな感じに書けるようになります。

このように、Node.js っぽいインターフェイスで非同期問い合わせができます。

ローカルで試したい場合は以下のようにしてみてください。

localhost に root ユーザがパスワード無しでいることを前提にしているので、そうでない場合は以下のようにしてみてください。

$conn = $loop->connect('HOST', 'USER', 'PASSWORD');

この例では数秒 SLEEP() するだけのクエリをいくつか投げており、直列に実行した場合は SLEEP() の合計時間が全体の実行時間となってしまうところを、並列に実行することで、ほぼ同時に全てのクエリが発行され、一番長いクエリの分だけの実行時間で済みます。

ただし、実行するクエリそれぞれ別々にコネクションが確率されてしまうため、実際にちゃんと使うにはキューの仕組みが必要だと思われます。
例えば最大で 16 並列までしかクエリを投げない、みたいなことができないと、KEN_ALL.csv の全件 INSERT を試みるだけで too many connections になってしまうことでしょう。 (まだ試してないですが)

ただ、これの開発を継続して行うつもりはなくて、あくまでも実装の例として作っています。
最終的には React に組み込めたらいいなー、なんて思っているんですが、React がイベントループに使用している stream_select() は MySQL では使えないため、どうしたもんかというところです。
(libevent を使った実装もあるけど、そもそも libevent に対する理解が足りていないのでどうすべきかイメージが湧いてない)

とりあえずは Ruby の EventMachine や Perl の AnyEvent あたりのコードを読んでみようと思います。

ところで話は変わるのですが、React 上で子プロセスの実行を非同期に行うものを書いていて、React の中の人たちにレビューをいただきつつ開発を進めています。

Pull Request #61: WIP: ChildProcess by yuya-takeyama · react-php/react

今は API が固まってきたので、ユニットテストを書いているところです。
(こういう自分の中でチャレンジングなものを作るときは API がコロコロ変わって、テストファーストが全然うまくいかないので、とりあえずガーッと作ってその後にテストで固めて行く、というやり方で作ることが多いです)

これについてはマージされたら改めて紹介しようと思います。