Azure Cosmos DB での競合解決ポリシーの管理Manage conflict resolution policies in Azure Cosmos DB

マルチリージョン書き込みを使用すると、複数のクライアントが同じ項目に書き込んだときに競合が発生する場合があります。With multi-region writes, when multiple clients write to the same item, conflicts may occur. 競合が発生した場合は、さまざまな競合解決ポリシーを使用してその競合を解決できます。When a conflict occurs, you can resolve the conflict by using different conflict resolution policies. この記事では、競合解決ポリシーを管理する方法について説明します。This article describes how to manage conflict resolution policies.

最終書き込み者優先競合解決ポリシーを作成するCreate a last-writer-wins conflict resolution policy

以下のサンプルでは、最終書き込み者優先競合解決ポリシーを使用してコンテナーを設定する方法について説明します。These samples show how to set up a container with a last-writer-wins conflict resolution policy. 最終書き込み者優先の既定のパスは、タイムスタンプ フィールドまたは _ts プロパティです。The default path for last-writer-wins is the timestamp field or the _ts property. SQL API の場合は、これを数値型のユーザー定義パスに設定することもできます。For SQL API, this may also be set to a user-defined path with a numeric type. 競合した場合には、最大値が優先されます。In a conflict, the highest value wins. このパスが設定されていない場合または無効な場合は、_ts が既定値になります。If the path isn't set or it's invalid, it defaults to _ts. このポリシーを使用して解決された競合は、競合フィードに表示されません。Conflicts resolved with this policy do not show up in the conflict feed. このポリシーはすべての API で使用できます。This policy can be used by all APIs.

.NET SDK V2.NET SDK V2

DocumentCollection lwwCollection = await createClient.CreateDocumentCollectionIfNotExistsAsync(
  UriFactory.CreateDatabaseUri(this.databaseName), new DocumentCollection
  {
      Id = this.lwwCollectionName,
      ConflictResolutionPolicy = new ConflictResolutionPolicy
      {
          Mode = ConflictResolutionMode.LastWriterWins,
          ConflictResolutionPath = "/myCustomId",
      },
  });

.NET SDK V3.NET SDK V3

Container container = await createClient.GetDatabase(this.databaseName)
    .CreateContainerIfNotExistsAsync(new ContainerProperties(this.lwwCollectionName, "/partitionKey")
    {
        ConflictResolutionPolicy = new ConflictResolutionPolicy()
        {
            Mode = ConflictResolutionMode.LastWriterWins,
            ResolutionPath = "/myCustomId",
        }
    });

Java Async SDKJava Async SDK

DocumentCollection collection = new DocumentCollection();
collection.setId(id);
ConflictResolutionPolicy policy = ConflictResolutionPolicy.createLastWriterWinsPolicy("/myCustomId");
collection.setConflictResolutionPolicy(policy);
DocumentCollection createdCollection = client.createCollection(databaseUri, collection, null).toBlocking().value();

Java Sync SDKJava Sync SDK

DocumentCollection lwwCollection = new DocumentCollection();
lwwCollection.setId(this.lwwCollectionName);
ConflictResolutionPolicy lwwPolicy = ConflictResolutionPolicy.createLastWriterWinsPolicy("/myCustomId");
lwwCollection.setConflictResolutionPolicy(lwwPolicy);
DocumentCollection createdCollection = this.tryCreateDocumentCollection(createClient, database, lwwCollection);

Node.js/JavaScript/TypeScript SDKNode.js/JavaScript/TypeScript SDK

const database = client.database(this.databaseName);
const { container: lwwContainer } = await database.containers.createIfNotExists(
  {
    id: this.lwwContainerName,
    conflictResolutionPolicy: {
      mode: "LastWriterWins",
      conflictResolutionPath: "/myCustomId"
    }
  }
);

Python SDKPython SDK

udp_collection = {
    'id': self.udp_collection_name,
    'conflictResolutionPolicy': {
        'mode': 'LastWriterWins',
        'conflictResolutionPath': '/myCustomId'
    }
}
udp_collection = self.try_create_document_collection(
    create_client, database, udp_collection)

ストアド プロシージャを使用したカスタム競合解決ポリシーを作成するCreate a custom conflict resolution policy using a stored procedure

以下のサンプルでは、競合を解決するストアド プロシージャが含まれたカスタム競合解決ポリシーを使用してコンテナーを設定する方法について説明します。These samples show how to set up a container with a custom conflict resolution policy with a stored procedure to resolve the conflict. ストアド プロシージャ内でエラーが発生しない限り、これらの競合は競合フィードに表示されません。These conflicts don't show up in the conflict feed unless there's an error in your stored procedure. このポリシーがコンテナーを使用して作成された後、ストアド プロシージャを作成する必要があります。After the policy is created with the container, you need to create the stored procedure. 以下の .NET SDK サンプルに例を示します。The .NET SDK sample below shows an example. このポリシーは、Core (SQL) API のみでサポートされています。This policy is supported on Core (SQL) Api only.

カスタム競合解決ストアド プロシージャの例Sample custom conflict resolution stored procedure

カスタム競合解決ストアド プロシージャは、以下に示す関数シグネチャを使用して実装する必要があります。Custom conflict resolution stored procedures must be implemented using the function signature shown below. この関数名は、ストアド プロシージャをコンテナーに登録したときに使用された名前と一致する必要はありませんが、名前付けを簡略化します。The function name does not need to match the name used when registering the stored procedure with the container but it does simplify naming. このストアド プロシージャに実装する必要があるパラメーターの説明を次に示します。Here is a description of the parameters that must be implemented for this stored procedure.

  • incomingItem: 競合が発生している、コミット時に挿入または更新される項目。incomingItem: The item being inserted or updated in the commit that is generating the conflicts. 削除操作の場合は null 値になります。Is null for delete operations.
  • existingItem: 現在コミットされている項目。existingItem: The currently committed item. この値は、更新では null 以外の値、挿入または削除では null 値になります。This value is non-null in an update and null for an insert or deletes.
  • isTombstone: incomingItem が以前に削除した項目と競合しているかどうかを示すブール値。isTombstone: Boolean indicating if the incomingItem is conflicting with a previously deleted item. true の場合は existingItem も null 値になります。When true, existingItem is also null.
  • conflictingItems: ID またはその他の一意なインデックス プロパティが incomingItem と競合している、コンテナー内のすべての項目のコミットされたバージョンによる配列。conflictingItems: Array of the committed version of all items in the container that are conflicting with incomingItem on ID or any other unique index properties.

重要

ストアド プロシージャと同様、カスタム競合解決プロシージャは、同じパーティション キーを使用してすべてのデータにアクセスすることも、挿入、更新、または削除操作を実行して競合を解決することもできます。Just as with any stored procedure, a custom conflict resolution procedure can access any data with the same partition key and can perform any insert, update or delete operation to resolve conflicts.

このストアド プロシージャの例では、/myCustomId パスから最小値を選択することで競合を解決します。This sample stored procedure resolves conflicts by selecting the lowest value from the /myCustomId path.

function resolver(incomingItem, existingItem, isTombstone, conflictingItems) {
  var collection = getContext().getCollection();

  if (!incomingItem) {
      if (existingItem) {

          collection.deleteDocument(existingItem._self, {}, function (err, responseOptions) {
              if (err) throw err;
          });
      }
  } else if (isTombstone) {
      // delete always wins.
  } else {
      if (existingItem) {
          if (incomingItem.myCustomId > existingItem.myCustomId) {
              return; // existing item wins
          }
      }

      var i;
      for (i = 0; i < conflictingItems.length; i++) {
          if (incomingItem.myCustomId > conflictingItems[i].myCustomId) {
              return; // existing conflict item wins
          }
      }

      // incoming item wins - clear conflicts and replace existing with incoming.
      tryDelete(conflictingItems, incomingItem, existingItem);
  }

  function tryDelete(documents, incoming, existing) {
      if (documents.length > 0) {
          collection.deleteDocument(documents[0]._self, {}, function (err, responseOptions) {
              if (err) throw err;

              documents.shift();
              tryDelete(documents, incoming, existing);
          });
      } else if (existing) {
          collection.replaceDocument(existing._self, incoming,
              function (err, documentCreated) {
                  if (err) throw err;
              });
      } else {
          collection.createDocument(collection.getSelfLink(), incoming,
              function (err, documentCreated) {
                  if (err) throw err;
              });
      }
  }
}

.NET SDK V2.NET SDK V2

DocumentCollection udpCollection = await createClient.CreateDocumentCollectionIfNotExistsAsync(
  UriFactory.CreateDatabaseUri(this.databaseName), new DocumentCollection
  {
      Id = this.udpCollectionName,
      ConflictResolutionPolicy = new ConflictResolutionPolicy
      {
          Mode = ConflictResolutionMode.Custom,
          ConflictResolutionProcedure = string.Format("dbs/{0}/colls/{1}/sprocs/{2}", this.databaseName, this.udpCollectionName, "resolver"),
      },
  });

//Create the stored procedure
await clients[0].CreateStoredProcedureAsync(
UriFactory.CreateStoredProcedureUri(this.databaseName, this.udpCollectionName, "resolver"), new StoredProcedure
{
    Id = "resolver",
    Body = File.ReadAllText(@"resolver.js")
});

.NET SDK V3.NET SDK V3

Container container = await createClient.GetDatabase(this.databaseName)
    .CreateContainerIfNotExistsAsync(new ContainerProperties(this.udpCollectionName, "/partitionKey")
    {
        ConflictResolutionPolicy = new ConflictResolutionPolicy()
        {
            Mode = ConflictResolutionMode.Custom,
            ResolutionProcedure = string.Format("dbs/{0}/colls/{1}/sprocs/{2}", this.databaseName, this.udpCollectionName, "resolver")
        }
    });

await container.Scripts.CreateStoredProcedureAsync(
    new StoredProcedureProperties("resolver", File.ReadAllText(@"resolver.js"))
);

Java Async SDKJava Async SDK

DocumentCollection collection = new DocumentCollection();
collection.setId(id);
ConflictResolutionPolicy policy = ConflictResolutionPolicy.createCustomPolicy("resolver");
collection.setConflictResolutionPolicy(policy);
DocumentCollection createdCollection = client.createCollection(databaseUri, collection, null).toBlocking().value();

コンテナーが作成されたら、resolver ストアド プロシージャを作成する必要があります。After your container is created, you must create the resolver stored procedure.

Java Sync SDKJava Sync SDK

DocumentCollection udpCollection = new DocumentCollection();
udpCollection.setId(this.udpCollectionName);
ConflictResolutionPolicy udpPolicy = ConflictResolutionPolicy.createCustomPolicy(
        String.format("dbs/%s/colls/%s/sprocs/%s", this.databaseName, this.udpCollectionName, "resolver"));
udpCollection.setConflictResolutionPolicy(udpPolicy);
DocumentCollection createdCollection = this.tryCreateDocumentCollection(createClient, database, udpCollection);

コンテナーが作成されたら、resolver ストアド プロシージャを作成する必要があります。After your container is created, you must create the resolver stored procedure.

Node.js/JavaScript/TypeScript SDKNode.js/JavaScript/TypeScript SDK

const database = client.database(this.databaseName);
const { container: udpContainer } = await database.containers.createIfNotExists(
  {
    id: this.udpContainerName,
    conflictResolutionPolicy: {
      mode: "Custom",
      conflictResolutionProcedure: `dbs/${this.databaseName}/colls/${
        this.udpContainerName
      }/sprocs/resolver`
    }
  }
);

コンテナーが作成されたら、resolver ストアド プロシージャを作成する必要があります。After your container is created, you must create the resolver stored procedure.

Python SDKPython SDK

udp_collection = {
    'id': self.udp_collection_name,
    'conflictResolutionPolicy': {
        'mode': 'Custom',
        'conflictResolutionProcedure': 'dbs/' + self.database_name + "/colls/" + self.udp_collection_name + '/sprocs/resolver'
    }
}
udp_collection = self.try_create_document_collection(
    create_client, database, udp_collection)

コンテナーが作成されたら、resolver ストアド プロシージャを作成する必要があります。After your container is created, you must create the resolver stored procedure.

カスタム競合解決ポリシーを作成するCreate a custom conflict resolution policy

以下のサンプルでは、カスタム競合解決ポリシーを使用してコンテナーを設定する方法について説明します。These samples show how to set up a container with a custom conflict resolution policy. これらの競合は競合フィードに表示されます。These conflicts show up in the conflict feed.

.NET SDK V2.NET SDK V2

DocumentCollection manualCollection = await createClient.CreateDocumentCollectionIfNotExistsAsync(
  UriFactory.CreateDatabaseUri(this.databaseName), new DocumentCollection
  {
      Id = this.manualCollectionName,
      ConflictResolutionPolicy = new ConflictResolutionPolicy
      {
          Mode = ConflictResolutionMode.Custom,
      },
  });

.NET SDK V3.NET SDK V3

Container container = await createClient.GetDatabase(this.databaseName)
    .CreateContainerIfNotExistsAsync(new ContainerProperties(this.manualCollectionName, "/partitionKey")
    {
        ConflictResolutionPolicy = new ConflictResolutionPolicy()
        {
            Mode = ConflictResolutionMode.Custom
        }
    });

Java Async SDKJava Async SDK

DocumentCollection collection = new DocumentCollection();
collection.setId(id);
ConflictResolutionPolicy policy = ConflictResolutionPolicy.createCustomPolicy();
collection.setConflictResolutionPolicy(policy);
DocumentCollection createdCollection = client.createCollection(databaseUri, collection, null).toBlocking().value();

Java Sync SDKJava Sync SDK

DocumentCollection manualCollection = new DocumentCollection();
manualCollection.setId(this.manualCollectionName);
ConflictResolutionPolicy customPolicy = ConflictResolutionPolicy.createCustomPolicy(null);
manualCollection.setConflictResolutionPolicy(customPolicy);
DocumentCollection createdCollection = client.createCollection(database.getSelfLink(), collection, null).getResource();

Node.js/JavaScript/TypeScript SDKNode.js/JavaScript/TypeScript SDK

const database = client.database(this.databaseName);
const {
  container: manualContainer
} = await database.containers.createIfNotExists({
  id: this.manualContainerName,
  conflictResolutionPolicy: {
    mode: "Custom"
  }
});

Python SDKPython SDK

database = client.ReadDatabase("dbs/" + self.database_name)
manual_collection = {
    'id': self.manual_collection_name,
    'conflictResolutionPolicy': {
        'mode': 'Custom'
    }
}
manual_collection = client.CreateContainer(database['_self'], collection)

競合フィードから読み取りを行うRead from conflict feed

以下のサンプルでは、コンテナーの競合フィードから読み取りを行う方法について説明します。These samples show how to read from a container's conflict feed. 競合が競合フィードに表示されるのは、自動的に解決されなかった場合またはカスタム競合ポリシーを使用している場合のみです。Conflicts show up in the conflict feed only if they weren't resolved automatically or if using a custom conflict policy.

.NET SDK V2.NET SDK V2

FeedResponse<Conflict> conflicts = await delClient.ReadConflictFeedAsync(this.collectionUri);

.NET SDK V3.NET SDK V3

FeedIterator<ConflictProperties> conflictFeed = container.Conflicts.GetConflictIterator();
while (conflictFeed.HasMoreResults)
{
    FeedResponse<ConflictProperties> conflicts = await conflictFeed.ReadNextAsync();
    foreach (ConflictProperties conflict in conflicts)
    {
        // Read the conflicted content
        MyClass intendedChanges = container.Conflicts.ReadConflictContent<MyClass>(conflict);
        MyClass currentState = await container.Conflicts.ReadCurrentAsync<MyClass>(conflict, new PartitionKey(intendedChanges.MyPartitionKey));

        // Do manual merge among documents
        await container.ReplaceItemAsync<MyClass>(intendedChanges, intendedChanges.Id, new PartitionKey(intendedChanges.MyPartitionKey));

        // Delete the conflict
        await container.Conflicts.DeleteAsync(conflict, new PartitionKey(intendedChanges.MyPartitionKey));
    }
}

Java Async SDKJava Async SDK

FeedResponse<Conflict> response = client.readConflicts(this.manualCollectionUri, null)
                    .first().toBlocking().single();
for (Conflict conflict : response.getResults()) {
    /* Do something with conflict */
}

Java Sync SDKJava Sync SDK

Iterator<Conflict> conflictsIterator = client.readConflicts(this.collectionLink, null).getQueryIterator();
while (conflictsIterator.hasNext()) {
    Conflict conflict = conflictsIterator.next();
    /* Do something with conflict */
}

Node.js/JavaScript/TypeScript SDKNode.js/JavaScript/TypeScript SDK

const container = client
  .database(this.databaseName)
  .container(this.lwwContainerName);

const { result: conflicts } = await container.conflicts.readAll().toArray();

PythonPython

conflicts_iterator = iter(client.ReadConflicts(self.manual_collection_link))
conflict = next(conflicts_iterator, None)
while conflict:
    # Do something with conflict
    conflict = next(conflicts_iterator, None)

次の手順Next steps

Azure Cosmos DB の次の概念について学習しましょう。Learn about the following Azure Cosmos DB concepts: