製造現場・営業端末・モバイルアプリなど、 「ネットが不安定でも動くアプリ」は今や必須です。 その中心となるのが、ローカルSQLite → サーバーDBの同期設計。 しかし、同期は単なる「アップロード・ダウンロード」ではなく、 差分管理・競合解決・キュー化・API設計など多くの要素が絡みます。
・ローカルDBとサーバーDBの同期モデル
・差分同期の仕組み(UpdatedAt / Version)
・競合解決のパターン
・送信キュー(アウトボックス)方式
・API設計(Pull / Push)
・業務アプリ向けの実務アーキテクチャ
1. オフライン対応アプリの基本構造
オフライン対応アプリは、次の3層で構成されます。
- ローカルDB(SQLite):アプリが直接読み書き
- 同期キュー(送信バッファ):変更履歴を蓄積
- サーバーAPI:Pull(取得)/ Push(送信)
この構造にすることで、ネットが切れてもアプリは動き続け、 オンラインに戻った瞬間に同期が走ります。
2. 差分同期の基本:UpdatedAt(タイムスタンプ)方式
最もシンプルで実務的なのが、 UpdatedAt(最終更新日時)を使った差分同期です。
■ テーブル例(ローカル・サーバー共通)
CREATE TABLE Users (
Id TEXT PRIMARY KEY,
Name TEXT NOT NULL,
Age INTEGER,
UpdatedAt TEXT NOT NULL, -- ISO8601 UTC
IsDeleted INTEGER NOT NULL DEFAULT 0
);
■ UpdatedAt の役割
- どのデータが新しいか判定できる
- 差分だけ同期できる
- 競合解決の基準にも使える
3. ローカル → サーバー同期(Push)の仕組み
ローカルで変更されたデータを、 送信キュー(アウトボックス)に記録しておき、 ネット復帰時にまとめてサーバーへ送信します。
■ アウトボックステーブル例
CREATE TABLE Outbox (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Entity TEXT NOT NULL, -- Users / Orders など
EntityId TEXT NOT NULL,
Payload TEXT NOT NULL, -- JSONで変更内容
CreatedAt TEXT NOT NULL
);
■ ローカル更新時の処理
UPDATE Users SET Name = @name, UpdatedAt = @now WHERE Id = @id;
INSERT INTO Outbox (Entity, EntityId, Payload, CreatedAt)
VALUES ('Users', @id, @json, @now);
これにより、ネットが無くても変更履歴が蓄積されます。
■ サーバー送信(Push API)
POST /sync/push
[
{
"Entity": "Users",
"EntityId": "U001",
"Payload": { "Name": "Taro", "UpdatedAt": "2026-04-04T09:00:00Z" }
}
]
■ サーバー側の処理
- EntityId でレコードを特定
- UpdatedAt を比較して新しければ更新
- 成功したら Outbox の該当行を削除
4. サーバー → ローカル同期(Pull)の仕組み
サーバー側の更新をローカルに反映するには、 「前回同期日時」以降の更新だけ取得します。
■ Pull API例
GET /sync/pull?since=2026-04-04T08:00:00Z
■ サーバーのレスポンス例
[
{ "Id": "U001", "Name": "Taro", "UpdatedAt": "2026-04-04T09:00:00Z" },
{ "Id": "U002", "Name": "Hanako", "UpdatedAt": "2026-04-04T09:10:00Z" }
]
■ ローカル側の反映
if (server.UpdatedAt > local.UpdatedAt)
{
UPDATE Users SET ...;
}
これで差分だけ高速に同期できます。
5. 競合解決(Conflict Resolution)
ローカルとサーバーが同じデータを別々に更新した場合、 どちらを採用するかを決める必要があります。
■ よくある競合解決ポリシー
| 方式 | 説明 | 用途 |
|---|---|---|
| Last Write Wins | UpdatedAt が新しい方を採用 | 最もシンプル |
| Server Wins | 常にサーバー側を優先 | マスタデータ |
| Client Wins | ローカル側を優先 | 現場入力を重視 |
| Merge | フィールドごとにマージ | 複雑な業務 |
多くの業務アプリでは、 Last Write Wins(最終更新優先)が採用されます。
6. 同期の実行タイミング
同期は次のタイミングで行うのが一般的です。
- アプリ起動時
- アプリ終了時
- 一定間隔(例:30秒ごと)
- ネットワーク復帰時
- ユーザーが「同期」ボタンを押したとき
特にネットワーク復帰時の自動同期はUXが良くなります。
7. セキュリティ:同期APIは必ず保護する
同期APIはデータの出入口なので、 認証・認可・暗号化が必須です。
■ 必須対策
- HTTPS(TLS)
- APIキー or JWT
- ユーザーごとのアクセス制御
- ログ(監査ログ)
8. 実務アーキテクチャ(完成形)
■ 全体構成図(文章版)
- ローカルSQLite └ アプリが直接読み書き └ UpdatedAt / IsDeleted を持つ
- アウトボックス(送信キュー) └ ローカル変更を記録 └ ネット復帰時にPush
- 同期サービス(バックグラウンド) └ Push(ローカル → サーバー) └ Pull(サーバー → ローカル) └ 競合解決
- サーバーAPI └ 認証 └ 差分取得(Pull) └ 差分反映(Push)
9. 業務アプリ向けベストプラクティス
- UpdatedAt(UTC)で差分同期する
- ローカル更新は必ずアウトボックスに記録
- 同期はバックグラウンドで自動化
- 競合解決ポリシーを明確にする
- 削除は IsDeleted フラグで扱う(物理削除しない)
- 同期APIは認証・暗号化必須
- ログ(監査ログ)で同期履歴を残す
まとめ:SQLite × サーバー同期は“差分・キュー・競合解決”が鍵
- オフライン対応はローカルDBが中心
- 差分同期(UpdatedAt)が最も実務的
- 送信キュー(アウトボックス)で安定運用
- 競合解決ポリシーを最初に決める
- 同期APIはセキュリティ必須
「ネットが不安定でも動くアプリ」は、 現場での信頼性を大きく左右します。 この記事のパターンをベースに、 あなたのアプリに最適な同期アーキテクチャを構築してみてください。