AI製アプリにリアルタイム共同編集を実装する(他の人の作業を壊さずに)
2人が同時にアプリを編集すると、リアルタイム共同編集は壊れる——片方の変更が静かに消えたり、上書きされたり、相手が見ている内容と矛盾したりする。3つの失敗パターンと、ひとつずつ実装する3つの解決策で解消できる。
2人が同時に同じアプリを編集すると何が起きるか?
リアルタイム共同編集とは、2人が同じアプリのデータを同時に編集したときに、互いの作業を上書きしてしまわないようにする仕組みだ——これを実装しないと、後から保存した人の変更が、先に保存した人の変更を黙って消してしまうことがある。あるチームで実際に起きたことを紹介しよう。
あるユーザーがチームで共有のタスクリストを作った。金曜の午後、2人のチームメンバーが同時にそれを開いた。両方の画面にはこう表示されていた。
- タスク1: 買い出し
- タスク2: 母に電話
- タスク3: ミーティングの予定調整
メンバーAは「買い出し」にチェックを入れた。メンバーBは「ルーターを直す」を追加した。2人とも保存ボタンを押した。
メンバーAが画面を更新すると、そこにはこう表示されていた。
- タスク1: 買い出し(チェック済み)
- タスク2: 母に電話
- タスク3: ミーティングの予定調整
「ルーターを直す」は消えていた。メンバーBの作業がなくなっていたのだ。
これが「衝突」だ——同時書き込みが起き、片方の変更が消える。まるで一つの機能のように聞こえるかもしれないが、実際にはデータ消失を防ぐための修正である。これがなければ、2人が同時に触れた瞬間にアプリは壊れる。
リアルタイム共同編集で最もよくあるバグとは?
リアルタイム共同編集が壊れる典型的なパターンは3つある。書き込みが黙って失われる、画面が古いデータを表示し続ける、そして2人がそれぞれ矛盾した事実を見てしまう、というものだ。それぞれ現れ方が違い、それぞれに個別の対処が必要になる。
失敗パターン1: 書き込みの消失(静かなデータ損失)
2人が同時に保存する。後から保存した方が、先の保存を上書きしてしまう。後の人には自分の変更が反映されたように見えるが、先の人には……何も見えない。あるいは画面を更新して、自分の作業がどこに消えたのか首をかしげることになる。
実話: あるウェディングプランナーとそのアシスタントがゲストリストを共同作業していた。アシスタントが3件の出欠回答を追加している間に、プランナーは2件を「確定」とマークした。プランナーのマークは消えてしまった。誰もそれに気づかないまま、プランナーがフォローアップの電話で二重に確認し、すでに「出席」と答えていた人にもう一度招待の連絡をしてしまうまで発覚しなかった。
実際に使われているアプリの多くは、「保存」ボタンを押したときだけでなく、キー入力のたびに保存することでこれを解決している。Google Sheets、Notion、Figmaはすべてこの仕組みを採用している。あなたのアプリにも同じ挙動が必要だ。
失敗パターン2: 古いままの画面(古いデータを見続ける)
Aさんがタスクを編集する。Bさんは同じページを開いたままで、古いバージョンを見ている。Bさんはその古いデータをもとに変更を加える。こうして、本人たちには見えない衝突が生まれる。
実話: 保険の査定員と施工業者が一つの保険金請求を共同で扱っていた。査定員は新しい写真をもとに「修理見積額: 3,000ドル」を「5,000ドル」に変更した。施工業者の画面には依然として3,000ドルと表示されたままだった。彼は3,000ドルで承認申請を提出した。後になって、両者はこの食い違いに気づくことになる。
リアルタイム更新がなければ、両者は「同じバージョンを見ている」と思い込んでしまう。実際には違うのに。
失敗パターン3: 連鎖する矛盾(2つの「事実」)
あるユーザーがレコードを削除する。別のユーザーはそのレコードの詳細画面を開いたままだ。片方には「削除済み」と表示され、もう片方には元のレコードがそのまま表示されている。2人は異なる「事実」をもとに行動することになる。
実話: ボランティアコーディネーターが、あるシフトを「キャンセル」に変更した。そのボランティアはまだ画面を更新しておらず、依然として「募集中」と表示されたままだった。彼女はそのシフトの人員募集を始めてしまう。数時間後、存在しないはずのシフトに2人が現れることになった。
リアルタイム共同編集のバグはどう直すのか?
順番に、一つずつ直していく。段階的な保存で書き込みの衝突を検知し、ローカルの編集内容を失わないようにデータ更新をマージし、そして衝突を隠すのではなく表に出す。初日から完璧なリアルタイム共同編集を実現する必要はない。
解決策1: 書き込みの衝突を検知する(段階的な保存)
「保存」ボタンを押したときだけでなく、あらゆる変更をその場で即座に保存するようにする。これが最も重要な修正だ。
ユーザーがフィールドを編集したら、その瞬間にデータベースへ送信する。小さな「保存済み」のインジケーターや、同期が完了すると消えるドットを表示するとよい。2人目が同時に保存した場合、データベース側では次のように処理されるべきだ。
- Aさんの変更が先に届く。
- Bさんの変更が後に届く。
- Bさんの変更が優先される(後勝ち、last-write-wins)。
容赦ないやり方だが、正直でもある。少なくとも片方は自分の変更が反映されなかったことに気づき、やり直すことができる。
ビルダーへの指示: 「保存」ボタンではなく、キー入力のたびに、あるいはユーザーの入力が2秒止まったタイミングで保存をトリガーする。同期インジケーターを表示する。テスト方法: アプリを2つのブラウザウィンドウで開き、同じフィールドを編集する。片方の変更がもう片方を目に見える形で上書きするはずだ。
解決策2: ローカルの編集を失わずに更新する
5秒ごとにデータベースをポーリングする場合(あるいはWebSocketで更新をプッシュする場合)、新しいデータをマージする際に、ユーザーが今まさに行っている編集を潰さないようにする。
やってはいけないやり方: ページ全体をリロードする。ローカルの編集はすべて消えてしまう。
正しいやり方: ユーザーが今まさに編集していないフィールドだけを更新する。タイトルを入力中ならそこには触れない。期限日をいじっていないなら、サーバー側の値で更新する。
ビルダーへの指示: データベースから最新のデータを取得したら、マージする——ローカルの編集は保持し、それ以外をすべて更新する。実際のフレームワークでは、たいていコード2行で済む処理だ。テスト方法: 一方のウィンドウで一つのフィールドを、もう一方のウィンドウで別のフィールドを同時に編集する。両方の変更が残っているはずだ。
解決策3: 事実をはっきり示す
衝突や古いデータがあるときは、それを見せる。隠してはいけない。
例:
- 「このタスクは他の人によって削除されました。元に戻しますか?」
- 「あなたが入力している間に、誰かがこのリストに3件のアイテムを追加しました。[新しい内容を見る]」
- 「これは2分前のバージョンです。最新の状態を見るには更新してください。」
ビルダーへの指示: 読み込み時に、表示しているデータにタイムスタンプが付いているか確認する。それが30秒以上前のもので、かつユーザーが編集しようとしたら、警告を表示して再取得する。リストを表示している場合は、「失敗の兆候」としてではなく、ユーザーの自然な操作として意味の通る「更新」ボタンを用意する。
完全に解決されたリアルタイム共同編集とはどんな姿か?
理想の姿はこうだ。あなたと私が一つの共有ドキュメントを編集している。私が文字を打つと、あなたには私のカーソルが動くのが見える。テキストは両方の画面に即座に反映され、どちらの作業も失われない。これを実現するには、3つの要素が揃って機能する必要がある。
- すべてのキー入力が即座に保存される ——ボタンを待たない。
- 衝突はルールに従って解決される ——2人が同じ単語を編集した場合、システムがルールに従って勝者を決める(通常は後勝ちか、衝突を知らせるプロンプトが出る)。
- 更新が即座に届く ——WebSocket、Server-Sent Events、あるいはFirebaseのようにプッシュ型のデータベースを使う。
ほとんどのアプリは、初日からここまで必要とはしない。まずは段階的な保存(解決策1)から始めよう。2人が同時に使うようになったら、ポーリングとマージ(解決策2)を追加する。衝突が実際に問題を引き起こすようになって初めて、即時プッシュ(解決策3)を導入すればいい。
公開前にリアルタイム共同編集をどうテストするか?
公開する前に、2つのブラウザウィンドウで3つのテストを行おう。同時保存テスト、古いデータのテスト、そして更新テストだ。それぞれに明確な合否基準がある。
テスト1: 同時保存テスト
- アプリを2つのブラウザウィンドウで開く。
- ウィンドウ1でフィールドXを編集して保存する。
- ウィンドウ2でフィールドYを編集し、直後に保存する。
- 両方のウィンドウを更新する。
- 合格: 両方の編集が残っている。不合格: 片方の編集が消えている。
テスト2: 古いデータのテスト
- ウィンドウ1でアプリを開く。何も操作しない。
- ウィンドウ2で大きめの変更を行う(行を追加/削除する、タイトルを変更するなど)。
- ウィンドウ1に戻る(まだ古いデータが表示されている)。
- ウィンドウ1の古いバージョンを編集しようとする。
- 合格: 警告が表示される、あるいはきれいにマージされる。不合格: ウィンドウ2の変更を上書きしてしまう。
テスト3: 更新テスト
- 意味のある作業を進行中の状態にする(途中まで入力したフォーム、下書きのメッセージなど)。
- ページを更新する。
- 合格: 作業内容がそのまま残っている。不合格: 消えてしまう。
キー入力のたびに保存すべきか、それとも保存ボタンを待つべきか?
キー入力のたびに保存する。この一つの判断だけで、リアルタイム共同編集への道のりの8割は達成できる——残りはそれを目に見える形にし、衝突を処理することだけだ。
今どき、ユーザーはこれを当然のこととして期待している。Gmail、Google Docs、Slack——現代的なアプリはどれもこれをやっている。あなたのアプリも同じであるべきだ。
まず最初にやるべきこと: あらゆる変更を自動的に保存するようにする。小さなインジケーターを表示する(「保存中…」と出て、それから消える)。2人が同時に編集したときに何が起きるかを観察する。片方の変更が消えてしまうなら、それが次に直すべきものだ。初日から完璧な共同編集を作ろうとするより、一度に一つの問題に取り組むほうがうまくいく。