Use Cases (Aktualisiert: 3.6.2026)

Claude Code und AWS DynamoDB: Tabellendesign, sichere Writes und Kosten

Praxisleitfaden für Claude Code mit DynamoDB: Keys, lokale Tests, TTL, IAM, Kosten und Hot Partitions.

Claude Code und AWS DynamoDB: Tabellendesign, sichere Writes und Kosten

Claude Code einfach nur zu bitten, “DynamoDB einzubauen”, ist für produktive Software zu ungenau. DynamoDB wirkt schemalos, aber das eigentliche Schema steckt in den Zugriffsmustern: welche Ansicht welche Daten liest, mit welchem Key abgefragt wird, welche Writes nicht überschreiben dürfen und ob sich Traffic gleichmäßig über Partition Keys verteilt.

Dieser Leitfaden nutzt Claude Code als Design-Reviewer für DynamoDB. Wir behandeln Tabellendesign, Partition Keys, einfaches Design gegenüber Single-Table Design, Conditional Writes, TTL, lokale Tests, IAM, Kosten und typische Hot-Partition-Fehler. Ergänzend passen der AWS Lambda Guide, der AWS IAM Guide und der CloudWatch Guide.

Prüfe Details immer in den offiziellen AWS-Dokumenten: data modeling foundations, partition key best practices, Query key condition expressions, condition expressions, TTL, DynamoDB local, throughput capacity und IAM fine-grained access control.

Zugriffsmuster zuerst

Ein Zugriffsmuster ist eine konkrete Operation: Aufgaben eines Projekts listen, eine Aufgabe abschließen, eine Session nach sieben Tagen auslaufen lassen oder ein Webhook-Event nur einmal verarbeiten. Ohne diese Liste erzeugt Claude Code oft generisches CRUD und repariert später mit Scan oder zusätzlichen Indizes.

Nutze vor der Implementierung diesen Prompt:

Bitte prüfe dieses DynamoDB-Design, bevor du Code schreibst.
Anforderungen:
- Aufgaben nach Projekt listen
- Eine Aufgabe per taskId aktualisieren
- Benutzer-Sessions nach 7 Tagen ablaufen lassen
- Jedes webhook eventId nur einmal verarbeiten

Ausgabe:
1. Tabelle der Zugriffsmuster
2. Vorschlag für PK/SK
3. Operationen, die Query nutzen können, und solche, die es nicht können
4. Notwendige Conditional Writes
5. Risiken für Hot Partitions und Kosten

Query in DynamoDB braucht eine Gleichheitsbedingung auf dem Partition Key. Der Sort Key kann danach weiter einschränken, etwa mit begins_with. Eine FilterExpression ersetzt kein SQL-WHERE, das Read-Kosten spart; sie filtert nach dem Lesen. Wenn Claude Code für eine Listenansicht Scan vorschlägt, ist das zunächst ein Designproblem.

Einfach oder Single Table

Single-Table Design speichert mehrere Entitätstypen in einer Tabelle, meist mit generischen Keys wie PK und SK. Das ist stark, wenn eine Oberfläche zusammenhängende Daten in einem Query braucht. Es erhöht aber auch die Anforderungen an Key-Namen, IAM, Streams und Backups.

Ein einfaches Design trennt Entitäten stärker oder hält eine Tabelle auf wenige verwandte Muster begrenzt. Für MVPs, interne Tools und Teams am Anfang mit Claude Code ist das oft besser prüfbar.

KriteriumEinfaches DesignSingle-Table Design
StartLeicht erklärbarStrengere Review nötig
Mehrere EntitätenMehrere Reads möglichOft ein Query
IAMTrennung nach TabelleLeadingKeys sind zentral
ÄnderungenNeue Tabelle ist einfachKey-Namen müssen stabil bleiben
Gut fürMVP, Lernen, kleine ToolsStabile Fachdomäne

Das Beispiel nutzt eine Tabelle, aber nur für Projekte, Aufgaben, Sessions und Webhook-Deduplizierung.

ClaudeCodeLabDemo

PK                 SK                   entityType
PROJECT#alpha      META                 Project
PROJECT#alpha      TASK#task-001        Task
USER#u-001         SESSION#s-001        Session
WEBHOOK#stripe     EVENT#evt_001        WebhookEvent

Queries:
- Projektaufgaben: PK = PROJECT#alpha AND begins_with(SK, TASK#)
- Benutzer-Sessions: PK = USER#u-001 AND begins_with(SK, SESSION#)
- Webhook-Dedupe: Conditional PutItem auf dieselbe PK/SK

Konkrete Use Cases

Erstens: ein Aufgabenboard pro Projekt. Mit PK = PROJECT#projectId und SK = TASK#taskId wird die Liste ein Query. Falls Statusfilter nötig sind, sollte Claude Code begründen, ob ein GSI wirklich gebraucht wird oder ob eine Projektsicht mit Sortierung in der Anwendung reicht.

Zweitens: Sessions, Einladungen und temporäre Tokens. TTL nutzt eine Unix-Epoch-Zeit in Sekunden als Number. Das hilft beim Aufräumen temporärer Daten, ist aber kein exakter Scheduler. Abgelaufene Items können bis zur Hintergrundlöschung noch lesbar sein. Prüfe expiresAt daher auch in der Anwendung.

Drittens: Idempotenz für Webhooks. Provider senden Events erneut. Mit WEBHOOK#provider, EVENT#eventId und attribute_not_exists(PK) AND attribute_not_exists(SK) gewinnt nur die erste Verarbeitung.

Viertens: einfaches Rate Limiting. PK = RATE#userId und SK = WINDOW#2026-06-03T10:00 funktioniert für kleinere interne APIs. Bei hoher Last kann ein einzelner Nutzer oder Tenant aber eine Hot Partition erzeugen.

Lokales Setup

Starte DynamoDB Local:

services:
  dynamodb-local:
    image: "amazon/dynamodb-local:latest"
    command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
    ports:
      - "8000:8000"
    volumes:
      - "./docker/dynamodb:/home/dynamodblocal/data"
    working_dir: /home/dynamodblocal
docker compose up -d
export AWS_ACCESS_KEY_ID=fakeMyKeyId
export AWS_SECRET_ACCESS_KEY=fakeSecretAccessKey
export AWS_REGION=us-west-2

Lege die lokale Tabelle an:

aws dynamodb create-table \
  --table-name ClaudeCodeLabDemo \
  --attribute-definitions AttributeName=PK,AttributeType=S AttributeName=SK,AttributeType=S \
  --key-schema AttributeName=PK,KeyType=HASH AttributeName=SK,KeyType=RANGE \
  --billing-mode PAY_PER_REQUEST \
  --endpoint-url http://localhost:8000 \
  --region us-west-2

Aktiviere das TTL-Attribut:

aws dynamodb update-time-to-live \
  --table-name ClaudeCodeLabDemo \
  --time-to-live-specification "Enabled=true,AttributeName=expiresAt" \
  --endpoint-url http://localhost:8000 \
  --region us-west-2

Installiere die SDK-Pakete:

npm init -y
npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

Kopierbare Implementierung

Speichere den folgenden Code als app.mjs. Er erstellt eine Aufgabe, listet Projektaufgaben, schließt eine Aufgabe per Conditional Update ab und erstellt eine Session mit TTL.

import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
  DynamoDBDocumentClient,
  PutCommand,
  QueryCommand,
  UpdateCommand,
} from "@aws-sdk/lib-dynamodb";

const TABLE_NAME = process.env.TABLE_NAME ?? "ClaudeCodeLabDemo";
const isLocal = process.env.DDB_LOCAL !== "0";

const client = new DynamoDBClient({
  region: process.env.AWS_REGION ?? "us-west-2",
  ...(isLocal
    ? {
        endpoint: "http://localhost:8000",
        credentials: {
          accessKeyId: "fakeMyKeyId",
          secretAccessKey: "fakeSecretAccessKey",
        },
      }
    : {}),
});

const ddb = DynamoDBDocumentClient.from(client, {
  marshallOptions: { removeUndefinedValues: true },
});

const nowIso = () => new Date().toISOString();
const ttlAfterDays = (days) => Math.floor(Date.now() / 1000) + days * 86400;
const taskKey = (projectId, taskId) => ({
  PK: `PROJECT#${projectId}`,
  SK: `TASK#${taskId}`,
});

async function createTask({ projectId, taskId, title, ownerId }) {
  const item = {
    ...taskKey(projectId, taskId),
    entityType: "Task",
    title,
    ownerId,
    status: "OPEN",
    createdAt: nowIso(),
    updatedAt: nowIso(),
  };

  await ddb.send(
    new PutCommand({
      TableName: TABLE_NAME,
      Item: item,
      ConditionExpression: "attribute_not_exists(PK) AND attribute_not_exists(SK)",
    }),
  );

  return item;
}

async function listProjectTasks(projectId) {
  const result = await ddb.send(
    new QueryCommand({
      TableName: TABLE_NAME,
      KeyConditionExpression: "PK = :pk AND begins_with(SK, :taskPrefix)",
      ExpressionAttributeValues: {
        ":pk": `PROJECT#${projectId}`,
        ":taskPrefix": "TASK#",
      },
      ReturnConsumedCapacity: "TOTAL",
    }),
  );

  console.log("consumed capacity:", result.ConsumedCapacity);
  return result.Items ?? [];
}

async function completeTask({ projectId, taskId, expectedOwnerId }) {
  const result = await ddb.send(
    new UpdateCommand({
      TableName: TABLE_NAME,
      Key: taskKey(projectId, taskId),
      UpdateExpression: "SET #status = :done, updatedAt = :now",
      ConditionExpression: "ownerId = :ownerId AND #status <> :done",
      ExpressionAttributeNames: {
        "#status": "status",
      },
      ExpressionAttributeValues: {
        ":done": "DONE",
        ":ownerId": expectedOwnerId,
        ":now": nowIso(),
      },
      ReturnValues: "ALL_NEW",
    }),
  );

  return result.Attributes;
}

async function createSession({ userId, sessionId }) {
  await ddb.send(
    new PutCommand({
      TableName: TABLE_NAME,
      Item: {
        PK: `USER#${userId}`,
        SK: `SESSION#${sessionId}`,
        entityType: "Session",
        createdAt: nowIso(),
        expiresAt: ttlAfterDays(7),
      },
      ConditionExpression: "attribute_not_exists(PK) AND attribute_not_exists(SK)",
    }),
  );
}

async function main() {
  const projectId = "alpha";
  const taskId = `task-${Date.now()}`;

  await createTask({
    projectId,
    taskId,
    title: "Review DynamoDB key design",
    ownerId: "masa",
  });

  await createSession({
    userId: "masa",
    sessionId: `session-${Date.now()}`,
  });

  console.log(await listProjectTasks(projectId));
  console.log(
    await completeTask({
      projectId,
      taskId,
      expectedOwnerId: "masa",
    }),
  );
}

main().catch((error) => {
  if (error.name === "ConditionalCheckFailedException") {
    console.error("Condition failed:", error.message);
    process.exit(2);
  }

  console.error(error);
  process.exit(1);
});
DDB_LOCAL=1 node app.mjs

Refactor-Prompt:

Refactore app.mjs in einen Lambda Handler.
Entferne ConditionExpression nicht.
Füge keinen Scan hinzu, außer du erklärst, warum Query nicht funktioniert.
Belasse expiresAt als Number in Unix-Epoch-Sekunden.
Behalte ReturnConsumedCapacity in der Entwicklungsumgebung.

IAM, Kosten und Fehler

Bei einer gemeinsamen Tabelle ist Zugriff auf die ganze Tabelle oft zu breit. Mit dynamodb:LeadingKeys kann IAM auf Partition Keys begrenzt werden.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ProjectScopedDynamoDBAccess",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:UpdateItem",
        "dynamodb:DeleteItem",
        "dynamodb:Query"
      ],
      "Resource": [
        "arn:aws:dynamodb:ap-northeast-1:123456789012:table/ClaudeCodeLabProd",
        "arn:aws:dynamodb:ap-northeast-1:123456789012:table/ClaudeCodeLabProd/index/*"
      ],
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:LeadingKeys": [
            "PROJECT#${aws:PrincipalTag/projectId}"
          ]
        }
      }
    }
  ]
}

On-Demand ist bequem zum Start, Provisioned kann bei stabiler Last günstiger planbar sein. Typische Fehler sind Scan für Listen, FilterExpression als SQL-Ersatz, feste Keys wie GLOBAL, falsche TTL-Erwartungen, GSIs ohne Zugriffsmuster, fehlende Conditional Writes und ein lokaler Endpoint in Produktion.

Prüfe diese DynamoDB-Implementierung auf Scan-Abhängigkeit, Hot Partitions, TTL-Missverständnisse, fehlende Conditional Writes, zu breite IAM-Rechte und On-Demand-Kostenspitzen. Gib zuerst Findings aus, dann Korrekturen.

Wiederverwendbare Prompts und Checklisten findest du in den ClaudeCodeLab Produkten. Für Teams mit AWS, IAM, CI-Reviews und Produktionsnachweisen ist Claude Code Training und Beratung der nächste Schritt.

Fazit

DynamoDB wird zuverlässig, wenn Zugriffsmuster, Keys, Fehlerbedingungen, TTL, IAM und Kostensignale explizit sind. Claude Code beschleunigt die Umsetzung, aber die Grenzen müssen vorher klar sein.

Praxisnotiz (実際に試した結果): Am besten funktionierte die Reihenfolge Zugriffsmuster zuerst, dann PK/SK und Fehlerfälle prüfen, dann Conditional Writes lokal ausführen. Besonders attribute_not_exists für Webhook-Deduplizierung ist eine kleine, aber sehr wirksame Review-Regel.

#claude-code #aws #dynamodb #nosql #typescript #database
Kostenlos

Kostenloses PDF: Claude-Code-Cheatsheet

E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.

Wir schützen Ihre Daten und senden keinen Spam.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.