マージ戦略 — その概要、マージの種類、動作原理

著者: IT Sectr 公開日: 2026-06-14 読了時間: 8 分

マージ戦略は、異なるバージョンの競合する変更を、一方のバージョンを他方で置き換えるのではなく、単一の一貫した状態に結合するデータマージ戦略です。Last Write Winsとは異なり、マージはデータ損失を最小限に抑えながら、すべてのブランチからの変更を保持しようとします。Apache CouchDBのドキュメント(2025年)によると、スリーウェイマージはドキュメント指向データベースにおける標準的な競合解決メカニズムです。スリーウェイマージは、各クライアントがどのフィールドを変更したかを判断するために共通のベースバージョンを使用します。

重要なポイント

  • マージ戦略は、競合する変更を置き換えるのではなくマージするアプローチであり、ユーザーデータの損失を最小限に抑えます。
  • スリーウェイマージは、ローカル、リモート、ベースの各バージョンを分析し、フィールドレベルで競合しない変更を自動的に解決します。
  • 履歴の保存 — マージでは、差異を検出するために以前のバージョンを保存する必要があり、保存されるデータ量が増加します。
  • 複雑さ — マージはLWWよりも実装が難しく、特にネストされた構造や配列の競合解決が困難です。
  • 適用 — 各フィールドが独立した値を持つプロフィール、ドキュメント、フォーム、その他の構造化データに最適です。

モバイル開発におけるマージ戦略とは?

マージ戦略は、競合するデータバージョンの一方を選択するのではなく、それらを結合するアルゴリズムの集合です。モバイルアプリケーションでは、2つのクライアントが同じオブジェクトの異なるフィールドやプロパティを独立して編集する場合にマージが使用されます。古いバージョンを完全に破棄する代わりに(LWWのように)、システムは個々のフィールドレベルで差異を分析し、両方のバージョンからの変更を含む結果オブジェクトを生成します。

主な違いは、マージとLWWの間で、各ユーザーの変更が互いに矛盾しない限り保持されることです。ユーザーAがタスク名を変更し、ユーザーBが説明を変更した場合、マージは両方の変更を保持します。両方が同じフィールドを変更した場合、解決が必要な競合が登録されます。これにより、マージはユーザーが同じデータで共同作業するアプリケーションに適しています。

Stripe Engineering Blog(2025年)のレポートによると、LWWの代わりにマージ戦略を実装したことで、モバイルプロジェクト管理アプリケーションにおけるデータ損失に関するユーザークレームの数が76%減少しました。ただし、競合処理時間は15~30ミリ秒増加し、これはデータ整合性のために許容可能なコストと見なされています。

スリーウェイマージ:メカニズムの仕組み

スリーウェイマージ(three-way merge)は、マージ戦略の最も一般的な実装です。このメカニズムは、ベース(分岐前の状態)、ローカル(現在のクライアントのバージョン)、リモート(サーバーバージョン)の3つのデータバージョンで動作します。システムは、ローカルバージョンとリモートバージョンの各フィールドをベースと比較して、どの側がどのフィールドを変更したかを判断します。

決定ロジックはシンプルです。1つのクライアントのみがフィールドを変更した場合(ベースに対して)、その変更は自動的に受け入れられます。両方のクライアントが同じフィールドを変更した場合、競合が登録され、自動的に(優先度によって)解決されるか、ユーザーに委ねられます。どちらのクライアントもフィールドを変更しなかった場合、ベース値が維持されます。このアプローチにより、独立した変更が失われたり競合したりしないことが保証されます。

フィールドディクショナリレベルでのスリーウェイマージアルゴリズム:

kotlin
fun threeWayMerge(
    base: Map<String, Any?>,
    local: Map<String, Any?>,
    remote: Map<String, Any?>
): Map<String, Any?> {
    val result = base.toMutableMap()
    val allKeys = base.keys + local.keys + remote.keys

    allKeys.forEach { key ->
        val baseVal = base[key]
        val localVal = local[key]
        val remoteVal = remote[key]

        result[key] = when {
            localVal == baseVal -> remoteVal
            remoteVal == baseVal -> localVal
            localVal == remoteVal -> localVal
            else -> // real conflict
                resolveConflict(key, localVal, remoteVal)
        }
    }
    return result
}

threeWayMerge関数は、3つのバージョンからのすべてのキーを順次処理します。ローカル値がベースと一致する場合、リモートの変更が受け入れられます。リモートがベースと一致する場合、ローカルの変更が受け入れられます。両方がベースと異なるが互いに等しい場合、どちらかが受け入れられます。実際の競合は、両側に異なる変更がある場合にのみ登録されます。

自動および手動の競合解決

自動解決は、変更が重複しない場合、またはシステムがルールに基づいて正しい値を判断できる場合に適用されます。たとえば、数値フィールドの場合は最大値を選択し、テキストフィールドの場合は連結または新しいバージョンを選択できます。CouchDBはJSONドキュメントフィールドに自動マージを使用し、配列には重複除去を伴う連結を使用します。

手動解決は、2人のユーザーが同じフィールドを異なる方法で変更した場合に必要です。この場合、アプリケーションは3つのオプションを持つダイアログを表示します:“ローカルバージョンを受け入れる”、“リモートバージョンを受け入れる”、または“手動でマージする”。CMU(カーネギーメロン大学、2024年)の研究によると、手動解決はユーザー満足度を40%低下させるため、自動マージを最大化する必要があります。

さまざまなフィールドタイプの解決戦略:

フィールドタイプ自動戦略手動の代替
数値(カウンター)最大値を採用両方の値を表示
テキスト(文字列)時間で選択ハイライトされたエディター
ブール値ロールによる優先順位3つの選択オプション
配列(リスト)重複除去とマージ要素ごとの選択
ネストされたオブジェクト再帰的マージ差分を表示

Kotlinでのマージ実装例

実装を考えてみましょう、REST APIを介した同期を備えたモバイルアプリケーションのユーザープロフィールに対するマージ戦略です。プロフィールには、名前、メール、アバター、通知設定が含まれます。各フィールドは、ユーザーの異なるデバイスで独立して変更できます。

フィールドレベルのバージョニングを備えたプロフィールデータクラス:

kotlin
data class UserProfile(
    val displayName: String,
    val email: String,
    val avatarUrl: String,
    val notificationsEnabled: Boolean
)

data class ProfileSnapshot(
    val profile: UserProfile,
    val version: Int
)

fun mergeProfiles(
    base: UserProfile,
    local: UserProfile,
    remote: UserProfile
): UserProfile {
    return UserProfile(
        displayName = if (local.displayName != base.displayName)
            local.displayName else remote.displayName,
        email = if (local.email != base.email)
            local.email else remote.email,
        avatarUrl = if (remote.avatarUrl != base.avatarUrl)
            remote.avatarUrl else local.avatarUrl,
        notificationsEnabled = if (local.notificationsEnabled != base.notificationsEnabled)
            local.notificationsEnabled
        else remote.notificationsEnabled
    )
}

mergeProfiles関数は、プロフィールの各フィールドを独立して処理し、ベースと異なるバージョンを選択します。競合の場合(両方がベースと異なる場合)、優先順位はアプリケーションルールによって決定されます。この例では、avatarUrlにはリモートバージョンが優先され、残りのフィールドにはローカルバージョンが優先されます。

モバイルアプリデータベースにおけるマージ戦略

CouchDBとPouchDBは、マージ戦略の組み込みサポートを備えた最もよく知られているデータベースです。ドキュメントのレプリケーション中、CouchDBはドキュメントレベルでの競合検出を伴うマルチスレッドレプリケーションを使用します。ベースバージョンはリビジョン履歴に保存され、競合が発生した場合、システムはすべての競合ブランチを保持し、マージメカニズムを通じてそれらを解決するためのAPIをアプリケーションに提供します。

Firebase Firestoreでは、マージは楽観的ロックによるトランザクションを通じて実装されます。開発者は、特定のフィールドがFieldValue.serverTimestamp()およびFieldValue.arrayUnion()を使用して原子的に更新されるべきであると指定できます。ただし、Firestoreは完全なスリーウェイマージをサポートしていません。競合が発生すると、トランザクションは新しいデータで再試行され、これは真のマージではなく再試行に相当します。

Kotlin MultiplatformおよびReact Native上のモバイルアプリケーションの場合、マージ戦略はクライアント側で実装されます。ローカルデータベース(SQLite、Realm)は各ドキュメントのバージョンを保存し、同期中にクライアントはサーバーバージョンをロードし、結果を送信する前にローカルでマージを実行します。このアプローチにより、より多くの競合が蓄積される長時間のオフライン操作中でもデータの整合性が確保されます。

よくある質問

データ同期におけるマージ戦略とは?

マージ戦略は、異なるバージョンの変更を単一の状態に結合する競合解決アプローチです。LWWとは異なり、マージはフィールドレベルで互いに矛盾しない場合、両方のブランチからの変更を保持します。

スリーウェイマージとツーウェイマージの違いは?

スリーウェイマージは、ベースバージョン(分岐前の状態)を使用して、各クライアントがどのフィールドを変更したかを判断します。ツーウェイマージは、元の状態を知らずに2つのバージョンのみを比較するため、誤った競合が発生しやすくなります。

どのデータベースがマージを標準サポートしていますか?

CouchDBとPouchDBは、スリーウェイマージの組み込みサポートを備えています。Firebase Firestoreはトランザクションレベルでの実装が必要です。MongoDBRealmは楽観的ロックメカニズムを提供しますが、完全な自動マージは提供しません。

マージ戦略が適さないのはどのような場合ですか?

マージは適していません、処理速度が重要なデータ(毎秒1000を超える競合)、ストリーミングデータ(ログ、イベント)、および変更が根本的に互換性がない場合(異なるスキーマバージョン)です。これらの場合、LWWまたはCRDTの方が効率的です。

モバイルアプリケーションでマージ戦略を実装するには?

実装には3つのステップが含まれます:サーバーからデータをロードする際にベースバージョンを保存し、保存時にフィールドレベルで変更を検出し、同期中にマージアルゴリズムを呼び出します。簡素化のために、JSON PatchまたはCRDTライブラリを使用してください。

まとめ

  • マージ戦略は、一方のバージョンを他方で置き換えるのではなく、異なるデータバージョンからの変更を結合する競合解決戦略です。
  • スリーウェイマージは最も人気のある実装であり、変更されたフィールドを判断するためにベース、ローカル、リモートの各バージョンを使用します。
  • 自動解決は、競合しない変更(異なるフィールド、クライアントの一方がデータを変更しなかった場合)に適用されます。
  • 手動解決は、1つのフィールドが2つのクライアントによって変更された場合に必要ですが、ユーザー満足度を40%低下させます。
  • 利点 — 最小限のデータ損失と、ドキュメントでの共同作業時の優れたユーザーエクスペリエンス。
  • 欠点 — 実装の複雑さの増加と、ローカルデータベースでのバージョン履歴の追加保存。
  • 推奨事項 — プロフィール、ドキュメント、設定にはマージを使用します。メタデータとログには、よりシンプルな代替としてLWWを使用します。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください