Administración de directivas de resolución de conflictos en Azure Cosmos DBManage conflict resolution policies in Azure Cosmos DB

Con las operaciones de escritura en varias regiones, cuando varios clientes escriben en el mismo elemento podrían producirse conflictos.With multi-region writes, when multiple clients write to the same item, conflicts may occur. Cuando se produce un conflicto de datos, se puede resolver mediante el uso de distintas directivas de resolución de conflictos.When a conflict occurs, you can resolve the conflict by using different conflict resolution policies. En este artículo se describe cómo administrar directivas de resolución de conflictos.This article describes how to manage conflict resolution policies.

Creación de una directiva de resolución de conflictos del tipo "el último en escribir gana"Create a last-writer-wins conflict resolution policy

En estos ejemplos se muestra cómo configurar un contenedor con una directiva de resolución de conflictos del tipo "el último en escribir gana".These samples show how to set up a container with a last-writer-wins conflict resolution policy. La ruta de acceso predeterminada para modo "el último en escribir gana" es el campo de marca de tiempo o la propiedad _ts.The default path for last-writer-wins is the timestamp field or the _ts property. Para la API SQL, también se puede establecer en una ruta de acceso definida por el usuario con un tipo numérico.For SQL API, this may also be set to a user-defined path with a numeric type. En un conflicto, gana el valor más alto.In a conflict, the highest value wins. Si no se establece la ruta de acceso o la que se establece no es válida, se usará de forma predeterminada _ts.If the path isn't set or it's invalid, it defaults to _ts. Los conflictos resueltos con esta directiva no se muestran en la fuente de conflictos.Conflicts resolved with this policy do not show up in the conflict feed. Todas las API pueden usar esta directiva.This policy can be used by all APIs.

SDK de .NET 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",
      },
  });

SDK de .NET 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",
        }
    });

SDK asincrónico para JavaJava 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();

SDK sincrónico para JavaJava 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);

SDK para Node.js/JavaScript/TypeScriptNode.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"
    }
  }
);

SDK para PythonPython 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)

Creación de una directiva de resolución de conflictos personalizada con un procedimiento almacenadoCreate a custom conflict resolution policy using a stored procedure

En estos ejemplos se muestran cómo configurar un contenedor con una directiva de resolución de conflictos personalizada con un procedimiento almacenado para resolver el conflicto.These samples show how to set up a container with a custom conflict resolution policy with a stored procedure to resolve the conflict. Estos conflictos no se mostrarán en la fuente de conflictos, salvo que haya un error en el procedimiento almacenado.These conflicts don't show up in the conflict feed unless there's an error in your stored procedure. Una vez que se crea una directiva en el contenedor, es necesario crear el procedimiento almacenado.After the policy is created with the container, you need to create the stored procedure. En el ejemplo del SDK de .NET siguiente se muestra un ejemplo.The .NET SDK sample below shows an example. Esta directiva solo se admite en Core (SQL) API.This policy is supported on Core (SQL) Api only.

Ejemplo de un procedimiento almacenado de resolución de conflictos personalizadaSample custom conflict resolution stored procedure

Los procedimientos almacenados de resolución de conflictos personalizada deben implementarse mediante la signatura de función que se muestra a continuación.Custom conflict resolution stored procedures must be implemented using the function signature shown below. No es necesario que el nombre de la función coincida con el nombre usado al registrar el procedimiento almacenado con el contenedor, pero simplifica la nomenclatura.The function name does not need to match the name used when registering the stored procedure with the container but it does simplify naming. A continuación, se muestra una descripción de los parámetros que se deben implementar para este procedimiento almacenado.Here is a description of the parameters that must be implemented for this stored procedure.

  • incomingItem: el elemento insertado o actualizado en la confirmación que está generando los conflictos.incomingItem: The item being inserted or updated in the commit that is generating the conflicts. Es nulo para las operaciones de eliminación.Is null for delete operations.
  • existingItem: el elemento confirmado actualmente.existingItem: The currently committed item. Este valor no es NULL en una actualización ye s NULL en una inserción o eliminaciones.This value is non-null in an update and null for an insert or deletes.
  • isTombstone: valor booleano que indica si incomingItem está en conflicto con un elemento eliminado anteriormente.isTombstone: Boolean indicating if the incomingItem is conflicting with a previously deleted item. Cuando es verdadero, existingItem también es nulo.When true, existingItem is also null.
  • conflictingItems: Matriz de la versión confirmada de todos los elementos del contenedor que están en conflicto con incomingItem en el identificador o cualquier otra propiedad de índice único.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.

Importante

Al igual que con cualquier procedimiento almacenado, un procedimiento de resolución de conflictos personalizado puede acceder a los datos con la misma clave de partición y puede realizar cualquier operación de inserción, actualización o eliminación para resolver los conflictos.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.

Este procedimiento almacenado de ejemplo resuelve los conflictos mediante la selección del valor más bajo de la ruta de acceso /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;
              });
      }
  }
}

SDK de .NET 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")
});

SDK de .NET 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"))
);

SDK asincrónico para JavaJava 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();

Una vez creado el contenedor, debe crear el procedimiento almacenado resolver.After your container is created, you must create the resolver stored procedure.

SDK sincrónico para JavaJava 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);

Una vez creado el contenedor, debe crear el procedimiento almacenado resolver.After your container is created, you must create the resolver stored procedure.

SDK para Node.js/JavaScript/TypeScriptNode.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`
    }
  }
);

Una vez creado el contenedor, debe crear el procedimiento almacenado resolver.After your container is created, you must create the resolver stored procedure.

SDK para PythonPython 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)

Una vez creado el contenedor, debe crear el procedimiento almacenado resolver.After your container is created, you must create the resolver stored procedure.

Creación de una directiva de resolución de conflictos personalizadaCreate a custom conflict resolution policy

En estos ejemplos se muestran cómo configurar un contenedor con una directiva de resolución de conflictos personalizada.These samples show how to set up a container with a custom conflict resolution policy. Estos conflictos se mostrarán en la fuente de conflictos.These conflicts show up in the conflict feed.

SDK de .NET V2.NET SDK V2

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

SDK de .NET v3.NET SDK V3

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

SDK asincrónico para JavaJava 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();

SDK sincrónico para JavaJava 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();

SDK para Node.js/JavaScript/TypeScriptNode.js/JavaScript/TypeScript SDK

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

SDK para PythonPython 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)

Lectura desde la fuente de conflictosRead from conflict feed

En estos ejemplos se muestra cómo leer desde la fuente de conflictos de un contenedor.These samples show how to read from a container's conflict feed. Los conflictos solo se muestran en la fuente si no se han resuelto de forma automática o si se usa una directiva de conflictos personalizada.Conflicts show up in the conflict feed only if they weren't resolved automatically or if using a custom conflict policy.

SDK de .NET V2.NET SDK V2

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

SDK de .NET v3.NET SDK V3

FeedIterator<ConflictProperties> conflictFeed = container.Conflicts.GetConflictQueryIterator();
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));
    }
}

SDK asincrónico para JavaJava Async SDK

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

SDK sincrónico para JavaJava Sync SDK

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

SDK para Node.js/JavaScript/TypeScriptNode.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)

Pasos siguientesNext steps

Obtenga información acerca de los siguientes conceptos de Azure Cosmos DB:Learn about the following Azure Cosmos DB concepts: