Intermedio5 min di lettura

Zero-Padding delle Sort Key in DynamoDB

Una sort key string di DynamoDB si ordina lessicograficamente — un carattere alla volta, da sinistra a destra — non numericamente. Quindi "10" finisce prima di "2", perché "1" viene prima di "2". Lo zero-padding a una larghezza fissa è il modo per far corrispondere l'ordine delle stringhe all'ordine numerico.

Perché "10" si ordina prima di "2" in una sort key di DynamoDB?

Perché una sort key string di DynamoDB viene confrontata lessicograficamente per ordine di byte UTF-8, non numericamente. Il byte di "1" precede "2", quindi "10" finisce prima di "2". Padda ogni numero a una larghezza fissa con zeri iniziali — "2" diventa "0000000002" — e l'ordine delle stringhe corrisponderà esattamente all'ordine numerico.

  • La trappola: numeri memorizzati come stringhe si ordinano come parole. "100", "11", "2" è l'ordine che DynamoDB ti dà — non quello che intendevi.
  • La soluzione: padda ogni numero a una larghezza fissa con zeri iniziali, così "2" diventa "0000000002". Ora ordine lessicografico e numerico concordano.
  • Scegli la larghezza una volta: dimensionala per il valore più grande che mai memorizzerai, poi aggiungi qualche cifra. Cambiare la larghezza più tardi significa riscrivere ogni chiave.
  • Discendente gratis: per ordinare dal più alto al più basso (il caso leaderboard), memorizza maxValue - value, anch'esso zero-paddato — DynamoDB non ha una direzione di sort per attributo.

Perché le sort key string ti tradiscono

Venendo da SQL, un ORDER BY score DESC su una colonna intera "funziona e basta" — il motore sa che la colonna è numerica. DynamoDB non ha questo lusso per una sort key che non è di tipo Number.

DynamoDB confronta le sort key string (S) per ordine di byte UTF-8, secondo la documentazione AWS sulle sort key. Byte, non grandezza. "9" (0x39) supera "10" perché il suo primo byte batte "1" (0x31). La lunghezza è irrilevante — decide solo il primo byte differente.

Questo è il footgun: nel momento in cui un numero vive dentro una sort key string, ogni Query che percorre il range restituisce righe in un ordine che sembra scomposto.

Costruisci una sort key per leaderboard

Prendi una leaderboard arcade stagionale. Una item collection per stagione contiene ogni run di ogni giocatore, e vuoi i punteggi più alti per primi.

Modellala con una composite key in un'unica item collection:

  • leaderboardId (chiave di partizione) — es. SEASON#2026-SPRING.
  • rankKey (sort key) — il punteggio zero-paddato più un tiebreaker.

Un primo tentativo ingenuo memorizza il punteggio grezzo come stringa:

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

Una Query su SEASON#2026-SPRING li restituisce in questo ordine di byte: "10", "1500", "240", "9". La run da 9 punti sta dead last e la run da 1500 punti è sepolta nel mezzo. Inutile per una leaderboard.

Padda a una larghezza fissa

Scegli una larghezza abbastanza ampia per il punteggio più grande che mai registrerai, poi padda a sinistra con zeri. Diciamo che i punteggi arrivano al massimo a dieci milioni — sono otto cifre, quindi usa dieci cifre per margine:

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

Ora ogni chiave ha la stessa lunghezza, quindi il confronto byte-per-byte e il confronto numerico producono l'ordine identico. Una Query ascendente dà 9, 10, 240, 1500. La matematica finalmente corrisponde ai byte.

La larghezza è una porta a senso unico. Se paddi a dieci cifre e un punteggio successivamente la supera, un valore a 11 cifre si ordina prima di uno a 10 cifre — ri-rompendo tutto — e correggerlo significa riscrivere ogni rankKey esistente. Sovradimensiona la larghezza; il costo è una manciata di byte.

Ordina discendente: memorizza la differenza

Una leaderboard vuole il punteggio più alto per primo. DynamoDB può leggere una sort key in avanti o all'indietro con ScanIndexForward: false, quindi discendente è di solito un flag in fase di lettura — reggiungilo per primo.

Ma quando una item collection deve servire direzioni di sort miste, o vuoi il punteggio più alto fisicamente per primo indipendentemente dai flag di lettura, inverti il numero stesso. Memorizza maxValue - score, zero-paddato alla stessa larghezza:

score   inverted (9999999999 - score)   rankKey
1500    9999998499                       "9999998499"
240     9999999759                       "9999999759"
10      9999999989                       "9999999989"
9       9999999990                       "9999999990"

L'ordine di byte ascendente sul valore invertito ora produce i punteggi originali dall'alto al basso: 1500, 240, 10, 9. Il trucco è nello spirito del paper Amazon Dynamo del 2007 — le chiavi sono byte opachi, quindi codifichi l'intento dentro i byte.

Aggiungi un tiebreaker

Due giocatori possono pareggiare. Un punteggio paddato nudo collide sulla sort key, e una seconda scrittura sovrascriverebbe la prima (stessa PK + SK). Aggiungi un suffisso univoco così ogni run è un Item distinto e i pareggi si risolvono in modo deterministico:

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

Per esempio "0000001500#0000001719100800#p_8842". Stesso punteggio, timestamp precedente vince lo slot più alto — padda anche il timestamp, altrimenti reintroduce l'esatto bug che hai appena corretto.

In DynoTable puoi sfogliare la leaderboard di stagione ordinata per rankKey zero-paddata e osservare i valori riempiti allineare correttamente le righe — prova che le larghezze sono giuste prima di rilasciarle.

Quando assembli quella composite key a mano è facile sbagliare una larghezza. Generare la KeyConditionExpression per una Query "top della stagione" nell' expression builder mantiene onesta la sintassi begins_with / between mentre sperimenti con le larghezze.

Navigazione della leaderboard di stagione in DynoTable, ordinata per rankKey zero-paddata.
Navigazione della leaderboard di stagione in DynoTable, ordinata per rankKey zero-paddata.

Errori da evitare

  • Padding troppo stretto. L'intero schema collassa la prima volta che un valore sfora la larghezza. Dimensiona per il caso peggiore, poi aggiungi cifre.
  • Dimenticare il flag di lettura. Se leggi sempre solo discendente, ScanIndexForward: false potrebbe essere tutto ciò che ti serve — non reggiungere a chiavi invertite quando basta un flag.
  • Larghezze miste in una collection. Ogni chiave che condivide un range di sort deve usare la stessa larghezza. Una migrazione che padda le nuove righe ma non le vecchie le intervalla in modo sbagliato.
  • Paddare il segmento sbagliato. In una composite key, padda ogni segmento numerico che partecipa all'ordinamento — punteggio e timestamp entrambi, non solo il punteggio.

Prossimi passi

Lo zero-padding è uno strumento nel più ampio toolkit di design delle sort key; abbinalo alle item collection quando sovraccarichi una chiave per servire diversi pattern, e appoggiati a una Query precisa invece di uno Scan una volta che l'ordinamento è giusto.

Prova DynoTable per navigare una tabella reale e osservare le tue sort key zero-paddate cadere in ordine numerico prima di rilasciare lo schema.

Aggiornato