Wie viele Schreibvorgänge pro Sekunde, bevor eine neue DynamoDB-Tabelle drosselt?
AWS dokumentiert, dass eine brandneue -Tabelle ab Werk "up to 4,000 write request units per second" bedient. Die Studie, die für das tatsächliche Verhalten immer noch alle zitieren — die, auf die Capital One und ScyllaDB beide verlinken — hat 2019 gemessen, vor Warm Throughput, vor konfigurierbaren Maximums, bevor die heutigen Scaling-Regeln existierten. Soweit wir sehen, hat seither niemand eine Messung veröffentlicht.
Also haben wir eine gemacht. Am 2026-08-27, gegen eine Tabelle, die Minuten vorher in us-east-1 angelegt worden war, mit angebotener Write-Last, hochgefahren von 1,000 auf 8,000 Requests pro Sekunde:
| Angeboten | Erreicht | Gedrosselte Requests |
|---|---|---|
| 1,000/s | 1,000/s | 0 |
| 2,000/s | 2,000/s | 0 |
| 3,000/s | 3,000/s | 0 |
| 4,000/s | 4,000/s | 0 |
| 5,000/s | 4,132/s | 25,992 |
| 6,000/s | 4,131/s | 55,966 |
| 8,000/s | 4,134/s | 115,922 |
Die dokumentierte Baseline hält, und sie ist leicht konservativ: Der Service akzeptierte alles bis 4,000/s ohne eine einzige Ablehnung und pinnte dann bei 4,130 ±2 Writes pro Sekunde, egal wie hart wir gedrückt haben. Drei Fenster, drei angebotene Raten, dieselbe Obergrenze auf 0.05% genau. Reads drosselten überhaupt nie — wir haben eine geseedete Tabelle über 12,700 letztendlich konsistente Reads pro Sekunde getrieben, und der Rückstand darüber war unser eigener Client, nicht DynamoDB.
Eine Tabelle, ein Tag, eine Region, ~1 KB große Items mit gleichverteilt zufälligen Keys — keine in Sicht. Dieser Scope ist das ehrliche Kleingedruckte an jeder Zahl hier. Der Rest dieses Posts ist, wie wir gemessen haben, inklusive des Teils, in dem der Benchmark dreimal scheiterte, bevor er funktionierte — und keiner der Fehlschläge war DynamoDBs Schuld.
Das Throttle ist ein 400, und es nennt zuerst den falschen Verdächtigen
Wenn die Obergrenze greift, lohnt es sich, den Fehler genau zu lesen:
ThrottlingException: Throughput exceeds the current capacity of your table or index.
DynamoDB is automatically scaling your table or index so please try again shortly.
If exceptions persist, check if you have a hot key:
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-partition-key-design.htmlDrei Beobachtungen, alle gemessen:
- Es ist ein HTTP 400, kein 5xx. Deine Retry-Policy und deine Dashboards müssen das wissen. Ein Client, der nur 5xx retryt, lässt diese unter den Tisch fallen; ein Monitor, der nur bei 5xx alarmiert, zeigt einen grünen Service, während ein Drittel deiner Writes abprallt.
- Das erste Throttle kam 0.9–3.8 Sekunden nach Beginn jedes Fensters über der Baseline — der Service gibt dir einen kurzen Grace-Burst, bevor die Obergrenze greift, und je höher die angebotene Rate, desto früher beißt sie.
- Der Hot-Key-Hinweis ist ein Default, keine Diagnose. Unsere Keys waren gleichverteilt zufällige UUIDs; es gab keinen Hot Key. Bei kleiner Skala ist der erste Verdächtige für diese Message schlicht die Obergrenze auf Tabellenebene.
Die Latenz blieb davon völlig unbeeindruckt: Die p50-Write-Latenz lag in jedem Fenster bei 4–5 ms in-Region, gedrosselt oder nicht. Ablehnen ist für den Service billig — er wird nicht langsamer, er sagt einfach Nein.
Vom Laptop aus lässt sich das nicht messen
Unser erstes Instrument war das naheliegende: ein Node-Script auf einem Laptop in Madrid. Es pacete 1,000 Writes pro Sekunde sauber und kollabierte bei 2,000 — nicht weil DynamoDB zurückschob, sondern weil ~100 ms Atlantik-Roundtrip bedeuten, dass 2,000 In-Flight-Requests pro Sekunde Hunderte gleichzeitiger Sockets brauchen, und der Event Loop ersoff. Der Service drosselte kein einziges Mal. Wir haben unser eigenes WLAN gebenchmarkt.
Versuch zwei verlegte den Runner als einzelne Lambda-Funktion in dieselbe Region. Der In-Region-Roundtrip liegt bei ~5 ms, und eine 3-GB-Funktion pacete 2,000 Requests pro Sekunde sauber bei 10 ms p50. Darüber flachte sie bei rund 1,100/s ab, mit ausgereizter CPU: Request-Signing und Response-Handling sind single-threaded JavaScript, und ein einzelner Runner kann schlicht keine 4,000 Requests pro Sekunde signieren. Allokierter Memory: 3 GB. Genutzter Memory: 253 MB. Der Bottleneck war nie RAM — der CPU-Anteil einer Lambda skaliert mit ihrem Memory-Setting, und wir haben Compute gekauft, nicht Storage.
Das finale Instrument ist also eine Flotte aus acht Lambdas, jede pacet ein Achtel der gesamten angebotenen Rate, alle gegen eine gemeinsame Wall-Clock-T0 gestartet, damit ihre Fenster zusammenfallen. Acht Runner mit komfortablen 1,000/s je gaben uns 8,000/s angebotene Last mit Luft nach oben, und das Aggregat ist eine Summe gezählter Requests — nirgends extrapoliert.
Drei Läufe starben, bevor einer funktionierte, und DynamoDB war jedes Mal unschuldig
Der erste Lauf der Flotte endete damit, dass ein Runner meldete, er sei 882 Sekunden nach T0 gestartet — fünfzehn Minuten zu spät zu einem zwanzig Sekunden langen Termin. Der zweite Lauf starb an einem Read-Timeout. Der dritte, mit deaktivierten Retries, failte laut auf allen acht Runnern gleichzeitig. Derweil zeigte CloudWatch, dass jede einzelne Lambda ihre vierminütige Messung sauber, pünktlich und ohne Fehler beendete.
Der Übeltäter war die Verbindung zwischen Laptop und Lambda. Eine synchrone Invocation hält eine HTTPS-Verbindung offen, vollkommen still, für den ganzen Lauf — und ein Consumer-Router killt stille Verbindungen nach ein paar Minuten leise. Die CLI sah einen toten Socket und tat das Schlimmstmögliche: sie retryte still und ließ eine Mess-Lambda erneut laufen, die ihr T0 dann längst verpasst hatte. Eine Benchmark-Harness, die unsichtbar zweimal laufen kann, ist keine Harness; sie ist ein Zufallszahlengenerator mit AWS-Rechnung.
Die Form, die schließlich funktionierte, hat drei Regeln, die wir jetzt für jede lang laufende Remote-Messung verwenden würden:
- Fire-and-forget, Ergebnisse out-of-band. Runner werden asynchron aufgerufen (die Verbindung schließt in Millisekunden) und schreiben ihre Ergebnisse als Items in eine kleine DynamoDB-Tabelle; der Driver pollt auf acht Ergebnis-Rows. Keine Verbindung lebt länger als ein Request.
- Retries sind überall aus. Der Mess-Client läuft mit einem Versuch pro Request — ein Retry würde still genau die Throttles absorbieren, zu deren Zählung wir da sind — und auch der Invocation-Pfad hat Retries deaktiviert, sodass kein Runner je zweimal ausführen kann.
- Ein Watchdog statt eines Hängers. Jeder Runner setzt seinen Zeitplan gegen eine Deadline; klemmt irgendetwas, gibt er Teilzählungen plus einen Snapshot zurück, wo genau er feststeckte, statt still ins Timeout zu laufen. Ein gescheiterter Lauf, der sich erklärt, kostet einen Read; ein hängender kostet einen Abend.
Jeder Request trägt außerdem ein 8-Sekunden-Timeout. Der Lauf, der hing, tat das, weil ein einzelner In-Flight-Request ohne Timeout den finalen Drain-Schritt für immer verkeilte. Ein einziges unbegrenztes Warten, auf ~4 Millionen Requests, hat gereicht.
Was die Reads gemacht haben
Die Read-Phase lief gegen eine zweite frische Tabelle, geseedet mit 1,000 Items, mit GetItems (~1 KB je, 0.5 Read Units):
| Angeboten | Erreicht | Gedrosselt |
|---|---|---|
| 4,000/s | 4,000/s | 0 |
| 8,000/s | 7,941/s | 0 |
| 12,000/s | 11,119/s | 0 |
| 16,000/s | 12,762/s | 0 |
Null Throttles, nie. Die dokumentierte Baseline von 12,000 Reads/s hält, und wir konnten ihre Kante nicht finden: Bei angebotenen 16,000/s liefen fünf unserer acht Runner in ihre eigene clientseitige Sättigung, die 12,762/s sind also der Punkt, an dem unsere Flotte dichtmachte, nicht DynamoDB. Wir sagen das klar, statt es als Service-Limit aufzuhübschen. Reads in-Region liefen bei 2–4 ms p50.
Zwei kleinere Zahlen zum Behalten: Eine frische On-Demand-Tabelle ging beim Benchmark-Lauf in 22 Sekunden von CreateTable auf ACTIVE und bei einer früheren Probe in 7.4 Sekunden — budgetiere die Varianz, nicht den Best Case. Und der ganze Benchmark, 672,116 abgerechnete Writes und 1.08 Millionen Reads, kostete $0.97. Das Instrument ist wiederverwendbar; das Experiment ist ein Kaffee.
Eine halbe Stunde Druck verdoppelt die Obergrenze nicht
AWSs Wachstumsregel sagt, dass On-Demand-Kapazität bis zum Doppelten deines vorherigen Peaks aufnimmt. Wir wollten das sehen, also haben wir nach dem Obergrenzen-Lauf eine Tabelle 34 zusammenhängende Minuten bei 8,000 Writes/s angebotener Last gehalten und die erreichte Rate pro 10 Sekunden gebucketet. Die Form:
| Minuten unter Last | Obergrenze |
|---|---|
| 0–8 | ~4,000/s (Baseline, unbewegt) |
| 9–25 | ~5,000/s |
| 26 | ~6,000/s |
| 27–34 | ~7,000/s |
Das Wachstum kommt in abrupten ~1,000/s-Stufen, nicht als Rampe — eine Minute ist flach auf einer Rate, die nächste Minute flach auf der nächsten. Die erste Obergrenze klebt volle 8 Minuten kontinuierlicher Übernachfrage. Und nach 34 Minuten bediente die Tabelle 7,000/s: 1.75× ihres Startwerts, immer noch unter den angebotenen 8,000 und unter einer sauberen Verdopplung. Wenn dein Launch mehr als ~4,000 Writes/s auf einer neuen Tabelle braucht, wärme sie vorher auf oder setze ihren maximalen On-Demand-Durchsatz explizit — der Wachstumsmechanismus ist echt, aber weder sofort noch großzügig nach deinem Zeitplan. Das Verhalten ist zudem stabil: Unter 9,000/s angebotener Last war die Tabelle der 2019er-Studie bei Testende auf etwa 7,000/s gewachsen — dasselbe Plateau, das unsere sieben Jahre später erreichte. Dieser Lauf kostete $12.67, das Teuerste, was wir den ganzen Tag gemacht haben.
Was übertragbar ist, wenn du selbst einen Cloud-Service misst
- Stell den Lastgenerator in dieselbe Region wie das Ziel. Sonst misst du deine Route, nicht den Service.
- Ein Node-Prozess macht bei rund 2,000 signierten Requests pro Sekunde dicht, unabhängig vom Memory; shard die Last über Runner und summiere gezählte Ergebnisse.
- Deaktiviere Retries auf dem Messpfad, auf jeder Ebene. Retries existieren, um genau das zu verbergen, was ein Benchmark sehen soll.
- Halte nie eine stille Verbindung über einen langen Lauf offen. Asynchron invoken, Ergebnisse out-of-band liefern, pollen.
- Gib jedem Request ein Timeout und jedem Runner einen Watchdog, der Teildaten plus einen Snapshot des Steckenbleibens zurückgibt.
- Setze eine harte Operations-Obergrenze pro Runner, damit ein Pacing-Bug abbricht statt die Rechnung hochzutreiben, und reiße in einem
finallyalles wieder ab, was der Lauf angelegt hat — Tabellen, Roles, Functions, Logs.
Die Referenzseiten, in die das einfließt
Der vollständige Datensatz — jedes Fenster, jeder Runner, Latenz-Perzentile, die wortwörtlichen Error-Strings — untermauert jetzt die gemessenen Tabellen in unserer DynamoDB-Limits-Referenz, neben den Item-Size- und Page-Limit-Proben, die wir früher veröffentlicht haben. Wenn du täglich gegen DynamoDB arbeitest: DynoTable ist unser Desktop-Client dafür — dasselbe Team, dieselbe Gewohnheit, Claims gegen den Live-Service zu prüfen, bevor wir sie wiederholen.