Mise à jour de lot conditionnelle
DynamoDB prend en charge les opérations groupées, par exemple BatchWriteItem, grâce auxquelles vous pouvez effectuer jusqu’à 25 demandes PutItem et DeleteItem en un seul lot. Cependant, BatchWriteItem ne prend pas en charge les opérations UpdateItem ni les expressions de condition. En solution de contournement, vous pouvez utiliser d’autres API DynamoDB, par exemple TransactWriteItems pour une taille de lot maximale de 100.
Lorsque d’autres éléments sont concernés et que vous devez modifier une grande partie des données, vous pouvez utiliser des services tels que AWS Glue, Amazon EMR, AWS Step Functions ou utiliser des scripts et des outils personnalisés tels que DynamoDB-shell pour des mises à jour groupées efficaces.
Quand utiliser ce modèle
DynamoDB-Shell n’est pas un cas d’utilisation pris en charge pour la production.
TransactWriteItems: jusqu’à 100 mises à jour individuelles avec ou sans conditions, exécutées sous la forme d’un bundle ACID « tout ou rien ». Les appelsTransactWriteItemspeuvent également être fournis avec unClientRequestTokensi votre application nécessite une idempotence, ce qui signifie que plusieurs appels identiques ont le même effet qu’un seul appel. Cela garantit que vous n’exécuterez pas la même transaction plusieurs fois et que vous n’obtiendrez pas un état de données incorrect.Compromis : plus de débit est consommé. 2 WCU par 1 Ko d’écriture au lieu de 1 WGU standard par 1 Ko d’écriture.
PartiQL
BatchExecuteStatement: jusqu’à 25 mises à jour avec ou sans conditions.BatchExecuteStatementrenvoie toujours une réponse positive à la demande globale et renvoie également une liste de réponses aux opérations individuelles qui préserve l’ordre.Compromis : pour les lots plus importants, une logique supplémentaire côté client est requise pour distribuer les demandes par lots de 25. Les réponses d’erreur individuelles doivent être prises en compte pour déterminer la stratégie de nouvelle tentative.
Exemples de code
Ces exemples de code utilisent la bibliothèque boto3, qui est le kit AWS SDK pour Python. Cet exemple suppose que boto3 est installé et configuré avec les informations d’identification AWS appropriées.
Supposons une base de données d’inventaire pour un fournisseur d’appareils électriques qui possède plusieurs entrepôts dans des villes européennes. À l’approche de la fin de l’été, le vendeur aimerait se débarrasser des ventilateurs de bureau pour faire de la place pour d’autres stocks. Le vendeur souhaite offrir une réduction sur tous les ventilateurs de bureau fournis par des entrepôts en Italie, mais uniquement s’il dispose d’un stock de réserve de 20 ventilateurs de bureau. La table DynamoDB est appelée inventaire. Elle possède un schéma clé de partition sku, qui est un identifiant unique pour chaque produit, et un entrepôt de clé de tri, qui est un identifiant d’entrepôt.
Le code Python suivant montre comment effectuer cette mise à jour conditionnelle par lots à l’aide d’un appel d’API BatchExecuteStatement.
import boto3 client=boto3.client("dynamodb") before_image=client.query(TableName='inventory', KeyConditionExpression='sku=:pk_val AND begins_with(warehouse, :sk_val)', ExpressionAttributeValues={':pk_val':{'S':'F123'},':sk_val':{'S':'WIT'}}, ProjectionExpression='sku,warehouse,quantity,price') print("Before update: ", before_image['Items']) response=client.batch_execute_statement( Statements=[ {'Statement': 'UPDATE inventory SET price=price-5 WHERE sku=? AND warehouse=? AND quantity > 20', 'Parameters': [{'S':'F123'}, {'S':'WITTUR1'}], 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}, {'Statement': 'UPDATE inventory SET price=price-5 WHERE sku=? AND warehouse=? AND quantity > 20', 'Parameters': [{'S':'F123'}, {'S':'WITROM1'}], 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}, {'Statement': 'UPDATE inventory SET price=price-5 WHERE sku=? AND warehouse=? AND quantity > 20', 'Parameters': [{'S':'F123'}, {'S':'WITROM2'}], 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}, {'Statement': 'UPDATE inventory SET price=price-5 WHERE sku=? AND warehouse=? AND quantity > 20', 'Parameters': [{'S':'F123'}, {'S':'WITROM5'}], 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}, {'Statement': 'UPDATE inventory SET price=price-5 WHERE sku=? AND warehouse=? AND quantity > 20', 'Parameters': [{'S':'F123'}, {'S':'WITVEN1'}], 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}, {'Statement': 'UPDATE inventory SET price=price-5 WHERE sku=? AND warehouse=? AND quantity > 20', 'Parameters': [{'S':'F123'}, {'S':'WITVEN2'}], 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}, {'Statement': 'UPDATE inventory SET price=price-5 WHERE sku=? AND warehouse=? AND quantity > 20', 'Parameters': [{'S':'F123'}, {'S':'WITVEN3'}], 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}, ], ReturnConsumedCapacity='TOTAL' ) after_image=client.query(TableName='inventory', KeyConditionExpression='sku=:pk_val AND begins_with(warehouse, :sk_val)', ExpressionAttributeValues={':pk_val':{'S':'F123'},':sk_val':{'S':'WIT'}}, ProjectionExpression='sku,warehouse,quantity,price') print("After update: ", after_image['Items'])
L’exécution produit le résultat ci-dessous sur des exemples de données :
Before update: [{'quantity': {'N': '20'}, 'warehouse': {'S': 'WITROM1'}, 'price': {'N': '40'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '25'}, 'warehouse': {'S': 'WITROM2'}, 'price': {'N': '40'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '28'}, 'warehouse': {'S': 'WITROM5'}, 'price': {'N': '38'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '26'}, 'warehouse': {'S': 'WITTUR1'}, 'price': {'N': '40'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '10'}, 'warehouse': {'S': 'WITVEN1'}, 'price': {'N': '38'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '20'}, 'warehouse': {'S': 'WITVEN2'}, 'price': {'N': '38'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '50'}, 'warehouse': {'S': 'WITVEN3'}, 'price': {'N': '35'}, 'sku': {'S': 'F123'}}] After update: [{'quantity': {'N': '20'}, 'warehouse': {'S': 'WITROM1'}, 'price': {'N': '40'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '25'}, 'warehouse': {'S': 'WITROM2'}, 'price': {'N': '35'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '28'}, 'warehouse': {'S': 'WITROM5'}, 'price': {'N': '33'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '26'}, 'warehouse': {'S': 'WITTUR1'}, 'price': {'N': '35'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '10'}, 'warehouse': {'S': 'WITVEN1'}, 'price': {'N': '38'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '20'}, 'warehouse': {'S': 'WITVEN2'}, 'price': {'N': '38'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '50'}, 'warehouse': {'S': 'WITVEN3'}, 'price': {'N': '30'}, 'sku': {'S': 'F123'}}]
Comme il s’agit d’une opération limitée pour un système interne, les exigences d’idempotence n’ont pas été prises en compte. Il est possible de placer des barrières de protection supplémentaires, comme la mise à jour des prix uniquement si le prix est supérieur à 35 et inférieur à 40 pour rendre les mises à jour plus robustes.
Nous pouvons également effectuer la même opération de mise à jour groupée en utilisant TransactWriteItems en cas d’idempotence et d’exigences ACID plus strictes. Cependant, il est important de se rappeler que soit toutes les opérations du lot de transactions sont effectuées, soit l’ensemble du lot échoue.
Supposons qu’il y ait une vague de chaleur en Italie et que la demande de ventilateurs de bureau ait fortement augmenté. Le fournisseur souhaite augmenter de 20 euros le coût de ses ventilateurs de bureau sortant de tous ses entrepôts en Italie, mais l’organisme de réglementation n’autorise cette augmentation des coûts que si le coût actuel est inférieur à 70 euros pour l’ensemble de son inventaire. Il est essentiel que le prix soit mis à jour dans l’ensemble de l’inventaire en une seule fois et uniquement si le coût est inférieur à 70 euros dans chacun de ses entrepôts.
Le code Python suivant montre comment effectuer cette mise à jour groupée à l’aide d’un appel d’API TransactWriteItems.
import boto3 client=boto3.client("dynamodb") before_image=client.query(TableName='inventory', KeyConditionExpression='sku=:pk_val AND begins_with(warehouse, :sk_val)', ExpressionAttributeValues={':pk_val':{'S':'F123'},':sk_val':{'S':'WIT'}}, ProjectionExpression='sku,warehouse,quantity,price') print("Before update: ", before_image['Items']) response=client.transact_write_items( ClientRequestToken='UUIDAWS124', TransactItems=[ {'Update': { 'Key': {'sku': {'S':'F123'}, 'warehouse': {'S':'WITTUR1'}}, 'UpdateExpression': 'SET price = price + :inc', 'ConditionExpression': 'price < :cap', 'ExpressionAttributeValues': { ':inc': {'N': '20'}, ':cap': {'N': '70'}}, 'TableName': 'inventory', 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}}, {'Update': { 'Key': {'sku': {'S':'F123'}, 'warehouse': {'S':'WITROM1'}}, 'UpdateExpression': 'SET price = price + :inc', 'ConditionExpression': 'price < :cap', 'ExpressionAttributeValues': { ':inc': {'N': '20'}, ':cap': {'N': '70'}}, 'TableName': 'inventory', 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}}, {'Update': { 'Key': {'sku': {'S':'F123'}, 'warehouse': {'S':'WITROM2'}}, 'UpdateExpression': 'SET price = price + :inc', 'ConditionExpression': 'price < :cap', 'ExpressionAttributeValues': { ':inc': {'N': '20'}, ':cap': {'N': '70'}}, 'TableName': 'inventory', 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}}, {'Update': { 'Key': {'sku': {'S':'F123'}, 'warehouse': {'S':'WITROM5'}}, 'UpdateExpression': 'SET price = price + :inc', 'ConditionExpression': 'price < :cap', 'ExpressionAttributeValues': { ':inc': {'N': '20'}, ':cap': {'N': '70'}}, 'TableName': 'inventory', 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}}, {'Update': { 'Key': {'sku': {'S':'F123'}, 'warehouse': {'S':'WITVEN1'}}, 'UpdateExpression': 'SET price = price + :inc', 'ConditionExpression': 'price < :cap', 'ExpressionAttributeValues': { ':inc': {'N': '20'}, ':cap': {'N': '70'}}, 'TableName': 'inventory', 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}}, {'Update': { 'Key': {'sku': {'S':'F123'}, 'warehouse': {'S':'WITVEN2'}}, 'UpdateExpression': 'SET price = price + :inc', 'ConditionExpression': 'price < :cap', 'ExpressionAttributeValues': { ':inc': {'N': '20'}, ':cap': {'N': '70'}}, 'TableName': 'inventory', 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}}, {'Update': { 'Key': {'sku': {'S':'F123'}, 'warehouse': {'S':'WITVEN3'}}, 'UpdateExpression': 'SET price = price + :inc', 'ConditionExpression': 'price < :cap', 'ExpressionAttributeValues': { ':inc': {'N': '20'}, ':cap': {'N': '70'}}, 'TableName': 'inventory', 'ReturnValuesOnConditionCheckFailure': 'ALL_OLD'}}, ], ReturnConsumedCapacity='TOTAL' ) after_image=client.query(TableName='inventory', KeyConditionExpression='sku=:pk_val AND begins_with(warehouse, :sk_val)', ExpressionAttributeValues={':pk_val':{'S':'F123'},':sk_val':{'S':'WIT'}}, ProjectionExpression='sku,warehouse,quantity,price') print("After update: ", after_image['Items'])
L’exécution produit le résultat ci-dessous sur des exemples de données :
Before update: [{'quantity': {'N': '20'}, 'warehouse': {'S': 'WITROM1'}, 'price': {'N': '60'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '25'}, 'warehouse': {'S': 'WITROM2'}, 'price': {'N': '55'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '28'}, 'warehouse': {'S': 'WITROM5'}, 'price': {'N': '53'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '26'}, 'warehouse': {'S': 'WITTUR1'}, 'price': {'N': '55'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '10'}, 'warehouse': {'S': 'WITVEN1'}, 'price': {'N': '58'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '20'}, 'warehouse': {'S': 'WITVEN2'}, 'price': {'N': '58'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '50'}, 'warehouse': {'S': 'WITVEN3'}, 'price': {'N': '50'}, 'sku': {'S': 'F123'}}] After update: [{'quantity': {'N': '20'}, 'warehouse': {'S': 'WITROM1'}, 'price': {'N': '80'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '25'}, 'warehouse': {'S': 'WITROM2'}, 'price': {'N': '75'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '28'}, 'warehouse': {'S': 'WITROM5'}, 'price': {'N': '73'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '26'}, 'warehouse': {'S': 'WITTUR1'}, 'price': {'N': '75'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '10'}, 'warehouse': {'S': 'WITVEN1'}, 'price': {'N': '78'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '20'}, 'warehouse': {'S': 'WITVEN2'}, 'price': {'N': '78'}, 'sku': {'S': 'F123'}}, {'quantity': {'N': '50'}, 'warehouse': {'S': 'WITVEN3'}, 'price': {'N': '70'}, 'sku': {'S': 'F123'}}]
Plusieurs approches permettent d’effectuer des mises à jour groupées dans DynamoDB. L’approche appropriée dépend de facteurs tels que les exigences en matière d’ACID et/ou d’idempotence, le nombre d’éléments à mettre à jour et la familiarité avec les API.