Architecture

ProvisionedThroughputExceededException in DynamoDB: Ursachen und Lösungen

2026-03-25 · 8 Min. Lesezeit

Ihre Anwendung läuft um 14 Uhr einwandfrei. Um 15 Uhr steigen die Antwortzeiten, Fehler häufen sich, und Ihre Logs füllen sich mit dieser Meldung:

An error occurred (ProvisionedThroughputExceededException) when calling the
PutItem operation: The level of configured provisioned throughput for the table
or one or more global secondary indexes was exceeded. Consider increasing the
provisioned throughput for the under-provisioned index or table.

Oder aus dem SDK:

DynamoDBServiceException: Rate exceeded for shard.
Throughput exceeds the current capacity of your table or index.

Dieser Fehler bedeutet, dass DynamoDB Ihre Anfragen ablehnt, weil Sie mehr Lese- oder Schreibverkehr senden, als Ihre Tabelle verarbeiten kann. Die eigentliche Frage ist aber warum — und die Antwort ist selten so einfach wie "Kapazität erhöhen". In den meisten Fällen liegt die Ursache in einem Designproblem, das durch mehr Kapazität nicht gelöst wird.

So diagnostizieren und beheben Sie das Problem richtig.

Schritt 1: Aktuelle Tabellenkonfiguration verstehen

Beginnen Sie damit, den Kapazitätsmodus und die aktuellen Einstellungen Ihrer Tabelle zu prüfen:

aws dynamodb describe-table \
  --table-name my-table \
  --query 'Table.{
    TableName: TableName,
    BillingMode: BillingModeSummary.BillingMode,
    ProvisionedThroughput: ProvisionedThroughput,
    TableSizeBytes: TableSizeBytes,
    ItemCount: ItemCount,
    GSIs: GlobalSecondaryIndexes[*].{
      IndexName: IndexName,
      ProvisionedThroughput: ProvisionedThroughput
    }
  }'

Die Ausgabe zeigt, ob Sie sich im PROVISIONED- oder PAY_PER_REQUEST-Modus (On-Demand) befinden. Im Provisioned-Modus geben die Werte ReadCapacityUnits und WriteCapacityUnits Ihre Obergrenze an.

Ein kritisches Detail, das viele Teams übersehen: Jeder Global Secondary Index (GSI) hat seinen eigenen provisionierten Durchsatz, der unabhängig von der Basistabelle ist. Selbst wenn die Basistabelle ausreichend Kapazität hat, kann ein gedrosselter GSI zurückwirken und Schreibvorgänge auf die Basistabelle drosseln.

Schritt 2: CloudWatch-Metriken auf Throttling prüfen

CloudWatch zeigt Ihnen genau, wo und wann Throttling auftritt:

# Gedrosselte Leseoperationen auf der Basistabelle prüfen
aws cloudwatch get-metric-statistics \
  --namespace AWS/DynamoDB \
  --metric-name ReadThrottleEvents \
  --dimensions Name=TableName,Value=my-table \
  --start-time "2026-03-25T14:00:00Z" \
  --end-time "2026-03-25T16:00:00Z" \
  --period 300 \
  --statistics Sum \
  --query 'Datapoints[*].[Timestamp,Sum]' \
  --output table

# Gedrosselte Schreiboperationen prüfen
aws cloudwatch get-metric-statistics \
  --namespace AWS/DynamoDB \
  --metric-name WriteThrottleEvents \
  --dimensions Name=TableName,Value=my-table \
  --start-time "2026-03-25T14:00:00Z" \
  --end-time "2026-03-25T16:00:00Z" \
  --period 300 \
  --statistics Sum \
  --query 'Datapoints[*].[Timestamp,Sum]' \
  --output table

Prüfen Sie auch die verbrauchte vs. provisionierte Kapazität:

# Verbrauchte Schreibkapazität
aws cloudwatch get-metric-statistics \
  --namespace AWS/DynamoDB \
  --metric-name ConsumedWriteCapacityUnits \
  --dimensions Name=TableName,Value=my-table \
  --start-time "2026-03-25T14:00:00Z" \
  --end-time "2026-03-25T16:00:00Z" \
  --period 300 \
  --statistics Sum Average Maximum \
  --query 'Datapoints | sort_by(@, &Timestamp)' \
  --output table

Wenn die verbrauchte Kapazität deutlich unter der provisionierten Kapazität liegt, aber trotzdem Throttling auftritt, sind fast sicher Hot Partitions die Ursache.

Ursache 1: Hot Partition Keys

Dies ist die häufigste und am meisten missverstandene Ursache. DynamoDB verteilt Daten anhand des Partition-Key-Hashes auf Partitionen. Wenn ein unverhältnismäßig großer Anteil des Traffics auf denselben Partition Key entfällt, wird diese einzelne Partition überlastet, während andere Partitionen untätig bleiben.

Ein klassisches Beispiel: Verwendung eines Datumsstrings wie 2026-03-25 als Partition Key. Alle heutigen Schreibvorgänge treffen eine Partition. Die gestrige Partition ist kalt. Die morgige existiert noch nicht.

Ein weiteres Beispiel: Eine Multi-Tenant-Anwendung mit tenant_id als Partition Key, wobei ein Mandant 90% des Traffics erzeugt. Die Partition dieses Mandanten ist überlastet.

Um Hot Partitions zu identifizieren, nutzen Sie CloudWatch Contributor Insights:

# Contributor Insights für die Tabelle aktivieren
aws dynamodb update-contributor-insights \
  --table-name my-table \
  --contributor-insights-action ENABLE

Die Lösung liegt in der Neugestaltung Ihres Partition Keys, um den Traffic gleichmäßiger zu verteilen. Gängige Strategien:

  • Zufälliges Suffix hinzufügen: Statt tenant_id verwenden Sie tenant_id#shard_N, wobei N eine Zufallszahl zwischen 0 und 9 ist. Das verteilt Schreibvorgänge auf 10 Partitionen pro Mandant.
  • Zusammengesetzter Schlüssel: Kombinieren Sie das Datum mit einem anderen Attribut, z.B. 2026-03-25#user_id.
  • Write Sharding: Für High-Velocity-Schreibmuster fügen Sie eine berechnete Shard-Nummer an den Partition Key an und verwenden Scatter-Gather für Lesevorgänge.

Ursache 2: Unzureichende provisionierte Kapazität

Manchmal ist die Antwort tatsächlich, dass Sie mehr Kapazität benötigen. Wenn Ihre verbrauchte Kapazität konstant die provisionierte Kapazität erreicht oder überschreitet, müssen Sie skalieren.

Schnelle Soforthilfe:

# Provisionierte Kapazität erhöhen
aws dynamodb update-table \
  --table-name my-table \
  --provisioned-throughput ReadCapacityUnits=1000,WriteCapacityUnits=500

Oder wechseln Sie zum On-Demand-Modus, um Throttling vollständig zu eliminieren:

aws dynamodb update-table \
  --table-name my-table \
  --billing-mode PAY_PER_REQUEST

Wichtige Hinweise zum On-Demand-Modus:

  • Sie können einmal pro Tag von Provisioned auf On-Demand wechseln
  • On-Demand hat weiterhin Limits pro Partition (3.000 RCU und 1.000 WCU pro Partition)
  • On-Demand ist pro Anfrage teurer als Provisioned bei vorhersagbarem Traffic
  • Neue On-Demand-Tabellen starten mit einem vorherigen Peak von 0 und skalieren schrittweise. Wenn Sie wechseln und sofort hohen Traffic senden, werden Sie weiterhin gedrosselt, bis sich die Tabelle anpasst

Falls Sie beim Provisioned-Modus bleiben, aktivieren Sie Auto-Scaling:

# Skalierbares Ziel registrieren
aws application-autoscaling register-scalable-target \
  --service-namespace dynamodb \
  --resource-id "table/my-table" \
  --scalable-dimension "dynamodb:table:WriteCapacityUnits" \
  --min-capacity 10 \
  --max-capacity 5000

# Skalierungsrichtlinie erstellen
aws application-autoscaling put-scaling-policy \
  --service-namespace dynamodb \
  --resource-id "table/my-table" \
  --scalable-dimension "dynamodb:table:WriteCapacityUnits" \
  --policy-name "my-table-write-scaling" \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration '{
    "TargetValue": 70.0,
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "DynamoDBWriteCapacityUtilization"
    },
    "ScaleInCooldown": 60,
    "ScaleOutCooldown": 60
  }'

Ursache 3: GSI-Throttling-Rückwirkung

Dies ist die heimtückischste Ursache. Wenn ein Global Secondary Index gedrosselt wird, drosselt DynamoDB Schreibvorgänge auf die Basistabelle, um zu verhindern, dass der GSI zu weit hinterherhinkt. Ihre Basistabelle hat genügend Kapazität, aber Schreibvorgänge werden abgelehnt, weil der GSI nicht mithalten kann.

Prüfen Sie GSI-spezifische Metriken:

aws cloudwatch get-metric-statistics \
  --namespace AWS/DynamoDB \
  --metric-name WriteThrottleEvents \
  --dimensions Name=TableName,Value=my-table Name=GlobalSecondaryIndexName,Value=my-gsi \
  --start-time "2026-03-25T14:00:00Z" \
  --end-time "2026-03-25T16:00:00Z" \
  --period 300 \
  --statistics Sum \
  --query 'Datapoints[*].[Timestamp,Sum]' \
  --output table

Die Lösung: Stellen Sie sicher, dass jeder GSI mindestens so viel Schreibkapazität hat wie die Basistabelle. Besser noch: Setzen Sie die GSI-Schreibkapazität höher als die der Basistabelle, da GSI-Schreibvorgänge den Overhead der Indexwartung beinhalten.

Ursache 4: Burst-Kapazität erschöpft

DynamoDB bietet 300 Sekunden Burst-Kapazität — ungenutzte Kapazität, die sich ansammelt, wenn Ihre Tabelle unterausgelastet ist. Bei Traffic-Spitzen greift DynamoDB auf diese Reserve zurück. Sobald sie erschöpft ist, greift striktes Throttling.

Wenn Sie Throttling nur bei kurzen Spitzen sehen, könnte Ihre Burst-Kapazität erschöpft sein. Das verräterische Zeichen ist Throttling, das nach wenigen Minuten reduzierten Traffics aufhört.

Es gibt keinen CLI-Befehl, um die Burst-Kapazität direkt zu prüfen — sie wird nicht als Metrik exponiert. Aber Sie können es ableiten: Wenn ConsumedCapacity über längere Zeiträume unter ProvisionedCapacity liegt und dann kurzzeitig darüber steigt, bevor Throttling einsetzt, war die Burst-Kapazität der Puffer, der erschöpft wurde.

Ursache 5: Adaptive Capacity noch nicht aktiviert

DynamoDBs Adaptive-Capacity-Feature verteilt automatisch provisionierte Kapazität auf Hot Partitions. Aber es braucht 5 bis 30 Minuten, um zu greifen. Wenn eine plötzliche Traffic-Musteränderung eine neue Hot Partition erzeugt, kann Throttling auftreten, bis Adaptive Capacity sich anpasst.

Dies ist ein vorübergehendes Problem. Wenn sich das Throttling nach 15-30 Minuten von selbst löst, war Adaptive Capacity wahrscheinlich die Ursache. Keine Aktion erforderlich, außer sicherzustellen, dass Ihre Retry-Logik das temporäre Throttling korrekt behandelt.

Retry-Logik korrekt implementieren

Unabhängig von der Ursache muss Ihre Anwendung Throttling elegant behandeln. Die AWS SDKs enthalten eingebaute Retry-Logik, aber die Standardwerte sind möglicherweise nicht ausreichend:

import boto3
from botocore.config import Config

# Aggressive Retries mit exponentiellem Backoff konfigurieren
config = Config(
    retries={
        'max_attempts': 10,
        'mode': 'adaptive'  # Adaptiver Modus passt sich Throttling-Mustern an
    }
)

dynamodb = boto3.resource('dynamodb', config=config)
table = dynamodb.Table('my-table')

Für Batch-Operationen implementieren Sie exponentiellen Backoff bei UnprocessedItems:

import time
import random

def batch_write_with_backoff(table_name, items, max_retries=8):
    client = boto3.client('dynamodb')
    unprocessed = {table_name: items}
    retries = 0

    while unprocessed and retries < max_retries:
        response = client.batch_write_item(RequestItems=unprocessed)
        unprocessed = response.get('UnprocessedItems', {})

        if unprocessed:
            retries += 1
            wait_time = min(2 ** retries + random.uniform(0, 1), 30)
            print(f"Retry {retries}: {len(unprocessed[table_name])} unverarbeitete Items, "
                  f"warte {wait_time:.1f}s")
            time.sleep(wait_time)

    return unprocessed

Best Practices zur Prävention

  1. Wählen Sie Partition Keys, die Traffic gleichmäßig verteilen. Attribute mit hoher Kardinalität wie User-IDs sind besser als solche mit niedriger Kardinalität wie Statuscodes oder Datumsangaben.

  2. Überwachen Sie CloudWatch-Throttle-Metriken mit Alarmen. Warten Sie nicht auf Anwendungsfehler, um Throttling zu entdecken:

aws cloudwatch put-metric-alarm \
  --alarm-name "DynamoDB-Throttle-my-table" \
  --namespace AWS/DynamoDB \
  --metric-name WriteThrottleEvents \
  --dimensions Name=TableName,Value=my-table \
  --statistic Sum \
  --period 300 \
  --threshold 10 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 1 \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:my-alerts
  1. Aktivieren Sie Contributor Insights für alle Produktionstabellen. Die Kosten sind minimal und Sie erhalten sofortige Sichtbarkeit auf Hot Keys.

  2. Verwenden Sie DAX (DynamoDB Accelerator) für leselastige Workloads. DAX cached häufig abgerufene Items und reduziert die Leselast auf die Basistabelle um das 10-fache oder mehr.

  3. Erwägen Sie den On-Demand-Modus für unvorhersagbare Workloads. Die Kosten pro Anfrage sind höher, aber Sie werden nie gedrosselt (innerhalb der Partitionslimits) und es ist keine Kapazitätsplanung erforderlich.

Wenn Throttling ein Architekturproblem ist

Wenn Sie sich ständig dabei ertappen, Kapazitätseinstellungen anzupassen oder Hot-Partition-Probleme zu bekämpfen, könnte das Problem in Ihrem Datenmodell liegen. DynamoDB glänzt bei Zugriffsmustern, die im Voraus bekannt und dafür entworfen sind. Wenn sich Ihre Zugriffsmuster über das hinaus entwickelt haben, was Ihr Tabellendesign unterstützt, ist es möglicherweise Zeit für eine Neugestaltung des Datenmodells.

Wir helfen Teams regelmäßig dabei, ihre DynamoDB-Tabellen und Zugriffsmuster neu zu gestalten, um Throttling-Probleme dauerhaft zu eliminieren. Wenn Ihr DynamoDB-Throttling die Benutzererfahrung beeinträchtigt oder durch überdimensionierte Kapazität Kosten verursacht, melden Sie sich für eine kostenlose Architektur-Überprüfung — wir analysieren Ihr Tabellendesign und Ihre Zugriffsmuster und zeigen Ihnen genau, wo die Engpässe liegen.

Brauchen Sie Hilfe mit Ihrer AWS-Infrastruktur?

Buchen Sie ein kostenloses 30-Minuten-Gespräch.