Реализация контроля оптимистичного параллелизма

Завершено

Использование пакета SDK для чтения элемента и обновления того же элемента в последующих операциях приводит к некоторому риску. Другая операция может поступать от отдельного клиента и изменять базовый документ до завершения операции обновления первого клиента. Этот конфликт может создать "потерянное обновление" ситуации. Давайте продемонстрируем этот конфликт на примере.

Ниже приведен типичный пример кода C# с отдельной операцией чтения и обновления.

string categoryId = "9603ca6c-9e28-4a02-9194-51cdb7fea816";
PartitionKey partitionKey = new (categoryId);

Product product = await container.ReadItemAsync<Product>("01AC0", partitionKey);

product.price = 50d;

await container.UpsertItemAsync<Product>(product, partitionKey);

Так как чтение и запись в этом примере являются различными операциями, между этими операциями возникает задержка. Эта задержка обозначена на схеме как n.

схеме задержки N между чтением и обновлением.

Эта задержка может составлять всего несколько миллисекунд или секунд, но все равно может приводить к потере обновлений. Некоторые приложения, которые сталкиваются с пользователем, когда входные данные пользователя вызывают большую задержку между операцией чтения и обновления, могут привести к более длинному n значению и более высокому потенциалу для потерянных обновлений. Эту проблему можно устранить путем реализации контроля оптимистичного параллелизма.

ItemResponse<Product> response = await container.ReadItemAsync<Product>("01AC0", partitionKey);

Каждый элемент имеет значение ETag. Это значение обновляется при обновлении элемента. Вы можете получить значение ETag элемента, наблюдая за ответом из операции чтения запроса.

Класс ItemResponse имеет свойство ETag, которое содержит соответствующее строковое значение.

Product product = response.Resource;
string eTag = response.ETag;

Чтобы предотвратить потерю обновлений, можно использовать правило if-match, чтобы узнать, совпадает ли ETag с текущим заголовком ETag элемента на стороне сервера в рамках запроса на обновление.

ItemRequestOptions options = new ItemRequestOptions { IfMatchEtag = eTag };
await container.UpsertItemAsync<Product>(product, partitionKey, requestOptions: options);

Если значение ETag не соответствует текущему заголовку ETag на стороне сервера элементов, операция завершается ошибкой, и необходимо повторно считывать элемент, чтобы получить последнюю версию элемента, прежде чем пытаться обновить его еще раз. Код сбоя — это 412 предварительных условий.

Обновления кода C# требуют только незначительных изменений для реализации управления оптимистическим параллелизмом, чтобы гарантировать, что операции обновления не потеряли ранее сохраненные изменения на стороне сервера конкурирующими клиентами.

string categoryId = "9603ca6c-9e28-4a02-9194-51cdb7fea816";
PartitionKey partitionKey = new (categoryId);

ItemResponse<Product> response = await container.ReadItemAsync<Product>("01AC0", partitionKey);
Product product = response.Resource;
string eTag = response.ETag;

product.price = 50d;

ItemRequestOptions options = new ItemRequestOptions { IfMatchEtag = eTag };
await container.UpsertItemAsync<Product>(product, partitionKey, requestOptions: options);

Ниже приведен типичный пример кода Python с отдельной операцией чтения и обновления.

item_id = "01AC0"
partition_key = "9603ca6c-9e28-4a02-9194-51cdb7fea816"

# Read the item
item_response = container.read_item(item=item_id, partition_key=partition_key)
product = item_response

# Update the product price
product["price"] = 50.0

# Upsert the item back to the container
container.upsert_item(body=product)

Так как чтение и запись в этом примере являются различными операциями, между этими операциями возникает задержка. Эта задержка обозначена на схеме как n.

схеме задержки N между чтением и обновлением.

Эта задержка может составлять всего несколько миллисекунд или секунд, но все равно может приводить к потере обновлений. Некоторые приложения, которые сталкиваются с пользователем, когда входные данные пользователя вызывают большую задержку между операцией чтения и обновления, могут привести к более длинному n значению и более высокому потенциалу для потерянных обновлений. Эту проблему можно устранить путем реализации контроля оптимистичного параллелизма.

item_response = container.read_item(item=item_id, partition_key=partition_key)
product = item_response

Каждый элемент имеет значение ETag. Это значение обновляется при обновлении элемента. Вы можете получить значение ETag элемента, наблюдая за заголовками из операции чтения .

Существует два способа доступа к значению ETag элемента. Одним из способов является использование метода get_response_headers. В этом методе значение ETag называется etag.

headers = item_response.get_response_headers()
etag = headers.get("etag")

Другим способом является использование метода в ответе элемента. С помощью этого метода значение ETag называется _etag.

etag = item_response.get("_etag")

Чтобы предотвратить потерянные обновления, можно использовать условие if-match, чтобы узнать, соответствует ли ETag текущий заголовок ETag на стороне сервера элемента в рамках запроса на обновление.

headers = {"If-Match": etag}
container.upsert_item(body=product, headers=headers)

Если значение ETag не соответствует текущему заголовку ETag на стороне сервера элементов, операция завершается ошибкой, и необходимо повторно считывать элемент, чтобы получить последнюю версию элемента, прежде чем пытаться обновить его еще раз. Код сбоя — это 412 предварительных условий.

Обновления кода Python требуют только незначительных изменений для реализации управления оптимистическим параллелизмом, чтобы гарантировать, что операции обновления не потеряли ранее сохраненные изменения на стороне сервера конкурирующими клиентами.

item_id = "01AC0"
partition_key = "9603ca6c-9e28-4a02-9194-51cdb7fea816"

# Read the item
item_response = container.read_item(item=item_id, partition_key=partition_key)
product = item_response
etag = item_response.get("_etag")

# Update the product price
product["price"] = 50.0

# Use optimistic concurrency control
headers = {"If-Match": etag}
container.upsert_item(body=product, headers=headers)

Ниже приведен типичный пример кода JavaScript с отдельной операцией чтения и обновления.

const categoryId = "9603ca6c-9e28-4a02-9194-51cdb7fea816";
const itemId = "01AC0";

// Read the item
const { resource: product } = await container.item(itemId, categoryId).read();

// Update the price
product.price = 50.0;

// Upsert the updated item
await container.items.upsert(product);

Так как чтение и запись в этом примере являются различными операциями, между этими операциями возникает задержка. Эта задержка обозначена на схеме как n.

схеме задержки N между чтением и обновлением.

Эта задержка может составлять всего несколько миллисекунд или секунд, но все равно может приводить к потере обновлений. Некоторые приложения, которые сталкиваются с пользователем, когда входные данные пользователя вызывают большую задержку между операцией чтения и обновления, могут привести к более длинному n значению и более высокому потенциалу для потерянных обновлений. Эту проблему можно устранить путем реализации контроля оптимистичного параллелизма.

Каждый элемент имеет значение ETag. Это значение обновляется при обновлении элемента. Вы можете получить значение ETag элемента, наблюдая заголовки ответа из операции чтения .

const { resource: product, headers } = await container.item(itemId, categoryId).read();
const eTag = headers.etag;

Чтобы предотвратить потерю обновлений, можно использовать правило if-match, чтобы узнать, совпадает ли ETag с текущим заголовком ETag элемента на стороне сервера в рамках запроса на обновление.

// Update the product
product.price = 50.0;

// Use the if-match header for optimistic concurrency control
await container.items.upsert(product, {
  accessCondition: { type: "IfMatch", condition: eTag }
});

Если значение ETag не соответствует текущему заголовку ETag на стороне сервера элементов, операция завершается ошибкой, и необходимо повторно считывать элемент, чтобы получить последнюю версию элемента, прежде чем пытаться обновить его еще раз. Код сбоя — это 412 предварительных условий.

Обновления кода JavaScript требуют только незначительных изменений для реализации управления оптимистичным параллелизмом, чтобы гарантировать, что операции обновления не теряют ранее сохраненные изменения на стороне сервера конкурирующими клиентами.

const categoryId = "9603ca6c-9e28-4a02-9194-51cdb7fea816";
const itemId = "01AC0";

// Read the item
const { resource: product, headers } = await container.item(itemId, categoryId).read();
const eTag = headers.etag;

// Update the product
product.price = 50.0;

// Use the if-match header for optimistic concurrency control
await container.items.upsert(product, {
  accessCondition: { type: "IfMatch", condition: eTag }
});