トラブル知らずのシステム設計 エラー制御・排他制御編を読み、排他制御について理解しイメージできる形でまとめました。
目次
排他制御
共有資源に対して複数トランザクションからの操作が見込まれる場合に、同時アクセスにより不整合が発生することを防ぐため、あるトランザクションから共有資源にアクセスしている時は他のトランザクションからはアクセスできないように直列に処理されるようにする制御のこと
つまり、システムを複数の利用者が同時に操作するような場合に、一貫して処理するために必要な制御機構が排他制御です。データの一貫性を保証する必要のある範囲(業務トランザクション)を検討し、その範囲で排他制御します。
例えばチームで共有して編集しているファイルがあったとして、メンバー同士で同時に編集してしまった場合データ不整合が起きてしまいます。そのような事態を防ぐために、あるメンバーがファイルを編集している時は、他のメンバーはそのファイルを編集できないようにする仕組みが必要です。Excelファイルの共有ではファイルごとやセルごとで排他制御を行、データ不整合を防いでいます。
楽観ロックと悲観ロック
楽観ロックは処理の終了時、悲観ロックは開始時に競合を検知するイメージです。実際は以下のようなタイミングでロックをしています。
- 楽観ロックは、ロックキー比較時
- 悲観ロックは、ロック状態確認時
- 行ロックは、ロックSQL発行時
楽観ロック
楽観ロックとは、他の操作による同時更新はよほどの限り起きないであろうという楽観的な前提の排他制御のことです。
楽観ロックでは「ロックキー」を使います。楽観ロック対象のデータにロックキーという属性を設け、データを更新するたびにロックキーを変更します。業務トランザクション開始時点と終了時点でロックキーの内容が変わっていなければ、他の操作でデータが更新されていないと判断できます。
楽観ロック対象データの何をロックキーとするか
ロックキーとしてよく候補になるのは「バージョン」と「タイムスタンプ」です。
- 「バージョン」の場合は、データを更新するたびに数値を加算し、その数値を比較します。
→ 更新したら1→2のようにインクリメントするイメージ - 「タイムスタンプ」の場合は、データを更新するたびに更新時刻を上書きし、その時刻を比較します。
→ updated_atのようなカラムを用意し、更新時刻を上書きするイメージ
タイムスタンプはデフォルトのテーブルで持っていたりするので、新たにカラムを追加せず採用しがちですが、ロックキーとして「バージョン」を推奨します。なぜなら「タイムスタンプ」は秒単位までしから保有していない場合、同一秒に複数の操作でデータを更新されると楽観ロックの判断ができなくなり、データ不整合が発生します。
それに対して「バージョン」は、更新時に単純に加算される数値であるため、十分な桁数を持っておけば、複数の操作で同一値になることはないからです。
楽観ロックは同一データの更新頻度が高い時は適さない
楽観ロックは業務トランザクションの終了のタイミングで排他制御を実現する方法であり、操作が終わるタイミングではじめて他の操作による更新があったことがわかります。楽観ロックは実際のシステムによく採用されますが、同一データの更新頻度が高いケースには適さず、代わりに悲観ロックが採用されます。
悲観ロック
他の操作による同じデータの変更が頻繁に起こるであろうという悲観的な前提の排他制御のことです。業務トランザクションの開始のタイミングで排他制御を実現します。
悲観ロックは、ロック対象のデータにロックしているユーザとロック状態を表す属性を設けます。同一ユーザによる複数端末からの同時ログインを許容しているようなシステムでは、セッションIDのようにどの端末からのアクセスかを識別できる情報も必要です。
悲観ロックの仕組み
業務トランザクションが始まると、データのロック状態を参照し、他のユーザによってデータがロックされていないことを確認します。他のユーザによってデータがロックされていた場合、悲観ロックによるエラーが発生し、業務トランザクションを終了させます。誰もデータをロックしていないことを確認できたら、データのロック状態を更新し、ロック中にします。
このように悲観ロックはデータのロック状態を参照してからロック状態を更新するまでの間、他の操作によるデータ更新が行われないようにします。
ロックを開放するタイミングが難しい
悲観ロックは業務トランザクションの開始時点でデータをロックして排他制御を実現する方式であるため、終了したタイミングで確実にロックを開放しなければ、誰もそのデータを更新できなくなります。業務トランザクションが終了するタイミングは一つではなく、システムからのログアウト、異なる画面への遷移、ブラウザを閉じるなど様々な事態を想定してロックを開放することが必要になります。さらにシャットダウン、セッションタイムアウトなどの操作はアプリケーション設計で完全防ぐことはできません。
そのため不測の事態に備え、管理者の場合ロック開放できるような機能や長時間ロックされているデータをロック開放する機能を設けるなど、ロック済みのデータを開放する仕組みが必要となります。
バッチ処理とオンライン処理を共存させる排他制御の考え方
鳴くまで待とうホトトギス方式
バッチ処理の対象となる全データをDBのロックメカニズムでロック(ここでいう行ロック)できるまで実行するという考え方です。行ロックを取得してしまえば、行ロックを解放するまで他のトランザクションでそのデータを更新することはできません。そのため、オンライン処理がロック開放されるまで待つしかありません。
殺してしまえホトトギス方式
バッチ処理を優先し、オンライン処理のロックを奪い取るという考え方です。バッチ処理対象のデータに対して、ロック状態にかかわらずバッチ処理でロック状態を上書きしてしまえば、オンライン処理のロックを奪い取ることができます。ただし、バッチ処理中のデータロックが奪われるとバッチ処理を正しく処理できないため、バッチ処理中のデータは管理者の操作による強制的なロック開放ができないようにするなどの考慮が必要です。
鳴かせてみせようホトトギス方式
オンライン処理を優先し、バッチ処理側はロックできたデータのみを処理対象とするという考え方です。オンライン処理で行ロックされているデータは処理対象としないため、データごとに「FOR UPDATE句」で行ロックを取得する必要があります。タイミング悪くバッチ処理中にオンライン処理データをロックしていると、次回のバッチ処理のタイミングまでそのデータを処理できないことになるため注意が必要です。