Webアプリケーションは基本はHTTPリクエストごとにステートレスなので、いくらプログラムが並列で動いていても、それをほとんど意識せずに書けて簡単です。が、データを保存するとなると少々厄介です。ユーザーが自分専用のデータを変更するときは問題ありません。同じ共有データに同時に複数の人から書き込みがある場合が問題です。
といっても、WebではRDBを使って当たり前というこの時代、トランザクションという高級で暗黙な排他制御があるんだから任せればいいやと、同時書き込みを気にする人は少なくなりました。むかしはファイルシステムのロックを利用したりして、アクセスを排他制御したものです。
なーんだ、いまどきトランザクション切れないSQLなんてないから、排他制御なんてもう忘れていいんじゃん... とは言えない事情が最近出てきています。NoSQLを併用したとき、バックグラウンドに非同期のジョブキューがあるとき、などなど。それらが必要になる負荷事情があるという時点で、すでにアプリケーションサーバーは2台以上になっているでしょう。懐かしのファイルシステムロックでは不十分な状況が多くなってきました。
でも大丈夫、YiiフレームワークではMutexコンポーネントが提供されていて、さまざまな排他ロックの実装に切り替えて使えます。
ひとまずアプリケーションサーバーのファイルシステムを使った排他処理でいい場合
return [
// ...
'components' => [
'mutex' => [
'class' => 'yii\mutex\FileMutex'
],
],
]
$mutex = \Yii::$app->get('mutex');
$timeout = 10; // sec
if ($mutex->acquire('hoge-lock'), $timeout) {
// なにかする
$mutex->release();
} else {
throw new HogeException('ロック獲得に失敗しました');
}
で、10秒待ってだめならタイムアウトして例外を吐く排他制御ができます。
その後サーバを分散したくなったら、アプリケーションコードをそのまま変えず、コンポーネントを切り替えます。
return [
// ...
'components' => [
'mutex' => [
'class' => 'yii\mutex\MySqlMutex'
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:...',
'username' => '...',
'password' => '...',
],
],
],
]
MySQLの SELECT GET_LOCK(...)
を使ってロックを獲得してくれます。'db' =>
のところは、メインのと同じDB接続でいいなら、'db' => 'db'
のようにコンポーネント名を書いてもかまいません。
PostgreSQL版もありますが、pg_try_advisory_lock
がタイムアウトを指定できないので微妙でした。
他に、PHPの排他制御ライブラリには ninja-mutex があります。Yii以外では、これをフレームワークに統合するサードパーティーライブラリが多いようです。
やっていることはYiiのものとだいたい同じで、ファイルシステムとMySQLの他に、memcacheやRedisのドライバがありました。Yiiのコンポーネントはユーザーが継承していろいろ作れるので、Yiiでも ninja-mutex を使うバージョンを作ることもできますね。
PHPみたいなステートレスな言語でも、また、PHPを使うのが適しているHTTPリクエストの文脈でも、やはり排他制御は大事なので、Yiiが最初からフレームワークにその抽象型を持っているのは偉いなあと思いました。