Intermedio6 min de lectura

Claves de ordenación con relleno de ceros en DynamoDB

Una clave de ordenación de cadena en DynamoDB se ordena lexicográficamente — un carácter a la vez, de izquierda a derecha — no numéricamente. Así que "10" aterriza antes que "2", porque "1" viene antes que "2". El relleno de ceros a un ancho fijo es cómo haces que el orden de cadenas coincida con el orden numérico.

¿Por qué "10" se ordena antes que "2" en una clave de ordenación de DynamoDB?

Porque una clave de ordenación de cadena en DynamoDB se compara lexicográficamente por orden de bytes UTF-8, no numéricamente. El byte de "1" precede a "2", por lo que "10" aterriza antes que "2". Rellena cada número a un ancho fijo con ceros a la izquierda — "2" se convierte en "0000000002" — y el orden de cadenas coincidirá exactamente con el orden numérico.

  • La trampa: los números almacenados como cadenas se ordenan como palabras. "100", "11", "2" es el orden que te da DynamoDB — no lo que querías decir.
  • La solución: rellena cada número a un ancho fijo con ceros a la izquierda, para que "2" se convierta en "0000000002". Ahora el orden lexicográfico y el numérico coinciden.
  • Elige un ancho una vez: dimensiónalo para el mayor valor que vayas a almacenar, luego añade unos cuantos dígitos. Cambiar el ancho después significa reescribir cada clave.
  • Descendente gratis: para ordenar de mayor a menor (el caso del ranking), almacena maxValue - value, también con relleno de ceros — DynamoDB no tiene dirección de ordenación por atributo.

Por qué las claves de ordenación de cadena te traicionan

Si vienes de SQL, un ORDER BY score DESC sobre una columna de enteros "simplemente funciona" — el motor sabe que la columna es numérica. DynamoDB no tiene ese lujo para una clave de ordenación que no sea de tipo Number.

DynamoDB compara las claves de ordenación de cadena (S) por orden de bytes UTF-8, según la documentación de claves de ordenación de AWS. Bytes, no magnitud. "9" (0x39) supera a "10" porque su primer byte le gana a "1" (0x31). La longitud es irrelevante — solo decide el primer byte que difiere.

Ese es el error de diseño: en el momento en que un número vive dentro de una clave de ordenación de cadena, cada Query que recorre el rango devuelve filas en un orden que parece revuelto.

Construye una clave de ordenación para un ranking

Toma un ranking de máquina recreativa por temporadas. Una item collection por temporada contiene cada partida de cada jugador, y quieres las mejores puntuaciones primero.

Modélalo con una clave compuesta en una sola item collection:

  • leaderboardId (clave de partición) — p. ej. SEASON#2026-SPRING.
  • rankKey (clave de ordenación) — la puntuación rellenada con ceros más un desempate.

Un primer intento ingenuo almacena la puntuación cruda como cadena:

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

Un Query sobre SEASON#2026-SPRING los devuelve en este orden de bytes: "10", "1500", "240", "9". La partida de 9 puntos queda en último lugar y la de 1500 puntos enterrada en medio. Inútil para un ranking.

Rellena a un ancho fijo

Elige un ancho lo bastante amplio para la mayor puntuación que vayas a registrar, luego rellena con ceros por la izquierda. Digamos que las puntuaciones topan en diez millones — eso son ocho dígitos, así que usa diez dígitos para tener margen:

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

Ahora cada clave tiene la misma longitud, así que la comparación byte a byte y la comparación numérica producen el orden idéntico. Un Query ascendente da 9, 10, 240, 1500. La matemática por fin coincide con los bytes.

El ancho es una puerta de un solo sentido. Si rellenas a diez dígitos y una puntuación luego supera eso, un valor de 11 dígitos se ordena antes que uno de 10 — rompiéndolo todo de nuevo — y arreglarlo significa reescribir cada rankKey existente. Sobreaprovisiona el ancho; el coste es un puñado de bytes.

Ordenar descendente: almacena la diferencia

Un ranking quiere la puntuación más alta primero. DynamoDB puede leer una clave de ordenación hacia adelante o hacia atrás con ScanIndexForward: false, así que descendente suele ser un flag de tiempo de lectura — recurre a eso primero.

Pero cuando una item collection debe servir direcciones de ordenación mixtas, o quieres la mejor puntuación físicamente primero independientemente de los flags de lectura, invierte el número en sí. Almacena maxValue - score, rellenado con ceros al mismo ancho:

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

El orden de bytes ascendente sobre el valor invertido ahora produce las puntuaciones originales de mayor a menor: 1500, 240, 10, 9. El truco está en el espíritu del paper Amazon Dynamo de 2007: las claves son bytes opacos, así que codificas la intención dentro de los bytes.

Añade un desempate

Dos jugadores pueden empatar. Una puntuación rellenada a secas colisiona en la clave de ordenación, y una segunda escritura sobrescribiría la primera (mismo PK + SK). Añade un sufijo único para que cada partida sea un item distinto y los empates se resuelvan de forma determinista:

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

Por ejemplo "0000001500#0000001719100800#p_8842". Misma puntuación, marca de tiempo más temprana gana el puesto más alto — rellena también la marca de tiempo, o reintroduce el mismo bug que acabas de arreglar.

En DynoTable puedes navegar el ranking de la temporada ordenado por la rankKey rellenada con ceros y observar cómo los valores rellenos alinean correctamente las filas — prueba de que los anchos son correctos antes de llevarlos a producción.

Al ensamblar esa clave compuesta a mano es fácil equivocarse de ancho. Generar el KeyConditionExpression para un Query de "cima de la temporada" en el expression builder mantiene honesta la sintaxis begins_with / between mientras experimentas con anchos.

Navegando el ranking de la temporada en DynoTable, ordenado por la rankKey rellenada con ceros.
Navegando el ranking de la temporada en DynoTable, ordenado por la rankKey rellenada con ceros.

Trampas que evitar

  • Relleno demasiado estrecho. Todo el esquema colapsa la primera vez que un valor desborda el ancho. Dimensiona para el peor caso, luego añade dígitos.
  • Olvidar el flag de lectura. Si solo lees descendente, ScanIndexForward: false puede ser todo lo que necesitas — no recurras a claves invertidas cuando un flag lo hace.
  • Anchos mixtos en una colección. Cada clave que comparte un rango de ordenación debe usar el mismo ancho. Una migración que rellena las filas nuevas pero no las antiguas las entremezcla mal.
  • Rellenar el segmento equivocado. En una clave compuesta, rellena cada segmento numérico que participe en la ordenación — puntuación y marca de tiempo ambos, no solo la puntuación.

Próximos pasos

El relleno de ceros es una herramienta dentro del kit más amplio de diseño de claves de ordenación; combínalo con item collections cuando sobrecargas una clave para servir varios patrones, y apóyate en un Query preciso en lugar de un Scan una vez que la ordenación esté bien.

Prueba DynoTable para navegar una tabla real y ver tus claves de ordenación rellenadas con ceros caer en orden numérico antes de que despliegues el esquema.

Actualizado