DynamoDB TransactWriteItems en Node.js (AWS SDK v3)
Un TransactWriteItemsCommand con éxito no te dice casi nada: sin Items, sin atributos, una respuesta vacía. Todo lo que necesitas está en la excepción, así que en el SDK v3 el bloque catch de abajo es la superficie real de la API, y vale la pena saber exactamente qué aterriza en él. (Para saber cuándo una transacción es la llamada correcta, mira transacciones en 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;
}
}Explicación
TransactItems— un array ordenado de accionesPut,Update,DeleteyConditionCheck. El orden no es el orden de ejecución (la transacción es atómica), pero sí es el orden en que vuelven los motivos del fallo, que es la única razón para que te importe. Los límites se cubren más abajo.- Qué lanza realmente v3. Las propiedades propias del objeto capturado son
$fault,$retryable,$metadata,name,CancellationReasons,messagey__type. No hayerr.code;err.namees la cadena sobre la que ramificar, yerr.$metadatallevahttpStatusCode: 400másattempts: 1, que es como puedes saber que el SDK no reintentó la cancelación por su cuenta. CancellationReasonses posicional y disperso. Para la transacción de arriba llega como[{"Code":"ConditionalCheckFailed","Message":"The conditional request failed"},{"Code":"None"}]. La entradaNoneno tiene ninguna propiedadMessage, así queerr.CancellationReasons.map((r) => r.Message.trim())lanza un error dentro de tu manejador de errores precisamente por las acciones que sí funcionaron.ReturnValuesOnConditionCheckFailure: 'ALL_OLD'añade unItemal motivo de esa acción, por delante deCodeyMessage, en DynamoDB JSON en crudo. Los atributos del Item que hizo fallar la condición vuelven gratis; la alternativa es unGetItemposterior cuando ya has perdido la carrera.- La comprobación de
err.nametiene un agujero, y vale la pena saber cuál. Apunta dos acciones al mismo Item y DynamoDB respondeValidationExceptioncon el mensajeTransaction request cannot include multiple operations on one item, y sin ningúnCancellationReasons, porque no se intentó nada. La ramaelse { throw err }de arriba lo relanza. Ese es el comportamiento correcto, no un bug, pero significa que los errores estructurales nunca llegan a tu registro de cancelaciones. - v3 ya envía un
ClientRequestToken, incluso cuando lo omites. Capturar el cuerpo serializado muestra un UUID nuevo en el cable, y dos llamadas asend()del mismo objeto de comando salieron con dos tokens distintos. Así que el token protege una llamada en vuelo, no tu propio bucle de reintento: capturas, reenvías, y tienes un token nuevo y ninguna idempotencia. Aporta el tuyo si un reintento puede cruzar la frontera de un proceso. Reutilízalo con cualquier parámetro cambiado y obtienesIdempotentParameterMismatchen vez de una doble aplicación silenciosa. - Solo otro código necesita su propio camino.
TransactionConflictsignifica que una transacción concurrente retenía uno de tus Items, así que un reintento con backoff es la respuesta correcta ahí donde nunca lo es paraConditionalCheckFailed. El resto están descifrados en la página de TransactionCanceledException. - Coste — cada Item de una transacción se escribe dos veces por debajo (preparar y luego confirmar), así que presupuesta más o menos 2× la capacidad de escritura de una escritura normal. Una escritura condicional de un solo Item te da atomicidad sobre ese Item por la mitad.
Con qué límite chocas primero
El tope de 100 acciones y el de 4 MB son independientes, y el de bytes es el que sorprende: cien incrementos de contador no son nada, mientras que una docena de Items gordos pueden agotar el agregado ellos solos. Mide un Item representativo con la calculadora de tamaño de Item de DynamoDB antes de decidir cuántas acciones agrupar. Para leer los Items que una acción va a tocar mientras aún estás escribiendo la condición, descarga DynoTable.
Ejemplos relacionados
- DynamoDB TransactWriteItems en Python — la misma transacción con boto3.
- DynamoDB TransactWriteItems con la AWS CLI — la misma transacción desde la shell.
- Escritura condicional en DynamoDB en Node.js — atomicidad de un solo Item sin el coste 2×.
- Transacciones en DynamoDB — aislamiento, idempotencia y cuándo compensan las transacciones.
- DynamoDB TransactionCanceledException — todos los códigos de motivo de cancelación, descifrados.
- "Too many actions in a TransactWriteItems call" — los límites de 100 acciones y 4 MB por transacción.
- "Transaction request cannot include multiple operations on one item" — una acción por Item y por transacción.
Referencias
- 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 por última vez el 2026-07-28 contra la documentación oficial de AWS enlazada arriba.