

# Prácticas recomendadas para utilizar operaciones de datos masivos en DynamoDB
<a name="BestPractices_BulkDataOperations"></a>

DynamoDB admite operaciones por lotes, como utilizar `BatchWriteItem`, que puede realizar hasta 25 solicitudes `PutItem` y `DeleteItem` juntas. Sin embargo, `BatchWriteItem` no admite operaciones `UpdateItem`. Cuando se trata de actualizaciones masivas, la distinción radica en los requisitos y la naturaleza de la actualización. Puede utilizar otras API de DynamoDB, como `TransactWriteItems` para tamaños de lote de hasta 100. Cuando se trata de más elementos, puede utilizar servicios como AWS Glue, Amazon EMR o AWS Step Functions, o utilizar scripts y herramientas personalizados como el intérprete de comandos de DynamoDB para actualizaciones masivas.

**Topics**
+ [Actualización por lotes condicional](BestPractices_ConditionalBatchUpdate.md)
+ [Operaciones masivas eficientes](BestPractices_EfficientBulkOperations.md)

# Actualización por lotes condicional
<a name="BestPractices_ConditionalBatchUpdate"></a>

DynamoDB admite operaciones por lotes, como `BatchWriteItem`, que puede realizar hasta 25 solicitudes `PutItem` y `DeleteItem` en un solo lote. No obstante, `BatchWriteItem` no admite operaciones `UpdateItem` ni expresiones condicionales. Como solución alternativa, puede utilizar otras API de DynamoDB, como `TransactWriteItems` para tamaños de lote de hasta 100.

Cuando se trata de más elementos y es necesario cambiar una gran cantidad de datos, puede utilizar servicios como AWS Glue, Amazon EMR o AWS Step Functions, o utilizar scripts y herramientas personalizados como el intérprete de comandos de DynamoDB para realizar actualizaciones masivas de forma eficiente.

**Cuándo utilizar este patrón**
+ El intérprete de comandos de DynamoDB no es compatible con el caso de uso de producción.
+ `TransactWriteItems`: hasta 100 actualizaciones individuales con condiciones o sin ellas, ejecutándose como una agrupación ACID de todo o nada. Las llamadas a `TransactWriteItems` también pueden suministrarse con un `ClientRequestToken` si la aplicación requiere idempotencia, lo que significa que varias llamadas idénticas tienen el mismo efecto que una sola llamada. Esto garantiza que no ejecute la misma transacción varias veces y acabe con un estado de datos incorrecto.

  Desventaja: se consume rendimiento adicional. 2 WCU por 1 KB de escritura en lugar del estándar de 1 WGU por 1 KB de escritura.
+ PartiQL `BatchExecuteStatement`: hasta 25 actualizaciones con condiciones o sin ellas. `BatchExecuteStatement` siempre devuelve una respuesta correcta a la solicitud general y también devuelve una lista de respuestas de operaciones individuales que conserva el orden.

  Desventaja: para lotes más grandes, se requiere lógica adicional del cliente para distribuir las solicitudes en lotes de 25. Deben tenerse en cuenta las respuestas individuales a los errores para determinar la estrategia de reintentos.

## Ejemplos de código
<a name="bp-conditional-code-examples"></a>

En estos ejemplos de código se utiliza la biblioteca boto3, que es el AWS SDK para Python. En los ejemplos se supone que tiene boto3 instalado y configurado con las credenciales de AWS adecuadas.

Imagine una base de datos de inventario para un vendedor de electrodomésticos que tiene varios almacenes en diferentes ciudades europeas. Debido a que es el final del verano, el vendedor desea retirar los ventiladores de mesa a fin de dejar espacio para otras existencias. El vendedor quiere ofrecer un descuento en el precio de todos los ventiladores de mesa suministrados desde los almacenes de Italia, pero solo si tienen un stock de reserva de 20 ventiladores de mesa. La tabla de DynamoDB se llama **inventory**, tiene un esquema de clave de partición **sku** que es un identificador único para cada producto y una clave de clasificación **warehouse** que es un identificador de un almacén.

En el siguiente código Python se muestra cómo realizar esta actualización por lotes condicional a través de una llamada a la 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'])
```

La ejecución produce el siguiente resultado en los datos de muestra:

```
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'}}]
```

Como se trata de una operación delimitada para un sistema interno, no se han tenido en cuenta los requisitos de idempotencia. Es posible colocar barreras de protección adicionales, como que la actualización de precios solo se produzca si el precio es superior a 35 y menor de 40, para que las actualizaciones sean más sólidas.

Como alternativa, podemos realizar la misma operación de actualización por lotes mediante `TransactWriteItems` en caso de que haya requisitos de idempotencia y ACID más estrictos. No obstante, es importante recordar que, o bien se realizan todas las operaciones de la agrupación de transacciones, o bien falla todo la agrupación.

Supongamos un caso en el que hay una ola de calor en Italia y la demanda de ventiladores de mesa ha aumentado considerablemente. El proveedor quiere aumentar en 20 EUR el costo de los ventiladores de mesa que salen de cada almacén en Italia, pero el organismo regulador solo permite este aumento de costos si el costo actual es inferior a 70 EUR en todo su inventario. Es esencial que el precio se actualice en todo el inventario de una sola vez y solo una vez y únicamente si el costo es inferior a 70 EUR en cada uno de sus almacenes.

En el siguiente código Python se muestra cómo realizar esta actualización por lotes a través de una llamada a la 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'])
```

La ejecución produce el siguiente resultado en los datos de muestra:

```
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'}}]
```

Hay varios enfoques para realizar actualizaciones por lotes en DynamoDB. El enfoque adecuado depende de factores como los requisitos de ACID o idempotencia, el número de elementos que se van a actualizar y el conocimiento de las API.

# Operaciones masivas eficientes
<a name="BestPractices_EfficientBulkOperations"></a>

**Cuándo utilizar este patrón**

Estos patrones son útiles para realizar eficientemente actualizaciones masivas en elementos de DynamoDB.
+ El intérprete de comandos de DynamoDB no es compatible con el caso de uso de producción.
+ `TransactWriteItems`: hasta 100 actualizaciones individuales con condiciones o sin ellas, ejecutándose como una agrupación ACID de todo o nada 

  Desventaja: se consume rendimiento adicional, 2 WCU por 1 KB de escritura.
+ PartiQL `BatchExecuteStatement`: hasta 25 actualizaciones con condiciones o sin ellas

  Desventaja: se requiere lógica adicional para distribuir las solicitudes en lotes de 25.
+ AWS Step Functions: operaciones masivas con tasa limitada para desarrolladores familiarizados con AWS Lambda.

  Desventaja: el tiempo de ejecución es inversamente proporcional al límite de velocidad. Limitado por el tiempo de espera máximo de la función de Lambda. La funcionalidad implica que los cambios de datos que se producen entre la lectura y la escritura pueden sobrescribirse. Para obtener más información, consulte [Backfilling an Amazon DynamoDB Time to Live attribute using Amazon EMR: Part 2](https://aws.amazon.com/blogs/database/part-2-backfilling-an-amazon-dynamodb-time-to-live-attribute-using-amazon-emr/).
+ AWS Glue y Amazon EMR: operación masiva de tasa limitada con paralelismo administrado. Para aplicaciones o actualizaciones que no son urgentes, estas opciones pueden ejecutarse en segundo plano, lo que solo consume un pequeño porcentaje del rendimiento. Ambos servicios utilizan el conector emr-dynamodb para realizar operaciones de DynamoDB. Estos servicios realizan una lectura grande seguida de una escritura grande de elementos actualizados con una opción de límite de velocidad.

  Desventaja: el tiempo de ejecución es inversamente proporcional al límite de velocidad. La funcionalidad incluye que los cambios de datos que se producen entre la lectura y la escritura pueden sobrescribirse. No puede leer desde los índices secundarios globales (GSI). Consulte [Backfilling an Amazon DynamoDB Time to Live attribute using Amazon EMR: Part 2](https://aws.amazon.com/blogs/database/part-2-backfilling-an-amazon-dynamodb-time-to-live-attribute-using-amazon-emr/).
+ Interprete de comandos de DynamoDB: operaciones masivas con límite de velocidad mediante consultas similares a SQL. Puede leer los GSI para mejorar la eficiencia.

  Desventaja: el tiempo de ejecución es inversamente proporcional al límite de velocidad. Consulte [Rate limited bulk operations in DynamoDB Shell](https://aws.amazon.com/blogs/database/rate-limited-bulk-operations-in-dynamodb-shell/).

## Uso del patrón
<a name="BestPractices_EfficientBulkOperations_UsingThePattern"></a>

Las actualizaciones masivas pueden tener implicaciones significativas en los costos, sobre todo si utiliza el modo de rendimiento bajo demanda. Hay un equilibrio entre velocidad y costos si utiliza el modo de rendimiento aprovisionado. Configurar el parámetro de límite de velocidad de forma muy estricta puede llevar a un tiempo de procesamiento muy largo. Puede determinar aproximadamente la velocidad de la actualización mediante el tamaño medio del elemento y el límite de velocidad.

De forma alternativa, puede determinar la cantidad de rendimiento necesaria para el proceso basándose en la duración esperada del proceso de actualización y el tamaño medio de los elementos. Las referencias de blog compartidas con cada patrón proporcionan detalles sobre la estrategia, la implementación y las limitaciones de utilizar el patrón. Para obtener más información, consulte [Cost-effective bulk processing with Amazon DynamoDB](https://aws.amazon.com/blogs/database/cost-effective-bulk-processing-with-amazon-dynamodb/).

Existen múltiples enfoques para realizar actualizaciones masivas en una tabla de DynamoDB activa. El enfoque adecuado depende de factores como los requisitos de ACID o idempotencia, el número de elementos que se van a actualizar y el conocimiento de las API. Es importante considerar el equilibrio entre costo y tiempo. La mayoría de los enfoques mencionados anteriormente ofrecen la opción de limitar el rendimiento utilizado por el trabajo de actualización masiva.