こんにちは。Webエンジニアの篠原です。
世の中、二重実行できてはならない操作があります。例えば、
など…。
あってはならないですが、Webシステムを構築しているといつかは突き当たる問題です。
二重実行と言っても、大きく分けると2つのケースがある認識です。
手が滑って2回ボタンを押してしまったり、画面が更新されないからと2度3度とボタンを押してしまう。
意図したものか、そうでないかは関係なく、複数回同じ操作のリクエストがWebサーバーに送られてしまうと、未対策のシステムではその数だけ処理が実行されてしまいます。
どのように防げば良いか。
主にJavaScriptでの対策ですが、ボタンが押下されたらそのボタンを非活性にしたり、画面ロックをかけましょう。
また、処理中であることを示すインジケーター(ぐるぐるするマークとか)を表示したり、時間がかかる処理であればプログレスバーを表示したりするのも親切ですね。
サーバー側でのチェックではないため、根本的な対策ではありませんが、経験則上、抑止効果はかなりあります。
プラグインで比較的簡単にできる対策も多いので、ぜひ導入しましょう。
システムとしての整合性を保つためには、サーバー側のチェックも必ず行います。
リクエストごとに一度だけ有効なワンタイムトークンを発行し、使用済みであればRejectするなどの処理を追加します。
ワンタイムトークンの管理はサーバーセッションやRedisを使用することとなるでしょう(中にはDBで管理しているプロジェクトもありました)。
また、情報登録・更新やなんらかの状態変更後に画面遷移する場合(通常POSTリクエストなど)は、処理完了後にページのリダイレクトを行うようにしましょう。こうすることで、更新完了画面で画面を更新した場合も、二重でPOSTリクエストが発生するのを防ぐことができます。
仮に複数人がログインして使用するシステムがあって、まったく同じ操作を、複数人が同時に実行した場合は、上記の対策では防ぐことができません。
このようなケースを防ぐには、画面側ではなく、サーバー側で対策を行う必要があります。
お互いのリクエストが同時に実行されないよう排他制御をかけることとなります。
排他制御の仕組みとして、最も安全と考えられるのはDBのロック機構を使用することです。
トランザクションを利用した悲観的ロックは最も確実ですが、処理しているリクエスト以外を待たせてしまったり、デッドロックを引き起こしやすいなどの難点があります。よりデッドロックの可能性が少ない、ロックバージョンを用いた楽観的ロックが採用される場合もあります。
なお、DBを使用していない環境では、ファイルをロックオブジェクトとして使用するケースも多いです。
プロジェクト上でどこまでの精度が必要となるかによりますが、大半のアプリではここまで挙げてきた内容が最低限必要です。
一見すると簡単なWebシステムであっても、工数や予算がかかる背景には、お客さまからは直接要求されていない、こうした対策をしなければならない事情もあります。
フレームワークやライブラリで解決できるものはうまく活用していきたいですね。