

# Práticas recomendadas para usar operações de dados em massa no DynamoDB
<a name="BestPractices_BulkDataOperations"></a>

O DynamoDB é compatível com operações em lote, como `BatchWriteItem`, que permitem realizar até 25 solicitações `PutItem` e `DeleteItem` em conjunto. No entanto, `BatchWriteItem` não permite operações `UpdateItem`. Quando se trata de atualizações em massa, a diferença está nos requisitos e na natureza da atualização. É possível usar outras APIs do DynamoDB, como `TransactWriteItems`, para lotes de até 100. Quando mais itens estão envolvidos, você pode usar serviços como o AWS Glue, o Amazon EMR, o AWS Step Functions ou usar scripts e ferramentas personalizados, como o DynamoDB-shell, para atualizações em massa.

**Topics**
+ [Atualização condicional em lote](BestPractices_ConditionalBatchUpdate.md)
+ [Operações em massa eficientes](BestPractices_EfficientBulkOperations.md)

# Atualização condicional em lote
<a name="BestPractices_ConditionalBatchUpdate"></a>

O DynamoDB é compatível com operações em lote, como `BatchWriteItem`, que permitem realizar até 25 solicitações `PutItem` e `DeleteItem` em um único lote. No entanto, `BatchWriteItem` não permite operações `UpdateItem` nem expressões condicionais. Como solução alternativa, você pode usar outras APIs do DynamoDB, como `TransactWriteItems`, para lotes de até 100.

Quando mais itens estão envolvidos e uma grande parte dos dados precisa ser alterada, é possível usar serviços como o AWS Glue, o Amazon EMR, o AWS Step Functions ou usar scripts e ferramentas personalizados, como o DynamoDB-shell, para atualizações em massa eficientes.

**Quando usar esse padrão**
+ O DynamoDB-shell não é compatível para caso de uso de produção.
+ `TransactWriteItems`: até 100 atualizações individuais com ou sem condições, executadas como um pacote ACID de “tudo ou nada”. As chamadas de `TransactWriteItems` também podem ser fornecidas com um `ClientRequestToken` se a aplicação exigir idempotência, o que significa que várias chamadas idênticas têm o mesmo efeito de uma única chamada. Isso garante que você não execute a mesma transação várias vezes e acabe com um estado incorreto dos dados.

  Desvantagem: throughput adicional é consumido. 2 WCUs por gravação de 1 KB em vez do padrão de 1 WCU por gravação de 1 KB.
+ PartiQL `BatchExecuteStatement`: até 25 atualizações com ou sem condições. `BatchExecuteStatement` sempre retorna uma resposta bem-sucedida à solicitação geral e também retorna uma lista de respostas individuais da operação que preserva a ordem.

  Desvantagem: para lotes maiores, é necessária uma lógica adicional do lado do cliente para distribuir solicitações em lotes de 25. As respostas de erro individuais precisam ser consideradas para determinar a estratégia de nova tentativa.

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

Esses exemplos de código usam a biblioteca boto3, que é o AWS SDK para Python. Os exemplos pressupõem que você tenha o boto3 instalado e configurado com as credenciais da AWS apropriadas.

Suponha um banco de dados de inventário para um fornecedor de eletrodomésticos que tenha vários armazéns em cidades europeias. Como é final do verão, o fornecedor gostaria de liquidar os ventiladores de mesa para abrir espaço para outros estoques. O fornecedor deseja oferecer um desconto no preço de todos os ventiladores de mesa fornecidos pelos armazéns na Itália, mas somente se eles tiverem um estoque de reserva de 20 ventiladores de mesa. A tabela do DynamoDB é chamada **inventário**, ela tem um esquema de chaves com chave de partição **sku**, que é um identificador exclusivo para cada produto, e uma chave de classificação **armazém**, que é um identificador de um armazém.

O código Python a seguir demonstra como realizar essa atualização condicional em lote usando a chamada de 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'])
```

A execução produz a saída abaixo nos dados de amostra:

```
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 essa é uma operação limitada para um sistema interno, os requisitos de idempotência não foram considerados. É possível colocar barreiras de proteção adicionais, como permitir a atualização de preços somente se o preço for maior que 35 e menor que 40, para tornar as atualizações mais robustas.

Como alternativa, podemos realizar a mesma operação de atualização em lote usando `TransactWriteItems` no caso de requisitos mais rígidos de idempotência e ACID. No entanto, é importante lembrar que ou todas as operações no pacote de transações são concluídas ou o pacote inteiro falha.

Vamos supor um caso em que haja uma onda de calor na Itália e a demanda por ventiladores de mesa tenha aumentado significativamente. O fornecedor deseja aumentar o custo do ventilador de mesa que sai de cada armazém na Itália em 20 euros, mas o órgão regulador só permite esse aumento de custo se o custo atual for inferior a 70 euros em todo o estoque. É essencial que o preço seja atualizado em todo o estoque de uma só vez e somente uma vez, e apenas se o custo for inferior a 70 euros em cada um dos armazéns.

O código Python a seguir demonstra como realizar essa atualização em lote usando a chamada de 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'])
```

A execução produz a saída abaixo nos dados de amostra:

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

Existem várias abordagens para realizar atualizações em lote no DynamoDB. A abordagem adequada depende de fatores como requisitos de ACID e/ou idempotência, número de itens a serem atualizados e familiaridade com as APIs.

# Operações em massa eficientes
<a name="BestPractices_EfficientBulkOperations"></a>

**Quando usar esse padrão**

Esses padrões são úteis para realizar com eficiência atualizações em massa nos itens do DynamoDB.
+ O DynamoDB-shell não é compatível para caso de uso de produção.
+ `TransactWriteItems`: até 100 atualizações individuais com ou sem condições, executadas como um pacote ACID de “tudo ou nada” 

  Desvantagem: é consumido um throughput adicional, 2 WCUs por gravação de 1 KB.
+ PartiQL `BatchExecuteStatement`: até 25 atualizações com ou sem condições

  Desvantagem: é necessária lógica adicional para distribuir solicitações em lotes de 25.
+ AWS Step Functions: operações em massa com taxa limitada para desenvolvedores familiarizados com o AWS Lambda.

  Desvantagem: o tempo de execução é inversamente proporcional ao limite de taxa. Limitado pelo tempo limite máximo da função do Lambda. A funcionalidade implica que as alterações de dados que ocorrem entre a leitura e a gravação podem ser substituídas. Consulte mais informações em [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 e Amazon EMR: operação em massa com taxa limitada e paralelismo gerenciado. Para aplicações ou atualizações que não são sensíveis ao tempo, essas opções podem ser executadas em segundo plano, consumindo apenas uma pequena porcentagem do throughput. Ambos os serviços usam o emr-dynamodb-connector para realizar operações do DynamoDB. Esses serviços realizam uma grande leitura seguida por uma grande gravação de itens atualizados com a opção de limitar a taxa.

  Desvantagem: o tempo de execução é inversamente proporcional ao limite de taxa. A funcionalidade inclui que as alterações de dados que ocorrem entre a leitura e a gravação podem ser substituídas. Não é possível ler de índices secundários globais (GSIs). 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/).
+ DynamoDB Shell: operações em massa com taxa limitada usando consultas semelhantes a SQL. É possível ler de GSIs para ter melhor eficiência.

  Desvantagem: o tempo de execução é inversamente proporcional ao limite de taxa. Consulte [Rate limited bulk operations in DynamoDB Shell](https://aws.amazon.com/blogs/database/rate-limited-bulk-operations-in-dynamodb-shell/).

## Uso do padrão
<a name="BestPractices_EfficientBulkOperations_UsingThePattern"></a>

Atualizações em massa podem ter implicações significativas de custo, especialmente se você usar o modo de throughput sob demanda. Haverá uma compensação entre velocidade e custo se você usar o modo de throughput provisionado. Definir o parâmetro de limite de taxa de forma muito restrita pode levar a um tempo de processamento muito longo. É possível determinar aproximadamente a velocidade da atualização usando o tamanho médio do item e o limite de taxa.

Como alternativa, é possível determinar a quantidade de throughput necessária para o processo com base na duração esperada do processo de atualização e no tamanho médio do item. As referências do blog compartilhadas com cada padrão fornecem detalhes sobre a estratégia, a implementação e as limitações do uso do padrão. Consulte mais informações em [Cost-effective bulk processing with Amazon DynamoDB](https://aws.amazon.com/blogs/database/cost-effective-bulk-processing-with-amazon-dynamodb/).

Existem várias abordagens para realizar atualizações em massa em uma tabela ativa do DynamoDB. A abordagem adequada depende de fatores como requisitos de ACID e/ou idempotência, número de itens a serem atualizados e familiaridade com as APIs. É importante considerar a relação entre custo e tempo. A maioria das abordagens discutidas acima oferece uma opção para limitar o throughput usado pelo trabalho de atualização em massa.