DynamoDB TransactWriteItems in Node.js (AWS SDK v3)

Ein erfolgreiches TransactWriteItemsCommand sagt dir fast nichts: keine Items, keine Attribute, eine leere Antwort. Alles, was du brauchst, hängt am Fehler — im SDK v3 ist der catch-Block unten also die eigentliche API-Oberfläche, und es lohnt sich, genau zu wissen, was darin landet. (Wann eine Transaktion überhaupt der richtige Aufruf ist, steht in DynamoDB-Transaktionen.)

Code

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;
  }
}

Erklärung

  • TransactItems — ein geordnetes Array aus Put-, Update-, Delete- und ConditionCheck-Aktionen. Die Reihenfolge ist keine Ausführungsreihenfolge (die Transaktion ist atomar), aber sie ist die Reihenfolge, in der die Fehlergründe zurückkommen — und das ist der einzige Grund, sich dafür zu interessieren. Die Obergrenzen stehen weiter unten.
  • Was v3 tatsächlich wirft. Die eigenen Eigenschaften des gefangenen Objekts sind $fault, $retryable, $metadata, name, CancellationReasons, message und __type. Es gibt kein err.code; err.name ist der String, über den du verzweigst, und err.$metadata trägt httpStatusCode: 400 plus attempts: 1 — daran erkennst du, dass das SDK die Stornierung nicht stillschweigend für dich wiederholt hat.
  • CancellationReasons ist positionsgebunden und lückenhaft. Für die Transaktion oben kommt es als [{"Code":"ConditionalCheckFailed","Message":"The conditional request failed"},{"Code":"None"}] an. Der None-Eintrag hat überhaupt keine Message-Eigenschaft — err.CancellationReasons.map((r) => r.Message.trim()) wirft also in deinem Error-Handler, und zwar genau bei den Aktionen, die erfolgreich waren.
  • ReturnValuesOnConditionCheckFailure: 'ALL_OLD' ergänzt den Grund dieser Aktion um ein Item, vor Code und Message, in rohem DynamoDB-JSON. Die Attribute des unterlegenen Items kommen gratis zurück; die Alternative ist ein nachgelagertes GetItem, nachdem du das Rennen bereits verloren hast.
  • Die err.name-Prüfung hat eine Lücke, und es lohnt sich zu wissen, welche. Richte zwei Aktionen auf dasselbe Item, und DynamoDB antwortet mit ValidationException und der Meldung Transaction request cannot include multiple operations on one item — und ohne jede CancellationReasons, weil nichts versucht wurde. Der else { throw err }-Zweig oben wirft ihn weiter. Das ist korrektes Verhalten, kein Bug, aber es bedeutet, dass die strukturellen Fehler nie in deinem Stornierungs-Logging ankommen.
  • v3 schickt bereits ein ClientRequestToken, auch wenn du es weglässt. Fängt man den serialisierten Body ab, sieht man auf der Leitung eine frische UUID, und zwei send()-Aufrufe desselben Command-Objekts gingen mit zwei verschiedenen Tokens raus. Das Token schützt also einen Aufruf in flight, nicht deine eigene Retry-Schleife: fangen, erneut senden — und du hast ein neues Token und keine Idempotenz. Liefere dein eigenes, wenn ein Retry eine Prozessgrenze überschreiten kann. Verwendest du es mit einem geänderten Parameter wieder, bekommst du IdempotentParameterMismatch statt einer stillen doppelten Anwendung.
  • Nur ein weiterer Code braucht einen eigenen Codepfad. TransactionConflict heißt, dass eine nebenläufige Transaktion eines deiner Items hielt — ein Retry mit Backoff ist hier die richtige Antwort, während er es bei ConditionalCheckFailed nie ist. Der Rest wird auf der Seite zur TransactionCanceledException entschlüsselt.
  • Kosten — jedes Item in einer Transaktion wird darunter zweimal geschrieben (Prepare, dann Commit), kalkuliere also grob die 2-fache Schreibkapazität eines einfachen Writes. Ein bedingter Write auf einem einzelnen Item gibt dir Atomarität für ein Item zum halben Preis.

Welches Limit du zuerst erreichst

Die Obergrenze von 100 Aktionen und die von 4 MB sind unabhängig voneinander, und die Byte-Grenze ist die, die Leute überrascht: Hundert Zähler-Inkremente sind nichts, während ein Dutzend fetter Items die Gesamtgrenze allein ausschöpfen kann. Miss ein repräsentatives Item mit dem DynamoDB-Item-Size-Rechner, bevor du entscheidest, wie viele Aktionen du bündelst. Um die Items zu lesen, die eine Aktion anfassen wird, während du noch die Bedingung schreibst, lade DynoTable herunter.

Verwandte Beispiele

Referenzen

Zuletzt verifiziert am 2026-07-28 gegen die oben verlinkte offizielle AWS-Dokumentation.

Mit DynamoDB ohne die Console arbeiten

Ein schneller DynamoDB-Desktop-Client, der das echte SQL ausführt, das DynamoDB nicht kann — JOINs, GROUP BY, Aggregationen — mit visueller Bearbeitung und einem KI-Agenten auf deinen eigenen Bedrock-Schlüsseln.

30 Tage kostenlos testen, keine Kreditkarte — danach der Kostenlos-Tarif ohne Zeitlimit.