Intermedio6 min de lectura

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:

PKSKauthortags
POST#9f3META{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:

PKSKauthorIdauthorNametitle
POST#9f3METAU#12"Mara Vance""Modeling 1:N"
POST#a71METAU#12"Mara Vance""Sparse GSIs"
POST#b04METAU#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)
Formahijo anidado dentro del padremismo valor en muchos items
Mejor paradatos acotados, propiedad del padreun valor compartido que muchos items muestran
Lecturaun GetItemun Query
Coste de actualizaciónreescribir el único item padrepropagar a cada copia
Riesgo de tamañotope de item de 400 KBninguno 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:

"DynamoDB"App"DynamoDB"App"Actualizar nombre USER"Consultar posts del autor""POST"Actualizar cada authorName"

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.

Actualizado