DynamoDB TransactWriteItems in Node.js (AWS SDK v3)
Un TransactWriteItemsCommand riuscito non ti dice quasi nulla: nessun Item, nessun attributo, una risposta vuota. Tutto ciò che ti serve sta sull'eccezione, quindi in SDK v3 il blocco catch qui sotto è la vera superficie dell'API, e vale la pena sapere esattamente cosa ci finisce dentro. (Per capire se una transazione è la chiamata giusta, vedi transazioni DynamoDB.)
Codice
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;
}
}Spiegazione
TransactItems— un array ordinato di azioniPut,Update,DeleteeConditionCheck. L'ordine non è l'ordine di esecuzione (la transazione è atomica), ma è l'ordine in cui tornano i motivi di fallimento, che è l'unico motivo per cui importa. I limiti sono trattati più sotto.- Cosa lancia davvero v3. Le proprietà proprie dell'oggetto intercettato sono
$fault,$retryable,$metadata,name,CancellationReasons,messagee__type. Non c'è nessunerr.code;err.nameè la stringa su cui fare switch, eerr.$metadataportahttpStatusCode: 400piùattempts: 1, che è come capisci che l'SDK non ha riprovato la cancellazione di nascosto per te. CancellationReasonsè posizionale e sparso. Per la transazione qui sopra arriva come[{"Code":"ConditionalCheckFailed","Message":"The conditional request failed"},{"Code":"None"}]. La voceNonenon ha affatto una proprietàMessage, quindierr.CancellationReasons.map((r) => r.Message.trim())lancia dentro il tuo gestore di errori proprio sulle azioni riuscite.ReturnValuesOnConditionCheckFailure: 'ALL_OLD'aggiunge unItemal motivo di quell'azione, prima diCodeeMessage, in DynamoDB JSON grezzo. Gli attributi dell'Item che ha vinto tornano gratis; l'alternativa è unaGetItemdi follow-up dopo che hai già perso la corsa.- Il controllo su
err.nameha un buco, e vale la pena sapere quale. Punta due azioni sullo stesso Item e DynamoDB rispondeValidationExceptioncon il messaggioTransaction request cannot include multiple operations on one item, e nessunCancellationReasons, perché non è stato tentato nulla. Il ramoelse { throw err }qui sopra lo rilancia. È il comportamento corretto, non un bug, ma significa che gli errori strutturali non arrivano mai al tuo logging delle cancellazioni. - v3 invia già un
ClientRequestToken, anche quando lo ometti. Catturando il body serializzato si vede un UUID nuovo sul filo, e due chiamatesend()dello stesso oggetto comando sono partite con due token diversi. Quindi il token protegge una chiamata in volo, non il tuo loop di retry: intercetti, rimandi, e hai un token nuovo e nessuna idempotenza. Forniscine uno tuo se un retry può attraversare il confine di un processo. Riusalo con un parametro cambiato e ottieniIdempotentParameterMismatchinvece di una doppia applicazione silenziosa. - Solo un altro codice ha bisogno di un percorso dedicato.
TransactionConflictsignifica che una transazione concorrente teneva uno dei tuoi Item, quindi un retry con backoff è la risposta giusta là dove perConditionalCheckFailednon lo è mai. Il resto è decodificato nella pagina TransactionCanceledException. - Costo — ogni Item in una transazione viene scritto due volte sotto il cofano (prepare, poi commit), quindi metti in conto circa 2× la capacità di scrittura di una scrittura semplice. Una scrittura condizionale su un singolo Item ti dà l'atomicità su un Item alla metà di quel costo.
Quale limite colpisci per primo
Il tetto di 100 azioni e quello di 4 MB sono indipendenti, e quello sui byte è quello che sorprende: cento incrementi di contatore non sono nulla, mentre una dozzina di Item grassi possono esaurire l'aggregato da soli. Misura un Item rappresentativo con il calcolatore della dimensione degli Item DynamoDB prima di decidere quante azioni raggruppare. Per leggere gli Item che un'azione toccherà mentre stai ancora scrivendo la condizione, scarica DynoTable.
Esempi correlati
- DynamoDB TransactWriteItems in Python — la stessa transazione con boto3.
- DynamoDB TransactWriteItems con la AWS CLI — la stessa transazione dalla shell.
- Scrittura condizionale DynamoDB in Node.js — atomicità su un singolo Item senza il costo 2×.
- Transazioni DynamoDB — isolamento, idempotenza e quando le transazioni valgono la pena.
- DynamoDB TransactionCanceledException — ogni codice di motivo di cancellazione, decodificato.
- "Too many actions in a TransactWriteItems call" — i limiti di 100 azioni e 4 MB per transazione.
- "Transaction request cannot include multiple operations on one item" — un'azione per Item, per transazione.
Riferimenti
- 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
Ultima verifica 2026-07-28 rispetto alla documentazione ufficiale AWS collegata sopra.