Intermedio8 min de lectura

Items singleton en DynamoDB

Un item singleton es una sola fila con una clave fija y hardcodeada que guarda estado para toda tu aplicación — no un registro por usuario o por pedido, sino un registro, punto. Feature flags, un blob de config, un kill-switch global: lo que en una app relacional vivirías en una tabla de settings de una sola fila.

Si vienes de SQL, irías a una tabla config con id = 1 y un SELECT * FROM config. En DynamoDB haces lo mismo con una clave de partición hardcodeada — y como siempre conoces esa clave, la lees con un GetItem, no con un Query ni un Scan.

¿Qué es un item singleton en DynamoDB?

Un item singleton es una sola fila de DynamoDB bajo una clave fija y hardcodeada que guarda estado global de toda la aplicación — feature flags, un blob de config, una versión de sistema — en lugar de un registro por usuario o pedido. Como siempre conoces la clave, la lees con un GetItem y la actualizas con más expresiones de condición.

  • Un singleton es un item con una clave constante. Hardcodeas el PK/SK en el código (p. ej. CONFIG#GLOBAL) en vez de templar un id de usuario o pedido.
  • Léelo con GetItem, nunca con Scan. Siempre conoces la clave completa, así que una lectura puntual tiene un coste plano y predecible (como mucho 1 RCU para un item pequeño) — sin filtro, sin recorrer la tabla.
  • Es una por definición. Cada petición puede tocar la misma partición, así que cachea y mantén el item pequeño; no lo conviertas en un cuello de botella de escritura.
  • Mútalo con seguridad con update + expresiones de condición, no con leer-modificar-escribir en tu app — ahí vive la carrera de lost-update.

Reconoce el patrón

Tienes estado global cuando los datos no están acotados a ninguna entidad. Algunas señales:

  • Un flag igual para todos (signup_enabled = false).
  • Un blob de tunables que tu app lee al arrancar (rate limits, cuotas por defecto).
  • Un contador o número de versión de todo el sistema, no por fila.

Cualquier cosa acotada a un usuario, tenant o pedido no es un singleton — es un item normal claveado por el id de esa entidad. El singleton es la rebanada global sobrante que no tiene otro sitio donde vivir.

Dale una clave constante

Todo el patrón gira en torno a una decisión. La clave es un literal, no una plantilla. Para un item global de feature flags en una single-table sobrecargada, elige un prefijo fijo y un valor fijo:

PKSKattributes
SETTINGS#APPFLAGS#V1signup_enabled, maintenance_mode, ai_search_enabled

PK = "SETTINGS#APP" y SK = "FLAGS#V1" van metidos en el código. No hay id de usuario ni de tenant — la aplicación pide exactamente este item cada vez. Esa previsibilidad es el punto: una clave conocida es un GetItem, y un GetItem es la lectura más barata y consistente que ofrece DynamoDB.

El sufijo V1 es deliberado. Si más adelante el esquema de flags cambia de forma, escribes un item FLAGS#V2 y cambias los lectores, en vez de mutar el vivo in place. Versionar la clave del singleton te compra una costura de migración limpia.

Léelo con GetItem

Como la clave es totalmente conocida, nunca haces Query ni Scan por un singleton. Un Scan lee toda la tabla y filtra en el cliente — el clásico pie del Scan — y es un exceso absurdo para traer una fila que puedes direccionar directamente.

Un GetItem contra SETTINGS#APP / FLAGS#V1 devuelve los flags en una sola lectura de consistencia fuerte o eventual. En on-demand en us-east-1, AWS factura un GetItem de un item ≤ 4 KB como 0.5 RCU con consistencia eventual o 1 RCU con consistencia fuerte (docs AWS de capacidad de lectura/escritura). Mantén el singleton pequeño y ese coste se queda plano para siempre — hinchar el blob de config a un segundo bloque de 4 KB duplica la lectura facturada.

En el camino de lectura, cuando la app arranca o llega una petición, haces GetItem de la clave fija y cacheas el resultado. El flujo:

noApp / peticiónGetItem PK=SETTINGS#APPSK=FLAGS#V1¿Item encontrado?Usar flags, cache localCaer a defaults seguros

La clave fija convierte un lookup global en una lectura puntual con un camino de default integrado.

Fíjate en la rama no: un singleton ausente no debería tumbarte. Por defecto usa el valor seguro (feature off, mantenimiento on) para que un hueco de primer deploy o una clave mala falle cerrado, no abierto.

Actualízalo sin carrera

La trampa es actualizar un singleton con leer-modificar-escribir en tu app: haces GetItem de los flags, cambias uno en memoria y luego PutItem del todo. Dos escritores concurrentes leen ambos el item viejo y el segundo Put aplasta el cambio del primero. Lost update.

Dos features de DynamoDB matan la carrera sin locking en la app:

  • Las mutan un atributo en el servidor, dejando el resto intacto. No hace falta volver a Put el item entero.
  • Las hacen que la escritura solo tenga éxito si el item sigue como esperas, así que una escritura obsoleta se rechaza con ConditionalCheckFailedException (docs AWS de expresiones de condición).

Para cambiar un flag, apunta solo a ese atributo con un SET y protégelo con un bump de versión para que escritores concurrentes no se pisoteen:

# UpdateItem
Key                  PK=SETTINGS#APP  SK=FLAGS#V1
UpdateExpression     SET signup_enabled = :on, schema_version = :next
ConditionExpression  schema_version = :current

Si dos escritores compiten, el check schema_version = :current del segundo falla y reintenta contra el valor fresco. Puedes andamiar los nombres, valores y esta forma exacta de expresión en el DynamoDB Expression Builder antes de cablearlo en código. Para ir más a fondo con los operadores, mira la guía de idiomas de update-expression.

Cuidado con la clave caliente

Un singleton es, por construcción, una clave caliente — cada parte de tu app puede leer la misma partición. Está bien para lecturas si cacheas, pero es el único riesgo real del patrón.

  • Cachea con agresividad. Lee los flags una vez por proceso (o cada N segundos), no en cada petición. El valor del singleton es lo más barato de memorizar.
  • No lo conviertas en un hot spot de escritura. Un flag que un admin cambia unas veces al día no es nada. Un singleton que incrementas en cada petición es un cuello de botella de throughput de partición — eso es un problema de contador, no de singleton.
  • Manténlo pequeño. El coste de lectura escala con el tamaño del item en bloques de 4 KB. Un blob de config hinchado hace cada arranque más caro de lo necesario.

Si de verdad necesitas un contador global de muchas escrituras, el singleton es la forma equivocada — shardea a través de N items y suma al leer. Eso es otro patrón.

Singleton vs item por entidad

La línea es simplemente a qué están acotados los datos.

Item singletonItem por entidad
ClaveConstante hardcodeada (SETTINGS#APP)Plantilla con un id (USER#42)
CuántosExactamente unoUno por usuario / pedido / tenant
Lectura típicaGetItem sobre la clave conocidaGetItem o Query por entidad
AlcanceToda la aplicaciónUna sola entidad
Úsalo paraFlags globales, config, versión sistemaPerfiles, pedidos, cualquier cosa por-id

Si te pillas queriendo dos singletons del mismo tipo, no tienes un singleton — tienes un item por entidad y la entidad es lo que olvidaste clavear (config por tenant, por ejemplo).

Escollos y próximos pasos

  • No hagas Scan para encontrarlo. Conoces la clave; dirígela directamente.
  • No hagas leer-modificar-escribir. Usa update + expresiones de condición.
  • No dejes que desaparezca en silencio. Por defecto al valor seguro en un cache miss.
  • No lo sobrecargues con escrituras de alta frecuencia. Eso es trabajo de contador sharded.

El singleton vive cómodo dentro de un single-table design — es solo una colección de items más con clave fija junto a tus filas de entidad.

Prueba DynoTable para explorar tu tabla, encontrar la fila singleton por su clave fija y editar flags a mano mientras construyes el camino de escritura.

Actualizado