← 記事一覧に戻る
テクノロジー

同時編集で壊れる実装を楽観ロックで防ぐ

同時編集で壊れる実装を楽観ロックで防ぐ

2人が同じデータを同時に編集すると、後から保存した側が先の変更を黙って上書きします。画面もログもエラーを出さないため、単独で動かすテストでは見つかりません。実装の段階で「ここは同時に触られる」と疑えるかが分かれ目です。

取りこぼしが起きる典型3パターン

1. 読んでから書く

編集画面を開いた時点の値をそのまま保存する形です。Aさんが開き、Bさんが保存し、Aさんが保存すると、Bさんの変更が消えます。

2. カウンタ加算

「現在値を読む → 1足す → 書く」と実装すると、同時に2件処理したとき加算が1回分しか反映されません。いいね数、在庫数、採番が典型です。

3. 一覧の並び替え

並び順を配列ごと保存する実装では、同時に別の人が追加した項目が、並び替えを保存した瞬間に消えます。

防ぎ方1: 楽観ロック(バージョン番号)

行にバージョン番号を持たせ、「読んだときの番号のままなら更新する」という条件を付けます。

UPDATE tasks
   SET title = :title, version = version + 1
 WHERE id = :id AND version = :version_at_read;
-- 更新件数が 0 なら、他の人が先に保存している

更新件数が0のときは上書きせず、HTTP 409 を返して画面に「他の人が先に更新しました。最新の内容を確認してください」と伝えます。Rails なら lock_version カラムを足すだけで同じ動きになります。

防ぎ方2: 原子的な更新

カウンタは読まずに、データベース側で加算します。

UPDATE items SET stock = stock - 1
 WHERE id = :id AND stock > 0;

条件に stock > 0 を入れておけば、在庫がマイナスになる二重購入も同時に防げます。

競合したときの画面をどうするか

409 を返すだけでは、利用者は入力した文章を失います。次の3点を決めておくと、取りこぼしを防いだ代わりに別の不満が出る事態を避けられます。

  • 入力途中の内容は破棄せず、画面に残したまま警告を出す
  • 相手が変更した項目と自分が変更した項目を並べて見せる
  • 項目が重ならない場合だけ自動で取り込み、重なる場合は利用者に選んでもらう

失敗例として多いのは、競合を検知してもエラー文言が「保存に失敗しました」だけで、利用者が再入力して同じ競合を繰り返すケースです。

実装時に疑う3つの質問

  • 保存する値は「画面を開いたときの値」か、「差分」か
  • 同じ行を同時に2人が更新できる画面・APIはどれか
  • 競合したとき、利用者に何を見せるか(上書き・拒否・マージ)

2タブで再現する確認手順

  1. 同じ編集画面を、ブラウザのタブ2つで開く
  2. タブ1でタイトルを変えて保存する
  3. タブ2で別の項目を変えて保存する
  4. タブ1の変更が残っていれば合格。消えていれば取りこぼしがある

カウンタは、同じ操作を2タブから連打して件数が合うかを見ます。

Bugoon での実践

同時操作のバグは「保存したのに戻っている」と報告されがちで、再現条件が伝わりにくい種類です。Bugoon のウィジェットなら、報告者がその場でスクリーンショットと注釈、操作ステップ、コンソールエラーをまとめて送れます。報告は GitHub Issue とカンバンボードに並ぶので、「どの画面で、何をした直後か」を開発者が確認できます。同じ画面に近い時刻で届いた別の報告者のレポートを並べて見られると、同時操作による競合バグにさらに気づきやすくなります。

チームのバグ報告を、もっとスムーズに。

Bugoon は無料で始められます。サイトにタグを 1 行追加するだけで、QA と開発の往復がなくなります。

無料で始める