Couchbase Mobile機能解説：データ同期における競合解決③（Sync Gatewayの競合解決）


## はじめに

https://qiita.com/yoshiyuki_kono/items/6e65310d9e6869f5eaaa

https://qiita.com/yoshiyuki_kono/items/983bb5dfe816eaf76c7d

本稿は、上記記事の続編です。ここでは、Sync Gatewayの競合解決にフォーカスして、解説します。

Sync Gateway(2.0以上)は、Couhbase Liteとの同期においてコンフリクトフリー（競合のない）モードで動作します（REST APIクライアントを利用する場合、コンフリクトフリーでない、つまり競合しているドキュメントをどちらも保存するモードが選択できますが、は本稿では割愛します）。

Couchbase Liteが、Sync Gatewayと同期する方法には、Couchbase Liteの変更をCouchbase Serverに反映させる「プッシュ」と、Couchbase Serverの変更をCouchbase Liteに反映させる「プル」の二方向があります。以下、それぞれのケースについて解説します。

## プッシュレプリケーション

次の図は、2つのクライアントがドキュメントの同じリビジョンへの変更をプッシュするシナリオを示しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/eb7e9ef6-7d54-00e5-f21c-e8591b82da1b.png)


このモードでは、Sync Gatewayは競合を引き起こすリビジョンを拒否しHTTP 409エラーを返します。これによって、データベース上に競合するリビジョンがないことを保証します。競合は、プルレプリケーション中にクライアント側で処理されます。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/28eaf922-30fa-7b9a-81e0-ff5e2a3e5c4e.png)


1. クライアントは、リビジョンの変更をSync Gatewayにプッシュします。
2. Sync Gatewayは、着信リビジョンがサーバーに現在保存されているリビジョンと競合している（つまり、着信リビジョンの祖先はサーバー上の最新のリビジョンでない）ことを検出します
3. Sync Gatewayは、409エラーでリビジョン変更を拒否します。Couchbase Liteは、エラーをログに記録する以外に何もしません。その後、プルレプリケーション中に競合が解決されます。

## プルレプリケーション
プルレプリケーション中に、クライアントが競合を検出した場合、競合は次の基準を使用して決定論的に解決されます

- 削除は常に勝ちます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/8d11651f-d171-22a3-05b1-0ac3e16f5ad1.png)

- 最新のリビジョン（つまり最大の「revID」）、または最新の変更（つまり最大の「generation ID」）が優先されます。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/201aa809-fe50-80ef-c744-ce17fd357b2c.png)

 


### サーバーブランチが勝者となるケース

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/6db236ca-9735-e3f9-7082-6b53480633c8.png)


1. クライアントは、Sync Gatewayからリビジョンの変更をプルします
1. クライアントは、着信リビジョンが現在保存されているリビジョンと競合していることを検出します（つまり、着信リビジョンと保存されたリビジョンは共通の親を共有します）
1. クライアントは、現在保存されているリビジョンとサーバーリビジョンの間の勝者を決定する競合解決関数を呼び出します。
 1.  サーバーからの着信リビジョン（Rev2-B）はローカルリビジョン（Rev2-A）よりも新しいため、サーバーリビジョンが勝者として選択されます。これは、サーバー上のリビジョンがトゥームストーンリビジョン（ドキュメント削除）の場合にも当てはまります。
1. サーバーブランチはローカルリビジョンツリーに接ぎ木されます（そしてローカルブランチはトゥームストーンされます）
1. その後、変更がサーバーにプッシュされると、競合は発生せず、サーバーはクライアント側と同期します。

### ローカルブランチが勝者となるケース

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/e3207912-6f24-1f30-070e-c620ef9b5329.png)

1. クライアントは、Sync Gatewayからリビジョンの変更をプルします
1. クライアントは、着信リビジョンが現在保存されているリビジョンと競合していることを検出します（つまり、着信リビジョンと保存されたリビジョンは共通の親を共有します）
1. クライアントは、現在保存されているリビジョンとサーバーリビジョンの間の勝者を決定する競合解決関数を呼び出します。
  1. ローカルリビジョン（Rev2-B）は受信サーバーリビジョン（Rev2-A）よりも新しいため、ローカルリビジョンが勝者として選択されます。これは、ローカルブランチのリビジョンが削除された場合にも当てはまることに注意してください。
1. サーバーブランチ（Rev2-B）はローカルリビジョンツリーに移植され、勝ったリビジョンの内容に対応する新しいリビジョン（Rev3）として追加されます。
1. その後、変更がサーバーにプッシュされると、競合は発生せず、サーバーはクライアント側と同期します。


## 同期モード選択上の注意

上述のとおり、競合はプルレプリケーション中にCouchbase Liteで解決されます。これは、レプリケーターがプッシュレプリケーターのみとして構成されている場合、Couchbase LiteのデータのビューがSync Gatewayのビューとは異なることを意味します。したがって、競合が発生することが予想され、競合を解決する必要がある場合は、競合のないモードで使用するときに、Couchbase Liteレプリケーターをプッシュプルモードで構成することが必要になります。

 

## Sync Gateway構成

Sync Gatewayのコンフリクトフリーモードは、Sync Gateway構成ファイルの`allow_conflicts`プロパティにより構成します。コンフリクトフリーモードを有効にするには、このプロパティを「`false`」に設定します。

ただし、このプロパティ構成に関係なく、Couchbase Lite2.0クライアントと同期するときには、Sync Gatewayに競合するリビジョンが追加されないことにも注意してください。「`allow_conflicts`」構成は、(2.0以外のクライアントと)REST APIクライアントにのみ影響します。以下の表は、その関係をまとめたものです。

allow_conflicts_configuration
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176567/32823b0c-3eed-dc85-9e4c-89e4b2f2a92c.png)


## データベースサイズに対する競合解決モードの影響

データベースサイズに対する競合解決の影響については、参考情報で紹介しているデータベースサイズの管理に関するブログも参照ください。

### 競合を許容するモード
競合を許容したシステムでは、競合するリビジョンが大きくなると、リビジョンツリーのサイズが大きくなり、データベースのサイズに影響を与えます。したがって、紛争をタイムリーに解決することが重要でした。

Sync Gatewayには、リビジョンツリーのサイズを決定するプロパティ`revs_limit`があります。`revs_limit`プロパティのデフォルト値は1000であり、最後の1000リビジョンに対応するメタデータがSync Gatewayに格納され得ることを意味します。設定中`revs_limit`を大きな値にするとデータベースのサイズに悪影響を与えるます。一方、古いリビジョンに対応するメタデータを維持することが、競合解決を処理するために必要だったため、重要でした。そのため、`revs_limit`を低すぎる値に設定しないことが重要でした。したがって、`revs_limit`の最小許容値は20となっています。

### コンフリクトフリー（競合のない）モード
コンフリクトフリーモードでは、古いリビジョンに対応するメタデータを実際に保存する必要はありません。これは、`revs_limit`の値が、実際上1であり得ることを意味します。これは、最新のアクティブなリビジョンのみが保存されていることを意味します。これにより、データベースサイズが大幅に節約されます。

## 参考情報

https://blog.couchbase.com/document-conflicts-couchbase-mobile/

https://blog.couchbase.com/conflict-resolution-couchbase-mobile/

https://blog.couchbase.com/database-sizes-and-conflict-resolution/