Avanzado6 min de lectura

Key overloading en DynamoDB

Si vienes de SQL, una columna significa una cosa para siempre: orders.created_at es siempre una fecha, users.email es siempre un email. El key overloading tira eso por la borda. Das a la clave de partición y de ordenación nombres genéricos — pk, sk — y dejas que cada tipo de item vierta un significado distinto en ellas. Una tabla, muchas entidades, una sola forma.

¿Qué es el key overloading en DynamoDB?

El key overloading consiste en almacenar muchos tipos de entidad en una sola tabla bajo nombres de clave genéricos como pk/sk, codificando el tipo en el valor (USER#u_3001, INVOICE#2026-0014). El nombre del atributo se mantiene neutral para que usuarios, facturas y eventos compartan una misma partición; el valor lleva el tipo, y un prefijo en la clave de ordenación permite que un único Query extraiga cada entidad usando begins_with.

  • Nombres de clave genéricos, valores tipados. Nombra tus claves pk/sk y pon el tipo de entidad en el valor: pk = "TENANT#acme", sk = "USER#u_3001". El nombre es tonto; el valor lleva el tipo.
  • Es lo que hace funcionar el single-table design. Sin overloading, una tabla compartida no es más que un cajón de sastre. Con él, cada entidad vive en una partición que puedes consultar con Query.
  • begins_with es la recompensa. Un prefijo de tipo en la clave de ordenación deja que un Query extraiga una entidad entera, o una porción de ella, sin Scan y sin filtro.
  • El coste: legibilidad. Un volcado crudo de pk/sk no te dice nada. Necesitas un visor que decodifique los prefijos, o estarás entrecerrando los ojos ante cadenas.

Por qué los nombres genéricos ganan a los reales

DynamoDB tiene exactamente dos atributos de clave por tabla, y un Query solo puede apuntar a una única clave de partición. Así que si nombras tu clave userId, solo los items de usuario pueden vivir limpiamente en esa tabla — todo lo demás tiene que fingir un userId o mudarse a su propia tabla.

El overloading esquiva eso. Un nombre neutro como pk no se compromete con ninguna entidad, así que un usuario, una factura y un evento de auditoría pueden compartir el mismo atributo de clave y la misma tabla. El valor, no el nombre del atributo, dice qué es el item.

Este es el movimiento que convierte el single-table design de teoría en algo que realmente puedes consultar. La tabla compartida es el contenedor; el overloading es lo que permite que entidades distintas coexistan dentro de ella.

Un ejemplo multi-tenant

Supón que gestionas un producto SaaS de facturación. Cada tenant tiene miembros, facturas y un rastro de auditoría. En lugar de tres tablas, ponlo todo en una y sobrecarga las claves:

pkskattributes
TENANT#acmeMETAname="Acme Inc", plan="team"
TENANT#acmeUSER#u_3001email, role="admin"
TENANT#acmeUSER#u_3002email, role="member"
TENANT#acmeINVOICE#2026-0014amount_cents, status="paid"
TENANT#acmeINVOICE#2026-0015amount_cents, status="open"
TENANT#acmeEVENT#2026-06-23T09:12Zactor="u_3001", action="invite"

Cada fila comparte pk = "TENANT#acme", así que forman una item collection — todas co-ubicadas, todas alcanzables en una sola lectura de partición.

Partición: TENANT#acmesk: METAsk: USER#u_3001sk: INVOICE#2026-0015sk: EVENT#2026-06-23T09:12ZUn Query

El prefijo de la clave de ordenación hace el trabajo real. Agrupa entidades y las ordena.

Consulta la colección sobrecargada

Como el tipo vive en el prefijo de la clave de ordenación, begins_with corta la partición por entidad sin escanear nada:

Query pk = "TENANT#acme"  -- el tenant entero, cada tipo
Query pk = "TENANT#acme" AND begins_with(sk, "USER#")  -- solo miembros
Query pk = "TENANT#acme" AND begins_with(sk, "INVOICE#")  -- solo facturas

Pagas solo por los items que la condición coincide, no por toda la partición — lo opuesto a un Scan filtrado, donde pagas por leer filas que luego descartas. AWS llama a esto una condición de clave; se ejecuta sobre las claves antes de que cualquier dato salga de la partición.

Si construyes esa condición begins_with a mano, acierta los tags de tipo — un USERS# errante en lugar de USER# no devuelve nada, en silencio. El expression builder genera el KeyConditionExpression y el mapa ExpressionAttributeValues para que los prefijos coincidan con lo que realmente escribiste.

Sobrecarga el índice también

El mismo truco aplica a un GSI. Dale nombres de clave genéricos — gsi1pk, gsi1sk — y deja que cada entidad escriba lo que necesite. Un índice responde entonces patrones que la tabla base no puede.

pkskgsi1pkgsi1sk
TENANT#acmeINVOICE#2026-0015STATUS#open2026-06-30
TENANT#acmeINVOICE#2026-0014STATUS#paid2026-06-12
TENANT#betaINVOICE#2026-0099STATUS#open2026-06-25

Ahora Query gsi1 WHERE gsi1pk = "STATUS#open" lista cada factura abierta de todos los tenants, ordenada por fecha de vencimiento — una vista entre particiones que las claves de la tabla base, limitadas al tenant, nunca podrían servir. Una entidad diferente puede reutilizar gsi1 con su propio significado (digamos gsi1pk = "ROLE#admin"), así que un índice cubre varias lecturas. Solo recuerda que un GSI es eventualmente consistente — sus escrituras van por detrás de la tabla base.

Hazlo en DynoTable

Las claves sobrecargadas en crudo son hostiles de leer: INVOICE#2026-0015 y EVENT#2026-06-23T09:12Z se confunden en una lista plana. Un visor que agrupa por partición y resalta los prefijos convierte el cajón de sastre de nuevo en entidades.

DynoTable navegando la item collection de un tenant — items META, USER, INVOICE y EVENT agrupados bajo una única clave de partición sobrecargada.
DynoTable navegando la item collection de un tenant — items META, USER, INVOICE y EVENT agrupados bajo una única clave de partición sobrecargada.

Trampas

  • Elige los delimitadores una vez y no los cambies nunca. # es la convención. Mezclar # y : entre entidades rompe begins_with de maneras de las que nada te avisa.
  • No sobrecargues valores que necesitan matemática de rango. Una clave de ordenación INVOICE#2026-0015 se ordena léxicamente, no numéricamente — rellena con ceros los ids y usa fechas ISO-8601 para que el orden de cadenas coincida con el orden que quieres decir.
  • Reserva el espacio de nombres de prefijos. Dos tipos de entidad que ambos empiezan con USER (digamos USER# y USERGROUP#) colisionarán bajo begins_with(sk, "USER"). Haz que los prefijos sean inequívocos desde el primer carácter.
  • Planifica la lectura antes que las claves. El overloading sirve patrones de acceso que has enumerado. Si aún no conoces tus lecturas, mira primero single-table design — las claves van por detrás de las consultas.

Traza un mapa de una partición, luego descarga DynoTable para navegar tus propias claves sobrecargadas y ver un Query extraer un tenant entero de una vez.

Actualizado