競合

分散データベースでは、データのコピーが複数の場所に保管される可能性があります。 本来のネットワークとネットワークのシステムの特性として、 ある場所に保管された文書に対して変更が行われても、 データベースの他の部分の更新や複製を即時に行えないことがあります。

言い換えると、 文書の別々のコピーに対して個々に更新が実行される可能性があります。 そのような更新が行われた結果、最終的に正しい文書の内容は何かという点で、不一致つまり「競合」が発生することがあります。

IBM® Cloudant® for IBM Cloud® は、潜在的な問題を警告することで競合が回避されるようにします。 問題のある更新リクエストに対しては、 409 のレスポンスを返すことで警告します。 ただし、 ネットワークに現在接続されていないシステムでデータベース更新が要求された場合、 409 応答が表示されないことがあります。 例えば、データベースが、インターネットから一時的に切断されたモバイル・デバイス上にある場合、競合する可能性がある他の更新が行われたかどうかを確認することは不可能です。

競合状態にある文書を要求した場合は、 IBM Cloudant は、予期したとおりに文書を返します。 ただし、内部アルゴリズムは、多くの要因を検討した後で、どの文書バージョンを返すかを決定します。 返された文書が最新バージョンであると想定してはなりません。

例えば、競合があるかどうかを確認しなかったり、競合に対処しなかったりした場合、IBM Cloudant データベースが以下の動作を示すことがあります。

  • 競合する文書の発生が増えるため、文書の内容の不整合が増える。
  • 競合するすべての文書を競合が解決されるまで保持する必要があるため、データベース・サイズが増える。
  • IBM Cloudant は、競合する文書の「可能な限り最適な」バージョンを判別しようとして各要求への応答での処理が多くなるため、パフォーマンスが低下する。

競合を確認して解決するタイミングを決定するのに役立つように、推奨される手法を以下に示します。

推奨プラクティス
アプリケーション特性 文書更新の頻度 取得時に競合を確認するかどうか 更新時に競合を確認するかどうか
常にネットワークに接続されている (例: サーバー)。 多くの場合 Y
常にネットワークに接続されている。 場合により
Y
常時ではないが、多くの場合ネットワークに接続されている (例: ラップトップ)。 多くの場合
Y
常時ではないが、多くの場合ネットワークに接続されている。 場合により
Y
ときおりネットワークに接続されている (例: タブレット)。 多くの場合
Y

競合の検出

文書に影響を与える可能性がある競合を検出するには、文書の取得時に照会パラメーター conflicts=true を追加します。 返される結果の文書には _conflicts 配列が含まれており、これには競合するすべてのリビジョンのリストが入っています。

文書の競合を検出するためのマップ関数の例を以下に示します。

function (doc) {
  if (doc._conflicts) {
    emit(null, [doc._rev].concat(doc._conflicts));
  }
}

データベース内の複数の文書の競合を検出するには、ビューを作成します。 示されている例のようなマップ関数を使用すると、競合のあるすべての文書のすべてのリビジョンを検出できます。

このようなビューがある場合、これを使用して、必要に応じて競合を検出して解決することができます。 代わりに、複製が行われるたびにビューを照会して、競合を即時に特定して解決することができます。

競合の解決方法

競合が検出されたら、次に示す 4 ステップの手順で解決できます。

  • 取得
  • マージ
  • アップロード
  • の削除

文書の最初のバージョンの例を以下に示します。

{
  "_id": "74b2be56045bed0c8c9d24b939000dbe",
  "_rev": "1-7438df87b632b312c53a08361a7c3299",
  "name": "Samsung Galaxy S4",
  "description": "",
  "price": 650
}

この例のシナリオを考えてみましょう。 オンラインショップ用の商品のデータベースがあると仮定します。 文書の最初のバージョンは、示されている例のようになります。

説明が追加された、文書の第 2 バージョン (最初のリビジョン) を以下に示します。

{
  "_id": "74b2be56045bed0c8c9d24b939000dbe",
  "_rev": "2-61ae00e029d4f5edd2981841243ded13",
  "name": "Samsung Galaxy S4",
  "description": "Latest smartphone from Samsung",
  "price": 650
}

この文書にはまだ説明がないため、他のユーザーが説明を追加する場合があります。

文書の最初のバージョンに対して値下げのデータ変更が行われた別の 第 2 バージョンを以下に示します。

{
  "_id": "74b2be56045bed0c8c9d24b939000dbe",
  "_rev": "2-f796915a291b37254f6df8f6f3389121",
  "name": "Samsung Galaxy S4",
  "description": "",
  "price": 600
}

それと同時に、複製されたデータベースで作業をしている他のユーザーが価格を下げます。 この変更は、文書の最初のバージョンに対して行われます。 そのため、 値下げの変更では説明の変更は「認識」されません。

後で 2 つのデータベースが複製されるときに、文書の 2 つの異なるバージョンのどちらが正しいかが明確ではないことがあります。 この例が競合シナリオです。

競合するリビジョンの取得

文書の競合するリビジョンを検出するには、 通常どおりその文書を取得しますが、 以下の例のように conflicts=true パラメーターを 含めます。

https://ACCOUNT.cloudant.com/products/$_ID?conflicts=true

文書取得に対する次の応答例では、競合するリビジョンが示されています。

{
  "_id":"74b2be56045bed0c8c9d24b939000dbe",
  "_rev":"2-f796915a291b37254f6df8f6f3389121",
  "name":"Samsung Galaxy S4",
  "description":"",
  "price":600,
  "_conflicts":["2-61ae00e029d4f5edd2981841243ded13"]
}

文書に競合がある場合、変更された説明または変更された価格の問題に基づく、示されている例のような応答が表示されることがあります。

変更された価格を持つバージョンが、文書の最新バージョンとして任意に 選択されました。 最後に更新されたバージョンの文書が、競合解決用の最新バージョンであると想定しないでください。

この例では、_rev2-f796915a291b37254f6df8f6f3389121 を持つ取得済みの文書と、_rev2-61ae00e029d4f5edd2981841243ded13 を持つ別の文書の間の競合を検討します。 競合する文書の詳細は _conflicts 配列に示されています。

多くの場合はこの配列の要素は 1 つのみですが、多くの競合するリビジョンが存在することもあります。 各リビジョンが配列にリストされます。

変更のマージ

アプリケーションは、可能性がある変更をすべて識別して調整することで、正しい有効な更新を効率的にマージすることにより、その文書の競合しない単一のバージョンを生成する必要があります。

リビジョンを比較して何が変更されているかを識別するために、アプリケーションはデータベースからすべてのバージョンを取得する必要があります。 まず、1 つの文書および競合するすべてのバージョンの詳細を取得します。 取得を開始するには、次のようなコマンドを使用します。このコマンドは _conflicts 配列も要求します。

https://$ACCOUNT.cloudant.com/products/$_ID?conflicts=true

このように取得することで、保管した文書の最新バージョン、および 同時に 取得する必要があるその他の競合するすべての文書 (...rev=2-61ae00e029d4f5edd2981841243ded13...rev=1-7438df87b632b312c53a08361a7c3299) のリストが提供されます。 これらのその他の競合する各バージョンも取得および保管されます。以下に例を示します。

https://$ACCOUNT.cloudant.com/products/$_ID?rev=2-61ae00e029d4f5edd2981841243ded13 https://$ACCOUNT.cloudant.com/products/$_ID?rev=1-7438df87b632b312c53a08361a7c3299

文書の競合するリビジョンをすべて入手したら、競合を解決できます。

前のシナリオでは、文書のバージョン間の違いは文書内の異なるフィールドに関するものだったため、より簡単にマージできました。

より複雑な競合では、それに対応してさらに分析が必要になる可能性があります。 これに役立つように、以下のようなさまざまな競合解決戦略の中から選択できます。

  • 時間基準 - 最初の編集または最後の編集の単純なテストを使用する。
  • ユーザー評価 - 競合はユーザーに報告され、その後そのユーザーが最良の解決策を決定する。
  • 洗練されたマージ・アルゴリズム - 多くの場合バージョン管理システムで使用される。 例えば、3 方向マージ

これらの変更を実装する方法の実例については、サンプル・コードを含むこのプロジェクトを参照してください。

新しいリビジョンのアップロード

以下は、解決し、前の競合するリビジョンからの変更をマージした後の、最終リビジョンです。

{
  "_id": "74b2be56045bed0c8c9d24b939000dbe",
  "_rev": "3-daaecd7213301a1ad5493186d6916755",
  "name": "Samsung Galaxy S4",
  "description": "Latest smartphone from Samsung",
  "price": 600
}

競合を評価して解決した後で、現行の確定版データを含む文書を作成します。 この新しい文書がデータベースにアップロードされます。

古いリビジョンの削除

古いリビジョンを削除するための要求の例を以下に示します。

DELETE https://$ACCOUNT.cloudant.com/products/$_ID?rev=2-61ae00e029d4f5edd2981841243ded13
DELETE https://$ACCOUNT.cloudant.com/products/$_ID?rev=2-f796915a291b37254f6df8f6f3389121

最終ステップでは、古いリビジョンを削除します。 古いリビジョンを削除するには、削除するリビジョンを指定して、 DELETE 要求を送信します。

文書の古いバージョンが削除されると、その文書に 関連付けられた競合には解決済みのマークが付けられます。 文書を再度要求することによって、競合がないことを確認できます。 conflicts パラメーターを true に設定し、 前述のように 競合の検出を使用します。