オフラインファーストと同期設計
電波が切れても操作を止めない設計で離脱を防げる。ローカルDBを正とし、競合解決と再送設計まで一気通貫で理解できる。
- 1.オフラインファーストはローカルDBを常に正の書き込み先とし、サーバーとの同期は非同期の背景処理に切り離す設計原則。
- 2.競合解決はLWW・CRDT・アプリ固有マージの3系統があり、データの性質で選ぶ必要がある。
- 3.同期キューは冪等なオペレーションログとして永続化し、指数バックオフと順序保証付きの再送で障害を吸収する。
なぜオフラインを前提に設計するのか
モバイル回線は本質的に不安定です。地下鉄、エレベーター、電波の弱い建物内など、通信が数秒から数分単位で途切れる状況は例外ではなく常態と考えるべきです。オフラインファーストとは、この前提を設計の出発点に据え、ローカルデータベースへの書き込みを常に成功する正の操作とし、サーバーとの同期をユーザー操作から切り離された背景処理として扱う設計原則です。対比されるのが「サーバーが正で、通信できない間はUIをブロックする」オンライン前提の設計で、これは通信品質がそのままアプリの体感品質に直結してしまいます。
オフラインファーストのアプリでは、ユーザーの操作はまずSQLiteやRealmなどのローカルDBに即座に反映され、画面はその結果を即座に描画します(楽観的UI更新)。同期処理は別スレッド・別プロセスでキューを消化し、成功すればローカルの状態を確定させ、失敗すれば再試行するか、あるいはユーザーに競合解決を委ねます。この分離により、通信状態に関わらず操作のレイテンシは常にローカルI/O並みに保たれます。
ローカルDBと同期の設計要素
ローカル永続化層には、行志向のリレーショナルモデルであるSQLiteと、オブジェクトをそのまま永続化するRealmという二つの潮流があります。
| 観点 | SQLite | Realm |
|---|---|---|
| データモデル | 行と列の関係モデル。クエリはSQL | オブジェクトグラフをそのまま永続化 |
| 変更検知 | アプリ側でdirtyフラグ等を明示管理 | オブジェクトの変更を透過的に監視しやすい |
| 同期との相性 | 同期用の差分テーブルを自前設計しやすい | Realm Syncなど専用同期基盤と統合しやすい |
| 学習コスト | SQLの知識で扱え移植性が高い | 独自APIとスキーマ移行の作法を覚える必要 |
どちらを選んでも共通して必要なのは、「ローカルでの変更をサーバーに伝えるための変更履歴」です。単純にテーブルの現在値だけを持つと、オフライン中に何が変更されたのかが失われてしまいます。そこで実務では、エンティティ本体とは別に同期用のオペレーションログ(作成・更新・削除の各イベントをタイムスタンプ付きで記録するテーブル)を用意し、これを同期キューの入力とする設計が主流です。
競合解決戦略
複数デバイスやオフライン中の並行編集が起きると、同一エンティティに対してサーバー側とローカル側で異なる変更が存在する状態、すなわち競合が発生します。解決方針は大きく3系統に分かれます。
各更新にタイムスタンプを付け、最も新しいものを採用する方式です。実装が単純で、多くのモバイルバックエンド(Firestoreのデフォルト挙動など)が採用します。弱点は、クロックのずれや同時刻更新で意図しない上書きが起き、片方の変更が静かに消える点です。金銭や在庫のように失われて困る値には不向きです。
CRDT(Conflict-free Replicated Data Type)は、データ構造そのものを「どんな順序でマージしても同じ結果に収束する」ように設計する方式です。カウンタ型(加算のみを記録し、マージ時に合算する)や、集合の要素追加・削除を別々に記録するOR-Setなどが代表例です。中央のサーバーによる調停なしに複数デバイスの変更を自動マージできる強みがありますが、汎用のドキュメント編集や自由形式のフィールドには適用しづらく、既存のリレーショナルスキーマへの後付けはコストが高いという制約があります。
アプリ固有マージは、フィールド単位で意味を持たせて解決する方式です。例えば「ステータスは進行が進んでいる方を優先する」「金額は合算する」「テキスト本文は3-wayマージを試み、失敗したらユーザーに選ばせる」といった業務ロジックをドメインごとに書きます。実装コストは高い一方、データ損失を最小化できるため、業務系アプリの中核データにはこの方式が選ばれることが多いです。
LWWやアプリ固有マージの多くは端末のローカル時計に依存しがちですが、モバイル端末の時計はユーザー操作やタイムゾーン変更でずれることがあります。ベクタークロックや、サーバー側で採番する論理シーケンス番号(同期のたびに単調増加するID)を使うと、実時計のずれに影響されない順序判定ができます。
楽観的UI更新の内部動作
楽観的UI更新とは、サーバーの応答を待たずに「成功するはず」という前提で即座に画面を更新し、実際の同期は裏側で進める手法です。内部的には次の手順を踏みます。
1. ローカルDBへ変更を書き込み、一意な操作IDを採番する
2. 画面はローカルDBの変更を購読しているため即座に再描画される
3. 同じ操作IDを持つレコードを同期キューへ積む
4. バックグラウンドで同期キューを消化し、サーバーへ送信する
5a. 成功: サーバー確定状態でローカルレコードを更新し、キューから除去する
5b. 失敗: エラー種別に応じて再試行するか、競合解決フローに回す
ポイントは、UIが参照するのは常にローカルDBであり、同期の成否を意識しないことです。失敗時の巻き戻し(ロールバック)が必要な場合は、変更前の値を操作ログ側に保持しておき、UIに「送信できませんでした」という状態を明示的に反映させます。何もフィードバックせずに黙って巻き戻すと、ユーザーは自分の操作が失われた理由を理解できません。
同期キューと再送設計
同期キューは、ネットワーク障害やアプリのプロセス終了をまたいでも操作を失わないよう、必ずローカルDB上に永続化します。メモリ上のキューだけでは、アプリがバックグラウンドで終了した瞬間に未送信の操作が消えてしまいます。
再送設計で押さえるべき原則は次の3点です。
冪等性: 同じ操作が二重に届いても結果が変わらないよう、操作IDをサーバー側で重複排除キーとして扱います。再送は「送ったかどうか分からない」状態を必ず生むため(応答がタイムアウトしただけで実際は処理済みの場合がある)、冪等性なしの再送は危険です。
順序保証: 同一エンティティに対する複数の操作は、生成順にサーバーへ適用されなければ最終状態が壊れます。キューはエンティティ単位、あるいは全体で順序を維持したまま消化し、ある操作が失敗している間は後続の依存する操作を待機させます。
指数バックオフとジッター: 再送間隔を固定にすると、大量端末が同時に復旧した瞬間にサーバーへ負荷が集中します(サンダリングハード問題)。再送のたびに待機時間を倍々に伸ばし、さらにランダムなジッターを加えることで、負荷集中を避けつつ復旧後の追従を早めます。
| 失敗の種類 | 典型例 | 対処 |
|---|---|---|
| 一時的 | タイムアウト、5xx応答、圏外 | 指数バックオフで再送を継続 |
| 永続的 | 認証切れ、バリデーションエラー | 再送を止めユーザーに明示的な対応を促す |
| 競合 | サーバー側の値が別クライアントで更新済み | 競合解決戦略に従いマージまたはユーザー選択 |
失敗の種類を区別せずに無限リトライすると、永続的エラーがキューに滞留し続け、後続の正常な操作までブロックしてしまいます。エラー種別ごとに再送継続・停止・分岐の判断をキュー側に持たせることが、実運用での破綻を防ぐ鍵です。
まとめ
オフラインファーストの本質は、ローカルDBを唯一の正としてUIをそこに結びつけ、サーバー同期を非同期の背景処理に完全分離することにあります。競合解決はLWW・CRDT・アプリ固有マージのどれか一つで済ませようとせず、データの性質ごとに使い分けるのが実務的です。そして同期キューは冪等性・順序保証・バックオフ付き再送を備えた永続キューとして設計することで、不安定な回線環境でもユーザー操作を一度も失わないアプリが成立します。土台となるローカルストレージやプロセス管理の考え方は /os/ の話題とも重なるため、合わせて理解すると設計の見通しが立てやすくなります。
モバイル開発 Article
オフラインファーストと同期設計を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
モバイル開発
比較で見る軸
難易度: advanced / カテゴリ: モバイル開発 / タグ数: 5
導入後に効く点
競合解決はLWW・CRDT・アプリ固有マージの3系統があり、データの性質で選ぶ必要がある。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- モバイル開発
- タグ数
- 5
判断チェックリスト
- 自社の用途が「モバイル開発 / オフラインファースト」に近いか確認する。
- 強みである「オフラインファーストはローカルDBを常に正の書き込み先とし、サーバーとの同期は非同期の背景処理に切り離す設計原則。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。