Desnormalización en DynamoDB
Si vienes de SQL, la desnormalización suena a pecado — datos duplicados, sin una única fuente de verdad. En DynamoDB es todo el objetivo. No hay joins, así que copias los datos relacionados en el item que los necesita y los lees de un solo golpe.
¿Qué es la desnormalización en DynamoDB?
La desnormalización en DynamoDB consiste en copiar los datos relacionados en el item que los lee, de modo que una sola consulta lo devuelve todo de una vez. Como DynamoDB no tiene joins, pre-juntas los datos en tiempo de escritura en lugar de unir tablas en tiempo de lectura. El intercambio son los datos obsoletos: duplica solo valores que cambian rara vez.
- Sin joins significa que pre-juntas en tiempo de escritura. Almacena el valor relacionado en el item que lo lee, para que una consulta nunca necesite una segunda búsqueda.
- Dos variantes. Embebe datos anidados en un atributo complejo sobre un item, o duplica un valor entre muchos items.
- El error son los datos obsoletos. Cuando la fuente cambia, cada copia queda incorrecta hasta que propagas la actualización. Duplica solo valores que cambian rara vez.
- Compra lecturas, no escrituras. Cambias escrituras más numerosas (y más cuidadosas) por lecturas baratas de una sola petición.
Por qué no hay joins a los que recurrir
Un JOIN relacional reensambla filas normalizadas en tiempo de lectura. DynamoDB no
tiene join — un Query lee una item collection y devuelve exactamente lo que hay
almacenado allí. Nada une dos tablas por ti.
Así que los datos ya tienen que estar moldeados para la lectura. Si una pantalla necesita un post y el nombre de su autor, ese nombre debe vivir en algún sitio que la lectura del post ya toque. El paper Amazon Dynamo de 2007 hizo este intercambio explícito: renunciar a las características relacionales para obtener lecturas predecibles de milisegundos de un solo dígito a escala.
Patrón 1 — embeber con un atributo complejo
Los atributos de DynamoDB pueden contener mapas y listas anidados, no solo escalares. Así que una forma común de desnormalización es meter un objeto hijo directamente dentro de su item padre en lugar de darle su propio item.
Un post con sus etiquetas y un pequeño snapshot del autor, todo en un item:
| PK | SK | author | tags |
|---|---|---|---|
| POST#9f3 | META | {id: U#12, name: "Mara Vance"} | ["dynamodb","aws"] |
Un GetItem devuelve el post, las etiquetas y el bloque del autor juntos. Sin segunda
lectura. Esto es genial para datos que son propiedad del padre y están acotados en
tamaño — un puñado de etiquetas, un snapshot de autor.
El límite a respetar: un solo item de DynamoDB tope en 400 KB, nombres y valores de atributos incluidos (Service Quotas). Embebe una lista no acotada (cada comentario de un post viral) y te pasarás de ese límite.
Patrón 2 — duplicar un valor entre items
El caso del blog es el de manual. Listas posts y quieres que cada fila muestre el nombre visible del autor — pero no quieres una segunda lectura por post para obtenerlo.
Así que escribes el nombre del autor en cada item de post cuando se crea el post:
| PK | SK | authorId | authorName | title |
|---|---|---|---|---|
| POST#9f3 | META | U#12 | "Mara Vance" | "Modeling 1:N" |
| POST#a71 | META | U#12 | "Mara Vance" | "Sparse GSIs" |
| POST#b04 | META | U#88 | "Lio Tan" | "Query vs Scan" |
Ahora Query PK begins_with "POST#" (o un GSI sobre posts) renderiza toda la lista —
título y autor — sin búsqueda por fila. El nombre del autor está desnormalizado: la
copia canónica vive en USER#12, y cada post lleva su propia copia.
El intercambio está ahí mismo. Has convertido una lectura N+1 en una sola lectura, a
costa de mantener "Mara Vance" en N+1 sitios.
Embeber vs. duplicar — cuál
| Embeber (atributo complejo) | Duplicar (copiar entre items) | |
|---|---|---|
| Forma | hijo anidado dentro del padre | mismo valor en muchos items |
| Mejor para | datos acotados, propiedad del padre | un valor compartido que muchos items muestran |
| Lectura | un GetItem | un Query |
| Coste de actualización | reescribir el único item padre | propagar a cada copia |
| Riesgo de tamaño | tope de item de 400 KB | ninguno por item |
Recurre a embeber cuando el hijo solo aparece junto a su padre. Recurre a duplicar cuando muchos items independientes necesitan mostrar el mismo valor compartido.
El error: copias obsoletas
Aquí está la parte que muerde. Mara se renombra a "Mara V.". Actualizas USER#12. Cada
item de post sigue diciendo "Mara Vance" hasta que vas a arreglarlos.
Así que actualizar un valor duplicado es una escritura de propagación, no una sola línea. Consultas cada item afectado y reescribes cada uno — idealmente protegido para que solo toques las filas que aún tienen el valor antiguo:
UPDATE POST#9f3
SET authorName = "Mara V."
WHERE authorName = "Mara Vance"
Puedes componer ese SET condicional contra authorName en el
Expression Builder y copiar el UpdateExpression
y ConditionExpression generados directamente en tu código.
La propagación en sí es una escritura por item: consulta los posts del autor, luego emite las actualizaciones. La secuencia:
El coste de duplicar datos: cada cambio en la fuente es una consulta más una escritura por copia.
Por eso la regla es duplicar solo valores que cambian rara vez. Un nombre visible, un nivel de plan, una etiqueta de categoría — bien. Un contador en vivo o un campo editado con frecuencia — no; la propagación te comerá vivo.
Cuándo la normalización sigue ganando
Si un valor cambia a menudo, o un item se lee con patrones genuinamente impredecibles, mantenlo normalizado y acepta la lectura extra. La desnormalización es una optimización para patrones de acceso conocidos e intensivos en lecturas — no un valor por defecto que aplicar en todas partes. Pre-junta las lecturas que de verdad ejecutas, y deja el resto en paz.
Para decidir dónde viven estos atributos duplicados, modela primero los patrones de acceso — ver single-table design y, para el lado de lectura del intercambio, Query vs Scan.
Descarga DynoTable para inspeccionar una tabla desnormalizada, detectar qué copias se han desviado y ejecutar la actualización de propagación contra tus propios datos.