Fortgeschritten5 Min. Lesezeit

Zero-Padding von Sort Keys in DynamoDB

Ein String- in DynamoDB sortiert lexikografisch — Zeichen für Zeichen, von links nach rechts —, nicht numerisch. "10" landet also vor "2", weil "1" vor "2" kommt. Zero-Padding auf eine feste Breite ist der Weg, die String-Reihenfolge zur numerischen zu machen.

Warum sortiert „10“ in einem DynamoDB-Sort-Key vor „2“?

Weil ein String- in DynamoDB lexikografisch nach UTF-8-Byte-Reihenfolge verglichen wird, nicht numerisch. Das Byte für "1" kommt vor "2", also landet "10" vor "2". Füll jede Zahl mit führenden Nullen auf eine feste Breite auf — aus "2" wird "0000000002" — und die String-Reihenfolge entspricht dann exakt der numerischen.

  • Lexikografische Reihenfolge: Als Strings gespeicherte Zahlen sortieren wie Wörter. "100", "11", "2" ist die Reihenfolge, die DynamoDB dir gibt — nicht das, was du meintest.
  • Die Lösung: Füll jede Zahl mit führenden Nullen auf eine feste Breite auf, sodass aus "2" "0000000002" wird. Jetzt stimmen lexikografische und numerische Reihenfolge überein.
  • Wähle die Breite einmal: Bemiss sie für den größten Wert, den du je speichern wirst, und leg dann ein paar Stellen drauf. Die Breite später zu ändern heißt, jeden Key neu zu schreiben.
  • Absteigend gratis: Um von hoch nach niedrig zu sortieren (der Leaderboard-Fall), speichere maxValue - value, ebenfalls mit Nullen aufgefüllt — DynamoDB hat keine Sortierrichtung je Attribut.

Warum String-Sort-Keys dich hintergehen

Aus der SQL-Welt „funktioniert“ ein ORDER BY score DESC über einer Integer-Spalte einfach — die Engine weiß, dass die Spalte numerisch ist. Diesen Luxus hat DynamoDB bei einem Sort Key, der kein Number-Typ ist, nicht.

DynamoDB vergleicht String-Sort-Keys (S) nach UTF-8-Byte-Reihenfolge, siehe die AWS-Dokumentation zum Sort Key. Bytes, nicht Größenordnung. "9" (0x39) steht über "10", weil sein erstes Byte "1" (0x31) schlägt. Die Länge ist irrelevant — nur das erste abweichende Byte entscheidet.

Das ist das Footgun: In dem Moment, in dem eine Zahl in einem String-Sort-Key steckt, gibt jede Query, die den Bereich abläuft, Zeilen in einer Reihenfolge zurück, die durcheinandergewürfelt aussieht.

Einen Leaderboard-Sort-Key bauen

Nimm ein saisonales Arcade-Leaderboard. Eine pro Saison hält jeden Lauf eines Spielers, und du willst die höchsten Scores zuerst.

Modelliere es mit einem in einer einzigen Item Collection:

  • leaderboardId (Partition Key) — z. B. SEASON#2026-SPRING.
  • rankKey (Sort Key) — der mit Nullen aufgefüllte Score plus ein Tiebreaker.

Ein naiver erster Versuch speichert den rohen Score als String:

leaderboardIdrankKeyplayerHandle
SEASON#2026-SPRING"9"quickdraw
SEASON#2026-SPRING"10"ace_pilot
SEASON#2026-SPRING"1500"nightowl
SEASON#2026-SPRING"240"bytecrash

Eine Query auf SEASON#2026-SPRING gibt sie in dieser Byte-Reihenfolge zurück: "10", "1500", "240", "9". Der Lauf mit 9 Punkten steht ganz hinten und der Lauf mit 1500 Punkten liegt in der Mitte begraben. Für ein Leaderboard nutzlos.

Auf eine feste Breite auffüllen

Wähle eine Breite, die für den größten Score reicht, den du je erfassen wirst, und füll links mit Nullen auf. Angenommen, Scores enden bei zehn Millionen — das sind acht Stellen, nimm also zehn Stellen für Luft nach oben:

leaderboardIdrankKeyplayerHandle
SEASON#2026-SPRING"0000000009"quickdraw
SEASON#2026-SPRING"0000000010"ace_pilot
SEASON#2026-SPRING"0000000240"bytecrash
SEASON#2026-SPRING"0000001500"nightowl

Jetzt ist jeder Key gleich lang, byteweiser Vergleich und numerischer Vergleich ergeben also dieselbe Reihenfolge. Eine aufsteigende Query liefert 9, 10, 240, 1500. Die Mathematik passt endlich zu den Bytes.

Die Breite ist eine Einbahnstraße. Füllst du auf zehn Stellen auf und übersteigt ein Score das später, sortiert ein 11-stelliger Wert vor einem 10-stelligen — womit wieder alles kaputt ist —, und die Reparatur heißt, jeden bestehenden rankKey neu zu schreiben. Überdimensioniere die Breite; sie kostet eine Handvoll Bytes.

Absteigend sortieren: die Differenz speichern

Ein Leaderboard will den höchsten Score zuerst. DynamoDB kann einen Sort Key vorwärts oder rückwärts lesen, mit ScanIndexForward: false, absteigend ist also meist ein Flag zur Lesezeit — greif zuerst dorthin.

Wenn aber eine Item Collection gemischte Sortierrichtungen bedienen muss oder du den Top-Score unabhängig von Lese-Flags physisch zuerst haben willst, dreh die Zahl selbst um. Speichere maxValue - score, auf dieselbe Breite mit Nullen aufgefüllt:

scoreinverted (9999999999 - score)rankKey
15009999998499"9999998499"
2409999999759"9999999759"
109999999989"9999999989"
99999999990"9999999990"

Aufsteigende Byte-Reihenfolge über dem invertierten Wert liefert nun die ursprünglichen Scores von hoch nach niedrig: 1500, 240, 10, 9. Der Trick liegt im Geist des Amazon-Dynamo-Papers von 2007 — Keys sind undurchsichtige Bytes, du kodierst deine Absicht also in die Bytes.

Einen Tiebreaker ergänzen

Zwei Spieler können gleichauf liegen. Ein bloßer aufgefüllter Score kollidiert im Sort Key, und ein zweiter Schreibvorgang würde den ersten überschreiben (gleicher PK + SK). Häng ein eindeutiges Suffix an, damit jeder Lauf ein eigenes Item ist und Gleichstände deterministisch aufgelöst werden:

rankKey = "<paddedScore>#<paddedTimestamp>#<playerId>"

Zum Beispiel "0000001500#0000001719100800#p_8842". Gleicher Score, früherer Zeitstempel gewinnt den höheren Platz — füll auch den Zeitstempel auf, sonst holst du dir genau den Fehler zurück, den du gerade behoben hast.

In DynoTable kannst du das Saison-Leaderboard sortiert nach dem aufgefüllten rankKey durchstöbern und zusehen, wie die aufgefüllten Werte die Zeilen korrekt ausrichten — der Beweis, dass die Breiten stimmen, bevor du sie ausrollst.

Setzt du diesen zusammengesetzten Key von Hand zusammen, vertippst du dich leicht bei einer Breite. Die KeyConditionExpression für eine „Spitze der Saison“-Query im Expression Builder zu generieren, hält die Syntax von begins_with / between ehrlich, während du mit Breiten experimentierst.

Das Saison-Leaderboard in DynoTable durchstöbern, sortiert nach dem mit Nullen aufgefüllten rankKey.
Das Saison-Leaderboard in DynoTable durchstöbern, sortiert nach dem mit Nullen aufgefüllten rankKey.

Fallstricke, die du vermeiden solltest

  • Zu schmal aufgefüllt. Das ganze Schema kippt beim ersten Wert, der die Breite sprengt. Bemiss für den schlimmsten Fall und leg dann Stellen drauf.
  • Das Lese-Flag vergessen. Liest du ohnehin nur absteigend, reicht vielleicht ScanIndexForward: false — greif nicht zu invertierten Keys, wenn ein Flag es tut.
  • Gemischte Breiten in einer Collection. Jeder Key, der sich einen Sortierbereich teilt, muss dieselbe Breite nutzen. Eine Migration, die neue Zeilen auffüllt, alte aber nicht, verschränkt sie falsch.
  • Das falsche Segment aufgefüllt. In einem zusammengesetzten Key füllst du jedes numerische Segment auf, das an der Ordnung beteiligt ist — Score und Zeitstempel, nicht nur den Score.

Nächste Schritte

Zero-Padding ist ein Werkzeug im größeren Baukasten des Sort-Key-Designs; kombiniere es mit Item Collections, wenn du einen Key überlädst, um mehrere Muster zu bedienen, und stütze dich auf eine präzise Query statt auf einen Scan, sobald die Ordnung stimmt.

Probier DynoTable aus, um eine echte Tabelle zu durchstöbern und deine mit Nullen aufgefüllten Sort Keys in numerische Reihenfolge fallen zu sehen, bevor du das Schema ausrollst.

Aktualisiert