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/SKen el código (p. ej.CONFIG#GLOBAL) en vez de templar un id de usuario o pedido. - Léelo con
GetItem, nunca conScan. 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:
| PK | SK | attributes |
|---|---|---|
| SETTINGS#APP | FLAGS#V1 | signup_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:
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
Putel 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 singleton | Item por entidad | |
|---|---|---|
| Clave | Constante hardcodeada (SETTINGS#APP) | Plantilla con un id (USER#42) |
| Cuántos | Exactamente uno | Uno por usuario / pedido / tenant |
| Lectura típica | GetItem sobre la clave conocida | GetItem o Query por entidad |
| Alcance | Toda la aplicación | Una sola entidad |
| Úsalo para | Flags globales, config, versión sistema | Perfiles, 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
Scanpara 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.