TransactWriteItems do DynamoDB em Node.js (AWS SDK v3)
Um TransactWriteItemsCommand bem-sucedido não te diz quase nada: sem itens, sem atributos, uma resposta vazia. Tudo o que você precisa está na exceção, então no SDK v3 o bloco catch abaixo é a superfície de API de verdade, e vale saber exatamente o que cai nele. (Para saber quando uma transação é a chamada certa, veja transações no DynamoDB.)
Código
import {DynamoDBClient, TransactWriteItemsCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
// Move one award between two songs — atomically. If the first song has no
// award to give, NEITHER update happens.
const command = new TransactWriteItemsCommand({
TransactItems: [
{
Update: {
TableName: 'Music',
Key: {Artist: {S: 'Arturo Sandoval'}, SongTitle: {S: 'Cubano Chant'}},
UpdateExpression: 'SET #upd0 = #upd0 - :one',
ConditionExpression: '#upd0 >= :one',
ExpressionAttributeNames: {'#upd0': 'Awards'},
ExpressionAttributeValues: {':one': {N: '1'}}
}
},
{
Update: {
TableName: 'Music',
Key: {Artist: {S: 'Arturo Sandoval'}, SongTitle: {S: 'A Mis Abuelos'}},
UpdateExpression: 'SET #upd0 = if_not_exists(#upd0, :zero) + :one',
ExpressionAttributeNames: {'#upd0': 'Awards'},
ExpressionAttributeValues: {':one': {N: '1'}, ':zero': {N: '0'}}
}
}
]
});
try {
await client.send(command);
console.log('Transaction committed');
} catch (err) {
if (err.name === 'TransactionCanceledException') {
// One reason per action, in TransactItems order. 'None' means that action
// was fine — some OTHER action sank the transaction.
const codes = (err.CancellationReasons ?? []).map((r) => r.Code);
console.log('Transaction canceled:', codes); // e.g. ['ConditionalCheckFailed', 'None']
} else {
throw err;
}
}Explicação
TransactItems— um array ordenado de açõesPut,Update,DeleteeConditionCheck. A ordem não é a ordem de execução (a transação é atômica), mas é a ordem em que as razões de falha voltam, que é o único motivo para se importar com ela. Os tetos estão cobertos abaixo.- O que a v3 de fato lança. As propriedades próprias do objeto capturado são
$fault,$retryable,$metadata,name,CancellationReasons,messagee__type. Não existeerr.code;err.nameé a string na qual fazer o switch, eerr.$metadatacarregahttpStatusCode: 400maisattempts: 1, que é como você percebe que o SDK não tentou de novo silenciosamente o cancelamento por você. CancellationReasonsé posicional e esparso. Para a transação acima, ele chega como[{"Code":"ConditionalCheckFailed","Message":"The conditional request failed"},{"Code":"None"}]. A entradaNonenão tem propriedadeMessagealguma, entãoerr.CancellationReasons.map((r) => r.Message.trim())lança um erro dentro do seu tratador de erros justamente nas ações que deram certo.ReturnValuesOnConditionCheckFailure: 'ALL_OLD'adiciona umItemà razão daquela ação, antes deCodeeMessage, em DynamoDB JSON cru. Os atributos do item perdedor voltam de graça; a alternativa é umGetItemde acompanhamento depois de você já ter perdido a corrida.- A checagem de
err.nametem um buraco, e vale saber qual. Aponte duas ações para o mesmo item e o DynamoDB respondeValidationExceptioncom a mensagemTransaction request cannot include multiple operations on one item, e nenhumCancellationReasons, porque nada foi tentado. O ramoelse { throw err }acima o relança. Esse é o comportamento correto, não um bug, mas significa que os erros estruturais nunca chegam ao seu log de cancelamentos. - A v3 já envia um
ClientRequestToken, mesmo quando você o omite. Capturar o corpo serializado mostra um UUID novo na rede, e duas chamadas desend()do mesmo objeto de comando saíram com dois tokens diferentes. Ou seja, o token protege uma chamada em voo, não o seu próprio laço de retry: capture, reenvie, e você tem um token novo e nenhuma idempotência. Forneça o seu próprio se um retry puder cruzar a fronteira de um processo. Reutilize-o com qualquer parâmetro alterado e você recebeIdempotentParameterMismatchem vez de uma dupla aplicação silenciosa. - Só um outro código precisa de caminho próprio.
TransactionConflictsignifica que uma transação concorrente segurava um dos seus itens, então um retry com backoff é a resposta certa ali, onde paraConditionalCheckFailednunca é. O resto está decodificado na página do TransactionCanceledException. - Custo — cada item de uma transação é gravado duas vezes por baixo (preparar e depois confirmar), então orce cerca de 2× a capacidade de escrita de uma escrita comum. Uma escrita condicional de item único te dá atomicidade em um item pela metade disso.
Qual limite você atinge primeiro
O teto de 100 ações e o teto de 4 MB são independentes, e o de bytes é o que surpreende as pessoas: cem incrementos de contador não são nada, enquanto uma dúzia de itens gordos consegue esgotar o agregado sozinha. Meça um item representativo com a calculadora de tamanho de item do DynamoDB antes de decidir quantas ações agrupar. Para ler os itens que uma ação vai tocar enquanto você ainda está escrevendo a condição, baixe o DynoTable.
Exemplos relacionados
- TransactWriteItems do DynamoDB em Python — a mesma transação com boto3.
- TransactWriteItems do DynamoDB com a AWS CLI — a mesma transação a partir do shell.
- Escrita condicional no DynamoDB em Node.js — atomicidade de item único sem o custo de 2×.
- Transações no DynamoDB — isolamento, idempotência e quando as transações valem a pena.
- DynamoDB TransactionCanceledException — cada código de razão de cancelamento, decodificado.
- "Too many actions in a TransactWriteItems call" — os limites de 100 ações e 4 MB da transação.
- "Transaction request cannot include multiple operations on one item" — uma ação por item, por transação.
Referências
- TransactWriteItems — Amazon DynamoDB API Reference
- Amazon DynamoDB transactions: how it works — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
Verificado pela última vez em 2026-07-28 contra a documentação oficial da AWS vinculada acima.