El módulo 5 terminó con un diagnóstico incómodo: el siguiente cuello de botella de MercadoFresco es la
base de datos. No porque mercadofresco-pedidos esté mal configurada —tiene Multi-AZ, réplica de
lectura, copias automáticas y PITR desde 02-04—, sino porque hace cuatro trabajos distintos y solo
uno de ellos es el suyo.
La tentación, llegados aquí, es abrir la consola y empezar a crear servicios. DynamoDB suena rápido, Redshift suena analítico, ElastiCache suena a caché. Esa es exactamente la forma de acabar con seis bases de datos y los mismos problemas, más el coste de mantenerlas.
Esta lección no crea ningún recurso. Es la lección en la que Marta se sienta con los datos que el módulo 5 le dio —métricas de CloudWatch, trazas de X-Ray, registros del ALB— y decide, con criterios explicables ante dirección, qué motor recibe cada carga de trabajo y por qué. Las cuatro lecciones siguientes ejecutan esa decisión. Esta la justifica.
Nota sobre coste. Esta lección es de diseño: no se crea nada y por tanto no genera factura. Las cifras de coste que aparecen son estimaciones de la región
eu-west-1con datos ficticios, y sirven para comparar órdenes de magnitud, no para presupuestar.
Contenido
- Por qué «una base de datos para todo» acaba fallando
- Los tres síntomas del acoplamiento de cargas
- Persistencia políglota: una herramienta por trabajo
- Las nueve familias de bases de datos
- Tabla maestra: familia, servicio de AWS y caso de MercadoFresco
- El primer criterio: el patrón de acceso, no el modelo de datos
- Volumen, crecimiento y latencia objetivo
- Consultas conocidas frente a consultas exploratorias
- Consistencia: el teorema CAP con una caja de fresas
- Transacciones, esquema y coste
- Árbol de decisión
- OLTP frente a OLAP
- Normalización, desnormalización y el cambio de mentalidad de NoSQL
- Las cuatro cargas de MercadoFresco, medidas
- La decisión razonada, carga por carga
- Cómo se migra sin parar la tienda
- AWS DMS y SCT: las herramientas de migración
- Errores comunes y consejos
- Ejercicios
- Conclusión
Por qué «una base de datos para todo» acaba fallando
Cuando en 02-04 creamos mercadofresco-pedidos, PostgreSQL era la respuesta correcta. Había una
aplicación, unas pocas tablas y un equipo de tres personas. Una base de datos relacional bien
normalizada resolvía el catálogo, los pedidos, el carrito y los informes con el mismo SELECT.
El problema no es que esa decisión fuera mala. Es que las cargas de trabajo divergen con el tiempo y la base de datos no. Dieciocho meses después, dentro de la misma instancia conviven:
- Escrituras minúsculas y constantes que caducan solas (el carrito).
- Transacciones que deben ser exactas para siempre (los pedidos).
- Lecturas idénticas repetidas miles de veces por minuto (el catálogo).
- Barridos de millones de filas para calcular sumas (los informes de Sara).
Cada una de esas cuatro cosas tiene un motor que la hace bien. Ninguna de las cuatro es PostgreSQL haciéndolo todo a la vez.
Los tres síntomas del acoplamiento de cargas
Meter cargas incompatibles en un mismo motor produce siempre los mismos tres síntomas. Reconocerlos es la mitad del diagnóstico.
1. Interferencia de rendimiento. Una carga degrada a otra por competir por el mismo recurso
finito. En MercadoFresco, el informe mensual de Sara mantiene un plan de ejecución con Seq Scan sobre
pedidos y lineas_pedido durante 40 segundos; mientras tanto, el buffer cache de PostgreSQL se
llena de páginas históricas que expulsan las páginas calientes del catálogo, y la latencia de la
tienda sube aunque el informe no toque ninguna tabla de la tienda. Es el efecto más contraintuitivo
del acoplamiento: el daño no viaja por los bloqueos, viaja por la memoria compartida.
2. Acoplamiento de disponibilidad. Si la instancia cae, caen las cuatro cargas. Un DROP INDEX
mal medido, una consulta que agota las conexiones o una actualización de versión afectan a todo por
igual. La réplica mercadofresco-pedidos-lectura mitiga esto solo para lecturas, y no para todas.
3. Escalado del mínimo común múltiplo. Cuando una sola carga necesita más recursos, hay que
escalar toda la instancia. Los informes de Sara necesitan memoria; el carrito necesita IOPS de
escritura; el catálogo necesita CPU para parsear la misma consulta miles de veces. Como no se pueden
escalar por separado, se acaba pagando una db.r6g.2xlarge para que una de las cuatro cargas vaya
bien tres horas al mes.
graph TD
A[Tienda MercadoFresco] --> DB[(mercadofresco-pedidos<br/>PostgreSQL 16 db.t3.small)]
B[Carrito y sesiones] --> DB
C[Catálogo de productos] --> DB
D[Informes de Sara] --> DB
DB --> E[Interferencia: el informe expulsa<br/>del cache las páginas del catálogo]
DB --> F[Acoplamiento: una caída, cuatro servicios parados]
DB --> G[Escalado: se paga por el pico de la carga más exigente]
Persistencia políglota: una herramienta por trabajo
Persistencia políglota es el nombre del principio que dice que una aplicación puede —y a menudo
debe— usar varios almacenes de datos, cada uno elegido por su ajuste a una carga concreta. No es una
moda de microservicios: es la aplicación al almacenamiento de la misma idea que ya aceptamos sin
discusión en otros ámbitos. Nadie guarda las fotos del catálogo en PostgreSQL: están en
mercadofresco-catalogo-fotos, en S3, desde 02-03. Eso ya era persistencia políglota.
Sus contrapartidas son reales y hay que decirlas antes de venderla:
| A favor | En contra |
|---|---|
| Cada carga rinde al máximo de su motor | Más servicios que operar, vigilar y parchear |
| Escalado y coste independientes por carga | Más superficie de seguridad (IAM, cifrado, red) |
| Un fallo no arrastra a las demás cargas | Coherencia entre almacenes: ya no hay un JOIN |
| Se puede cambiar una pieza sin tocar el resto | El equipo debe conocer varios modelos de datos |
| Se elimina la interferencia de rendimiento | Copias de seguridad y restauración coordinadas |
La regla práctica: añade un motor cuando puedas nombrar la carga concreta que lo justifica y medir la mejora. Si no puedes hacer ambas cosas, no lo añadas. En MercadoFresco pasaremos de una base de datos a cuatro almacenes, y cada uno tendrá detrás una medición del módulo 5.
Las nueve familias de bases de datos
Conviene entender qué modela cada familia antes de mirar nombres de producto. El modelo de datos determina qué consultas son baratas y cuáles son caras, y eso es lo único que importa.
Relacional
Modela entidades y relaciones en tablas con filas y columnas, esquema fijo y restricciones de
integridad. El motor garantiza propiedades ACID y permite combinar tablas con JOIN en tiempo de
consulta. Brilla cuando los datos tienen relaciones complejas, cuando las consultas no se conocen de
antemano y cuando la corrección transaccional es innegociable. No brilla cuando hay que escalar
horizontalmente la escritura, cuando el esquema cambia cada semana o cuando el volumen por consulta es
de millones de filas agregadas.
Clave-valor
Modela un diccionario gigante: una clave devuelve un valor opaco. No hay JOIN ni consultas por
campos arbitrarios. A cambio, la búsqueda por clave es de coste constante y el motor puede repartir
las claves entre cientos de particiones sin coordinación. Brilla en sesiones, carritos, perfiles y
cualquier acceso «dame el registro X». No brilla cuando hace falta preguntar «dame todos los registros
que cumplan Y» sin haberlo previsto.
Documental
Modela documentos JSON anidados con esquema flexible e índices sobre campos internos. Es un clave-valor que además sabe mirar dentro del valor. Brilla en catálogos con atributos heterogéneos —una merluza tiene «peso» y «origen de captura», un queso tiene «curación» y «leche»— y en datos que se leen siempre completos. No brilla en agregaciones masivas ni cuando las relaciones entre documentos son el centro del problema.
En memoria
Modela estructuras de datos (cadenas, hashes, listas, conjuntos ordenados) que viven en RAM, con latencias de microsegundos y persistencia opcional. Brilla como caché, como almacén de sesiones, como contador y como marcador en tiempo real. No brilla como fuente de verdad de datos que no se pueden perder, ni cuando el conjunto de datos no cabe en memoria a un coste razonable.
Columnar o almacén de datos
Modela hechos y dimensiones y guarda los datos por columna en lugar de por fila, lo que permite leer solo las columnas necesarias y comprimirlas muchísimo. Brilla en agregaciones sobre cientos de millones de filas. No brilla en la lectura o escritura de una fila concreta, que es precisamente lo que hace una tienda todo el día.
Series temporales
Modela medidas con marca de tiempo: métricas, telemetría, sensores. Optimiza la escritura secuencial, la retención por antigüedad y las funciones de ventana temporal. Brilla en «temperatura de la cámara frigorífica cada 10 segundos durante dos años». No brilla en datos sin dimensión temporal dominante.
Grafos
Modela nodos y aristas y hace baratos los recorridos de profundidad variable: «clientes que
compraron lo mismo que este cliente y viven en su ciudad, a tres saltos». En un relacional eso son
JOIN recursivos que explotan. Brilla en recomendaciones, detección de fraude y redes sociales. No
brilla cuando el problema es tabular y las relaciones son de un solo salto.
Búsqueda
Modela texto invertido: para cada término, la lista de documentos que lo contienen, con relevancia, tolerancia a errores tipográficos, sinónimos y facetas. Brilla en el buscador de la tienda —«sarmón» debe devolver «salmón»— y en el análisis de registros. No brilla como fuente de verdad transaccional.
Libro mayor
Modela un registro inmutable y verificable criptográficamente de cambios. Brilla cuando hay que demostrar ante un tercero que un histórico no se ha alterado. No brilla como base de datos de propósito general, y conviene saber que Amazon QLDB está en fin de soporte: para casos nuevos, la recomendación de AWS es usar Aurora PostgreSQL con tablas de solo inserción y firmas.
Tabla maestra: familia, servicio de AWS y caso de MercadoFresco
| Familia | Servicio de AWS | Latencia típica | Consultas ad hoc | Caso de MercadoFresco |
|---|---|---|---|---|
| Relacional | Amazon RDS / Aurora | 1-10 ms | Sí, SQL completo |
Pedidos, facturas, stock: la fuente de verdad (06-03) |
| Clave-valor | Amazon DynamoDB | <10 ms, estable | No, solo por clave | Carrito y sesiones (06-02) |
| Documental | Amazon DocumentDB | 1-10 ms | Parcial | Fichas de producto con atributos heterogéneos (futuro) |
| En memoria | ElastiCache / MemoryDB | <1 ms | No | Caché del catálogo y sesiones web (06-05) |
| Columnar | Amazon Redshift | Segundos | Sí, analítico | Informes de Sara sobre 18 meses (06-04) |
| Series temporales | Amazon Timestream | ms | Temporal | Telemetría de temperatura de furgonetas frigoríficas |
| Grafos | Amazon Neptune | ms | Recorridos | «Quien compró esto también compró…» |
| Búsqueda | Amazon OpenSearch Service | ms | Texto y facetas | Buscador de la tienda con tolerancia a erratas |
| Libro mayor | (QLDB en fin de soporte) | — | — | Trazabilidad de lote de producto fresco |
Los cuatro en negrita son los de este módulo. Los demás se nombran para que sepas que existen y no para usarlos: cada servicio que añades es una cosa más que operar, y MercadoFresco no tiene equipo para nueve.
El primer criterio: el patrón de acceso, no el modelo de datos
El error más extendido al elegir base de datos es empezar por dibujar las entidades. En el mundo relacional funciona, porque el modelo relacional está diseñado precisamente para que puedas normalizar primero y consultar después de cualquier forma. En el resto de familias no funciona, y el orden correcto es el inverso.
Antes de elegir motor, responde con números a estas preguntas para cada carga:
- ¿Qué operaciones se hacen, con qué frecuencia y en qué proporción de lectura frente a escritura?
- ¿Por qué campo se accede? ¿Siempre el mismo, o cambia?
- ¿Cuántos elementos devuelve una operación típica: uno, cien, un millón?
- ¿Cuál es la latencia máxima aceptable en el percentil 99?
- ¿Qué pasa si un dato se pierde? ¿Y si se lee un valor de hace dos segundos?
- ¿Cuánto crece al mes y qué tamaño tendrá en tres años?
Estas seis respuestas, y no el diagrama entidad-relación, deciden el motor. El módulo 5 nos dio las seis para las cuatro cargas de MercadoFresco: eso es lo que hace que esta decisión sea defendible.
Volumen, crecimiento y latencia objetivo
Volumen y crecimiento deciden si un motor de nodo único sirve. Un servidor relacional bien dimensionado maneja sin dificultad decenas de terabytes; el problema aparece cuando lo que hay que escalar es la escritura, porque una base de datos relacional escala escrituras verticalmente —máquina más grande— hasta que se acaban las máquinas. Los motores clave-valor distribuidos escalan escritura horizontalmente añadiendo particiones, y eso es lo que los hace distintos.
Latencia objetivo hay que expresarla siempre en percentiles, nunca en media. En 05-01 vimos que la
media de TiempoConfirmacionPedido era 240 ms y el p99, 1.900 ms: la media escondía el problema. Los
órdenes de magnitud a memorizar:
| Origen | Latencia típica | Qué significa para la tienda |
|---|---|---|
| Caché en memoria (misma AZ) | 0,2-1 ms | Imperceptible |
| Clave-valor gestionada | 3-10 ms | Imperceptible en una petición |
| Relacional con índice | 1-10 ms | Bien, si son pocas consultas |
Relacional con Seq Scan |
100 ms-60 s | El p99 de 05-02 |
| Almacén columnar | 1-30 s | Aceptable en un informe, letal en una ficha |
Una ficha de producto que hace 30 consultas relacionales de 8 ms tarda 240 ms solo en base de datos. La misma ficha resuelta desde caché tarda menos de 10 ms. Ese es todo el argumento de 06-05.
Consultas conocidas frente a consultas exploratorias
Este criterio separa el mundo en dos y casi nadie lo formula explícitamente.
- Consultas conocidas de antemano. Sabes hoy, en tiempo de diseño, las diez consultas que la aplicación hará durante los próximos dos años: «dame el carrito del cliente X», «dame el pedido Y». Con eso puedes modelar para que cada consulta sea un acceso directo. Es el terreno de DynamoDB.
- Consultas exploratorias. Sara no sabe hoy qué preguntará en marzo. Necesita cruzar lo que sea con lo que sea. Ahí hace falta un motor que acepte SQL arbitrario: relacional para volúmenes pequeños, columnar para volúmenes grandes.
Elegir DynamoDB para una carga exploratoria produce Scan sobre toda la tabla, que es lento y caro.
Elegir relacional para una carga conocida de altísimo volumen produce lo que tiene MercadoFresco. El
mismo dato puede vivir en los dos sitios: los pedidos son transaccionales en Aurora y analíticos en
Redshift, y eso no es una contradicción sino el diseño.
Consistencia: el teorema CAP con una caja de fresas
El teorema CAP dice que un sistema distribuido no puede garantizar simultáneamente consistencia (Consistency), disponibilidad (Availability) y tolerancia a la partición de red (Partition tolerance). Como la partición de red no es opcional —los cables se cortan—, la elección real es entre consistencia y disponibilidad cuando hay partición.
Con producto fresco se ve inmediatamente. Quedan 3 cajas de fresas y dos clientes las piden a la vez desde nodos distintos que han perdido contacto entre sí:
| Elección | Comportamiento | Consecuencia para MercadoFresco |
|---|---|---|
| Consistencia (CP) | El nodo aislado rechaza la operación hasta recuperar contacto | Nadie vende de más; algunos clientes ven un error al añadir al carrito |
| Disponibilidad (AP) | Ambos nodos aceptan y se reconcilian después | Nadie ve errores; se pueden vender 4 cajas de 3 |
La respuesta correcta depende de la operación, no de la empresa:
- Confirmar el pedido y descontar stock: consistencia fuerte, sin discusión. Vender fresas que no existen significa llamar al cliente, reembolsar y perderlo. Va a Aurora, con transacciones.
- Mostrar «quedan pocas unidades» en la ficha: consistencia eventual, perfectamente aceptable. Que el contador vaya dos segundos retrasado no daña a nadie. Va a la caché.
- Guardar el carrito: consistencia eventual aceptable, con lectura fuerte solo en el paso final de compra. Va a DynamoDB, que permite pedir cada lectura de una u otra forma.
Esta distinción —consistencia por operación, no por sistema— es el concepto que más rendimiento libera en la práctica y el que más cuesta aceptar viniendo del mundo relacional.
Transacciones, esquema y coste
Transacciones. Pregunta si varias escrituras deben tener éxito o fracasar juntas. Confirmar un
pedido es descontar stock, crear el pedido, registrar el pago y generar la factura: o las cuatro o
ninguna. Eso pide un motor con ACID real y un BEGIN … COMMIT natural. DynamoDB tiene
TransactWriteItems con límites (hasta 100 elementos, sin lógica intermedia) y sirve para casos
acotados, no para un flujo de negocio completo.
Esquema. ¿Todas las filas tienen los mismos campos? El catálogo de MercadoFresco no: el pescado
tiene lonja de origen y talla mínima, el vino tiene denominación y añada. En relacional eso son
columnas nulas o tablas de atributos; en documental o clave-valor es natural. Pero esquema flexible
no significa sin esquema: significa que el esquema lo valida la aplicación en vez del motor, y si
nadie lo valida, acabas con precio como número en unos registros y como cadena en otros.
Coste. Los modelos son estructuralmente distintos y no se comparan mirando el precio por hora:
| Modelo | Se paga por | Bueno cuando | Malo cuando |
|---|---|---|---|
| Instancia (RDS, Aurora provisionada) | Horas de instancia, esté o no en uso | Carga estable y predecible | Carga con valles profundos |
| Sin servidor por capacidad (Aurora Serverless v2) | ACU-hora consumida | Carga variable con valle nocturno | Carga plana 24×7 |
| Por petición (DynamoDB bajo demanda) | Cada lectura y escritura | Tráfico irregular o impredecible | Volumen enorme y constante |
| Por consulta (Athena) | TB escaneados | Consultas esporádicas | Muchas consultas diarias |
| Por memoria (ElastiCache) | Horas de nodo | Conjunto caliente y acotado | Datos fríos y voluminosos |
Árbol de decisión
graph TD
A[Nueva carga de trabajo] --> B{¿Es analítica:<br/>agregar millones de filas?}
B -->|Sí| C{¿Consultas frecuentes<br/>o esporádicas?}
C -->|Frecuentes| D[Redshift]
C -->|Esporádicas sobre S3| E[Athena]
B -->|No| F{¿Se accede siempre<br/>por una clave conocida?}
F -->|No| G{¿Búsqueda de texto<br/>o relaciones profundas?}
G -->|Texto| H[OpenSearch]
G -->|Relaciones| I[Neptune]
G -->|Ninguna: SQL ad hoc| J[Aurora / RDS]
F -->|Sí| K{¿Puede perderse<br/>sin consecuencias?}
K -->|Sí, es una copia| L[ElastiCache]
K -->|No, es la verdad| M{¿Necesita transacciones<br/>de varias entidades?}
M -->|Sí| J
M -->|No| N[DynamoDB]
El árbol es una guía, no una ley. Su valor está en el orden de las preguntas: primero analítico o transaccional, luego acceso por clave o consulta libre, luego verdad o copia, y solo al final transacciones. Ese orden reproduce el impacto real de cada decisión.
OLTP frente a OLAP
La primera bifurcación del árbol merece desarrollarse, porque es la que explica el 80 % de los problemas de MercadoFresco.
| OLTP (transaccional) | OLAP (analítico) | |
|---|---|---|
| Pregunta típica | «Dame el pedido 84.213» | «Ticket medio por ciudad en 18 meses» |
| Filas tocadas | 1-100 | 10⁶-10⁹ |
| Columnas tocadas | Todas las de la fila | 3-6 de 40 |
| Latencia esperada | Milisegundos | Segundos o minutos |
| Concurrencia | Miles de sesiones | Decenas |
| Escrituras | Constantes y pequeñas | Cargas masivas periódicas |
| Almacenamiento | Por filas | Por columnas |
| Normalización | Alta (3FN) | Baja (estrella) |
| Servicio | Aurora, RDS, DynamoDB | Redshift, Athena |
Cuando Sara ejecuta su informe mensual en mercadofresco-pedidos está pidiéndole a un motor OLTP que
haga trabajo OLAP. PostgreSQL lo hace —es un buen motor— pero lee filas completas de 40 columnas para
usar 4, no comprime nada y no paraleliza. Que tarde 40 segundos y arrase el cache no es un fallo: es la
consecuencia previsible de usar la herramienta equivocada.
Normalización, desnormalización y el cambio de mentalidad de NoSQL
En relacional, normalizar —cada dato en un solo sitio, sin duplicar— es la forma correcta de
diseñar. Evita anomalías de actualización: si cambia el nombre de un producto, cambia en una fila y ya.
El precio de esa pureza es que reconstruir información completa exige JOIN en cada consulta.
En clave-valor y documental se desnormaliza deliberadamente: se duplica el dato para que una sola
lectura devuelva todo lo necesario, porque no hay JOIN y porque la lectura es la operación cara de
optimizar.
| Normalizado | Desnormalizado | |
|---|---|---|
| Dato duplicado | No | Sí, a propósito |
| Coste de lectura | Varios JOIN |
Una lectura |
| Coste de escritura | Una escritura | Varias escrituras coordinadas |
| Riesgo | Consultas lentas | Copias divergentes |
| Cambiar consultas nuevas | Fácil | Puede exigir remodelar |
El cambio de mentalidad que exige NoSQL se resume en cuatro inversiones respecto al hábito relacional:
- Se diseña desde las consultas, no desde las entidades.
- Duplicar no es un error, es la técnica.
- La integridad la garantiza la aplicación, no el motor.
- No se añaden consultas nuevas gratis: una consulta imprevista puede requerir un índice secundario nuevo o reescribir el modelo.
El fallo clásico —y lo veremos en 06-02— es llevar DynamoDB a la mentalidad relacional: una tabla por
entidad, Scan para buscar y unión manual en el código. El resultado es más lento y más caro que
PostgreSQL, y a menudo la conclusión errónea es «DynamoDB no sirve».
Las cuatro cargas de MercadoFresco, medidas
Estos son los datos reales que el módulo 5 dejó sobre la mesa. Son ficticios, pero son el tipo de cifra que hay que tener antes de decidir.
Carga A: pedidos y stock
- 900 pedidos/hora en el pico del viernes; unos 4.000 al día.
- Cada confirmación son 6 escrituras en 4 tablas, dentro de una transacción.
- Consultas exploratorias frecuentes de atención al cliente: «pedidos de este cliente en marzo».
- Volumen: 340 GB, +12 GB/mes.
- Pérdida de un dato: inaceptable. Consistencia: fuerte.
Carga B: carrito y sesiones
- 180.000 escrituras/día en
sesiones; el 92 % son actualizaciones del mismo registro. - Acceso siempre por
id_clienteoid_sesion. Cero consultas exploratorias. - La tabla ha crecido a 78 GB de los que el 71 % son carritos abandonados de hace más de 60 días.
- El
VACUUMde esa tabla es la operación que más E/S consume de toda la instancia. - Pérdida de un dato: molesta, no grave. Consistencia: eventual salvo en el paso de pago.
Carga C: catálogo
- 4.100 lecturas/minuto en hora punta; el 94 % de las consultas devuelven exactamente el mismo resultado que la anterior.
- Los precios y descripciones cambian una vez al día, a las 06:00, en la carga de la lonja.
- Volumen: 1,2 GB. Cabe entero en memoria varias veces.
- Latencia objetivo: <5 ms. Consistencia: eventual, con margen de minutos.
Carga D: informes
- 3-8 informes al día de Sara, con picos a fin de mes.
- Cada uno agrega entre 8 y 18 meses: de 6 a 40 millones de filas.
- Usa 4-6 columnas de las 40 disponibles.
- Duración actual: de 40 s a 6 min, con impacto medido en la latencia de la tienda.
- Consistencia: los datos de ayer sirven perfectamente.
La decisión razonada, carga por carga
| Carga | Patrón dominante | Motor elegido | Razón decisiva | Lección |
|---|---|---|---|---|
| A. Pedidos y stock | OLTP transaccional, SQL ad hoc | Aurora PostgreSQL | Transacciones ACID y consultas libres; compatible sin reescribir | 06-03 |
| B. Carrito y sesiones | Clave-valor, alta escritura, datos efímeros | DynamoDB | Acceso por clave, escala de escritura y TTL que borra solo | 06-02 |
| C. Catálogo | Lectura repetida idéntica | ElastiCache (Redis) | Latencia <1 ms y descarga del 94 % de las lecturas | 06-05 |
| D. Informes | OLAP, agregación masiva | Redshift Serverless | Columnar y MPP; y sobre todo, fuera de producción | 06-04 |
Vale la pena hacer explícito lo que no se elige y por qué, porque una decisión sin alternativas descartadas no es una decisión:
- El carrito no va a ElastiCache aunque sea rápido: es datos del cliente que no queremos perder en una conmutación por error, y necesitamos que caduque de forma auditable. Redis lo haría; DynamoDB con TTL lo hace además con durabilidad de disco y sin dimensionar memoria.
- Los pedidos no van a DynamoDB: atención al cliente hace consultas que nadie previó, y la confirmación de pedido es una transacción de cuatro entidades con lógica intermedia.
- Los informes no se resuelven con la réplica de lectura: la réplica quita el bloqueo, pero sigue leyendo por filas, sin comprimir y sin paralelizar. Pasar de 40 s a 35 s no es la solución.
- El catálogo no se arregla con más CPU en la base de datos: el problema no es que la consulta sea lenta, es que se hace 4.100 veces por minuto para devolver lo mismo.
Y una nota de orden importante: primero Aurora, luego el resto. Migrar el motor principal antes de repartir cargas evita hacer dos migraciones sobre el mismo dato. En la práctica, MercadoFresco ejecutará DynamoDB (06-02) y Aurora (06-03) en paralelo porque tocan datos disjuntos, y Redshift (06-04) y ElastiCache (06-05) después, porque ambos leen de lo anterior.
Cómo se migra sin parar la tienda
Ninguna de estas migraciones puede hacerse con un corte de servicio largo. Hay tres estrategias, y se combinan.
Doble escritura
La aplicación escribe en el almacén antiguo y en el nuevo a la vez, lee del antiguo, y cuando el nuevo lleva semanas coincidiendo, se cambia la lectura.
sequenceDiagram
participant App as Tienda
participant PG as PostgreSQL (antiguo)
participant DDB as DynamoDB (nuevo)
Note over App: Fase 1 · escritura doble, lectura antigua
App->>PG: escribir carrito
App->>DDB: escribir carrito
App->>PG: leer carrito
Note over App: Fase 2 · lectura nueva con reserva
App->>DDB: leer carrito
alt no encontrado
App->>PG: leer carrito (reserva)
end
Note over App: Fase 3 · solo el nuevo
App->>DDB: leer y escribir
Sus tres trampas: hay que decidir qué pasa si la segunda escritura falla (lo correcto casi siempre es registrar el error y seguir, no romper el pedido); hay que migrar el histórico aparte, porque la doble escritura solo cubre lo nuevo; y hay que comparar ambos almacenes con un proceso automático antes de fiarse.
Patrón estrangulador
En lugar de migrar todo de golpe, se intercepta una funcionalidad cada vez y se redirige al sistema
nuevo, hasta que el antiguo se queda sin trabajo y se apaga. Es el patrón que sigue este módulo: el
carrito sale primero, luego los informes, luego el catálogo, y mercadofresco-pedidos acaba haciendo
solo aquello para lo que era bueno.
Corte controlado
Para cargas que toleran una ventana —los informes, por ejemplo— basta con exportar, cargar y cambiar el destino. Es la más simple y hay que usarla siempre que se pueda: la complejidad de la doble escritura solo se justifica cuando el corte es inaceptable.
| Estrategia | Corte | Complejidad | Reversión | Cuándo usarla |
|---|---|---|---|---|
| Corte controlado | Minutos u horas | Baja | Fácil | Informes, cargas internas |
| Doble escritura | Cero | Alta | Media | Carrito, sesiones |
| Estrangulador | Cero | Media | Fácil por función | Migración por fases |
| Réplica promovida | 1-2 minutos | Media | Difícil tras promover | Cambio de motor compatible |
AWS DMS y SCT: las herramientas de migración
AWS Database Migration Service (DMS) copia datos entre almacenes y, lo importante, puede replicar los cambios de forma continua (CDC) después de la carga inicial: el origen sigue en producción mientras el destino se mantiene sincronizado. Admite orígenes y destinos heterogéneos —PostgreSQL a Aurora, PostgreSQL a DynamoDB, PostgreSQL a Redshift o a S3—, que es exactamente el abanico de este módulo.
# Estructura de una tarea de DMS: instancia de réplica, dos endpoints y una tarea.
# 1) La instancia de réplica vive en la VPC, en las subredes de datos.
aws dms create-replication-instance \
--replication-instance-identifier dms-mercadofresco \
--replication-instance-class dms.t3.medium \
--replication-subnet-group-identifier sng-mercadofresco-datos \
--no-publicly-accessible \
--tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
Key=Componente,Value=migracion Key=Propietario,Value=marta \
Key=CentroCoste,Value=plataforma \
--region eu-west-1 --profile mercadofresco-dev
# 2) El endpoint de origen apunta a la RDS actual. La contraseña NO se escribe aquí:
# se referencia el secreto de Secrets Manager creado en 04-03.
aws dms create-endpoint \
--endpoint-identifier origen-mercadofresco-pedidos \
--endpoint-type source --engine-name postgres \
--secrets-manager-secret-id mercadofresco/produccion/rds/mfadmin \
--secrets-manager-access-role-arn arn:aws:iam::111122223333:role/rol-dms-secretos \
--database-name pedidos \
--region eu-west-1 --profile mercadofresco-devTres detalles que hacen que la tarea funcione o no:
dms.t3.mediumes la instancia de réplica, no el destino. Se paga por hora mientras la tarea vive y hay que borrarla al terminar: es el olvido más caro de una migración.- El tipo de tarea
full-load-and-cdchace carga completa y luego cambios continuos. Requiere que el origen tengards.logical_replicationactivado, lo que obliga a reiniciar la instancia: hay que planificarlo antes, no descubrirlo el día de la migración. - DMS no migra índices, procedimientos ni restricciones por defecto: copia datos. El esquema se prepara aparte.
AWS Schema Conversion Tool (SCT) traduce el esquema y el código —vistas, funciones, procedimientos— entre motores distintos, y genera un informe de lo que no puede convertir automáticamente. Para MercadoFresco, que va de PostgreSQL a Aurora PostgreSQL, no hace falta: el motor es el mismo. SCT es la herramienta de las migraciones heterogéneas, típicamente Oracle o SQL Server hacia PostgreSQL.
Aviso de coste y limpieza. Una migración deja residuos caros: instancias de réplica de DMS, snapshots manuales, clústeres de destino en pruebas y buckets intermedios. Antes de cerrar cualquier migración, revisa la etiqueta
Componente=migracionen Cost Explorer (11-03) y borra todo lo que la lleve. Y no borres el origen hasta tener al menos una restauración probada del destino.
Errores Comunes y Consejos
Elegir por moda. «Usemos DynamoDB porque escala.» ¿Escala qué, exactamente, y desde qué cifra? Si una carga hace 40 escrituras por segundo, PostgreSQL la aguanta con una mano atada. La pregunta no es si un motor escala, sino si tu carga necesita esa escala. La moda cuesta cara porque el coste no es la factura: es el equipo aprendiendo un modelo nuevo mientras hay incidencias que atender.
Migrar sin medir antes. Sin la línea base del módulo 5 —latencia p99, IOPS, aciertos de caché, duración de los informes— no se puede demostrar que la migración mejoró nada. Y si no se puede demostrar, la siguiente propuesta de arquitectura no se aprueba. Captura las métricas la semana antes, no el día después.
Usar NoSQL con mentalidad relacional. Una tabla de DynamoDB por entidad, Scan para buscar y
uniones en el código de la aplicación. Es lento, caro y frágil. Si no puedes escribir la lista de
consultas que la tabla debe servir, no estás listo para diseñarla.
Confundir «puede» con «debe». PostgreSQL tiene tipo JSONB, búsqueda de texto completo, tipos
geoespaciales y hasta extensiones vectoriales. Puede hacer casi todo. Eso no significa que deba: la
pregunta es si lo hace suficientemente bien para tu volumen. Con 1,2 GB de catálogo, la búsqueda de
texto de PostgreSQL sobra; con 300 GB y facetas, no.
Añadir un motor sin plan de operación. Cada almacén nuevo necesita: copias de seguridad probadas,
alarmas en CloudWatch, política de IAM de mínimo privilegio, cifrado con alias/mercadofresco-datos,
etiquetas completas y alguien que sepa restaurarlo a las tres de la mañana. Si no puedes comprometerte
a esas seis cosas, no lo añadas.
Olvidar que ahora hay dos verdades. En cuanto el carrito vive en DynamoDB y el pedido en Aurora, ya
no hay JOIN ni transacción que los abarque. Hay que decidir explícitamente qué pasa si una de las dos
escrituras falla. El módulo 7 trata precisamente de eso.
Consejo: escribe la decisión. Un documento de una página por carga —patrón medido, opciones consideradas, motor elegido, criterio decisivo, cómo se medirá el éxito— es lo que convierte una migración en ingeniería. Dentro de un año, cuando alguien pregunte por qué el carrito está en DynamoDB, esa página vale más que la memoria de nadie.
Ejercicios
Ejercicio 1: clasificar cinco cargas nuevas
MercadoFresco planea cinco funcionalidades. Para cada una, indica familia de base de datos, servicio de AWS y el criterio decisivo (uno solo, el que más pesa):
- Trazabilidad del fresco. Cada lote de pescado debe poder demostrar ante Sanidad su recorrido desde la lonja. 400 lotes/día, consultas rarísimas pero legalmente obligatorias, y el histórico no puede alterarse.
- Temperatura de las furgonetas. 30 vehículos envían la temperatura del compartimento cada 10 segundos. Se consulta la curva de las últimas 24 horas y se conservan 2 años por normativa.
- Buscador de la tienda. Los clientes escriben «pimient», «sarmon» o «queso curado oveja» y esperan resultados relevantes, con filtros por categoría, alérgenos y rango de precio.
- «Los clientes como tú también compraron». Recomendación basada en compras conjuntas, hasta tres saltos de distancia entre clientes y productos.
- Notificaciones enviadas. Registro de cada correo y SMS enviado: 50.000/día, se consulta solo por
id_clientepara atención al cliente, y se borra a los 90 días.
Ejercicio 2: aplicar CAP a una decisión concreta
MercadoFresco quiere lanzar «últimas unidades»: cuando quedan menos de 5 unidades de un producto, la ficha muestra un contador en vivo. El equipo propone dos diseños:
- Diseño A: la ficha consulta el stock real en la base de datos transaccional en cada carga.
- Diseño B: el stock se publica en una caché con TTL de 30 segundos, y la ficha lee de ahí.
Responde: (a) ¿qué elige cada diseño en términos de CAP y qué se sacrifica?; (b) con 4.100 lecturas por minuto en hora punta, ¿qué carga añade cada uno a la base de datos?; (c) ¿qué le pasa a un cliente que añade al carrito la última caja de fresas en el diseño B?; (d) propón un diseño C que combine ambos y di exactamente en qué punto del flujo de compra se necesita la lectura fuerte.
Ejercicio 3: plan de migración del carrito
Escribe el plan de migración de la tabla sesiones (78 GB, 180.000 escrituras/día) de PostgreSQL a
DynamoDB, sin corte de servicio. Debe incluir: estrategia elegida y por qué; las fases con su
criterio de salida medible; qué se hace con los 55 GB de carritos abandonados de más de 60 días; cómo
se verifica que ambos almacenes coinciden; el plan de reversión en cada fase; y las tres métricas del
módulo 5 con las que demostrarás ante Marta que la migración mereció la pena.
Soluciones
Solución 1
| # | Familia | Servicio | Criterio decisivo |
|---|---|---|---|
| 1 | Relacional con registro inmutable | Aurora PostgreSQL con tablas de solo inserción | Verificabilidad ante un tercero; QLDB está en fin de soporte, así que la vía actual es solo-inserción más firma y trail-mercadofresco como respaldo de auditoría |
| 2 | Series temporales | Amazon Timestream | Retención por antigüedad: 259.200 puntos/día que se consultan casi siempre en ventana reciente y luego solo se conservan; el ciclo de vida memoria→magnético lo hace el motor |
| 3 | Búsqueda | Amazon OpenSearch Service | Tolerancia a erratas y facetas: «sarmon» debe devolver «salmón», y eso no lo da un LIKE |
| 4 | Grafos | Amazon Neptune | Recorridos de profundidad variable: tres saltos en SQL son JOIN recursivos que crecen exponencialmente |
| 5 | Clave-valor | DynamoDB | Acceso solo por clave y caducidad: patrón idéntico al carrito; TTL a 90 días resuelve el borrado sin proceso alguno |
Matiz importante para el caso 1: aunque técnicamente sea trazabilidad de alimentos, el volumen es
ridículo (400 lotes/día). No justifica un motor nuevo. La respuesta correcta operativamente es
resolverlo en Aurora con una tabla de solo inserción y disparadores que impidan UPDATE y DELETE.
Ese es justamente el consejo de la lección: no añadas un motor si puedes nombrar por qué no.
Solución 2
(a) CAP. El diseño A elige consistencia: el dato mostrado es siempre el real, a costa de que cada visita a una ficha dependa de la disponibilidad y la latencia de la base de datos. El diseño B elige disponibilidad y latencia: la ficha responde en menos de un milisegundo aunque la base de datos esté saturada, a costa de mostrar un valor con hasta 30 segundos de antigüedad. Lo que se sacrifica en B es exactitud momentánea; lo que se sacrifica en A es rendimiento y aislamiento de fallos.
(b) Carga añadida. Diseño A: 4.100 consultas por minuto adicionales, unas 68 por segundo, sobre la instancia que ya es el cuello de botella. Diseño B: con TTL de 30 segundos y aunque cada producto se consulte constantemente, la base de datos recibe como mucho 2 consultas por minuto y producto; para un catálogo de 600 productos activos, unas 1.200 por minuto en el peor caso, y en la práctica muchísimo menos porque solo se refrescan los productos que alguien mira. La reducción es de uno a dos órdenes de magnitud.
(c) El caso de la última caja. El cliente ve «quedan 2», añade al carrito y llega al pago. Entre su lectura y su compra, otro cliente se llevó las dos. En el diseño B el error no debe aparecer al añadir al carrito, sino en la confirmación del pedido, donde una transacción con consistencia fuerte descuenta stock y falla si no hay existencias. La experiencia correcta es un mensaje claro —«se han agotado mientras completabas el pedido»— y la sugerencia de un producto alternativo. Nunca se debe confirmar un pedido leyendo de la caché.
(d) Diseño C. Lectura eventual desde caché para mostrar (ficha, listados, buscador) y lectura y
escritura fuertes dentro de una transacción para comprometer (el UPDATE stock SET unidades = unidades - :n WHERE id = :p AND unidades >= :n en la confirmación del pedido, comprobando que afectó a
una fila). El punto exacto donde se necesita la lectura fuerte es la confirmación del pedido, no el
carrito ni la ficha. Este es el patrón general: eventual para leer, fuerte para decidir.
Solución 3
Estrategia: doble escritura combinada con estrangulador. El corte no es aceptable —un carrito perdido es una venta perdida— y la carga es de escritura continua, así que el corte controlado queda descartado. El estrangulador ordena las fases por funcionalidad; la doble escritura garantiza que no se pierde nada durante la transición.
Fases y criterios de salida:
| Fase | Qué se hace | Criterio de salida medible |
|---|---|---|
| 0 | Modelar la tabla mercadofresco-carritos a partir de las consultas reales extraídas de los registros |
Lista cerrada de consultas; ninguna requiere Scan |
| 1 | Escritura doble; lectura solo de PostgreSQL; errores en DynamoDB se registran pero no rompen | 7 días con tasa de error de escritura en DynamoDB <0,01 % |
| 2 | Migrar el histórico útil con DMS en modo carga completa | 100 % de carritos activos de los últimos 30 días presentes en ambos |
| 3 | Lectura desde DynamoDB con reserva a PostgreSQL si no se encuentra | 7 días con reservas <0,1 % de las lecturas |
| 4 | Quitar la reserva y la escritura en PostgreSQL | 7 días sin incidencias; TiempoConfirmacionPedido p99 estable o mejor |
| 5 | Eliminar la tabla sesiones tras copia final |
Copia verificada en mercadofresco-copias-basedatos |
Los 55 GB de carritos abandonados: no se migran. Migrarlos costaría escrituras y almacenamiento para datos que nadie va a consultar. Se exportan a S3 en Parquet como archivo histórico (por si Sara quiere analizar el abandono de carrito en Redshift, 06-04) y se descartan del destino. La tabla nueva nace con TTL de 30 días, de modo que el problema no puede reproducirse. Este es el punto más importante del ejercicio: una migración es la única ocasión barata de no arrastrar la basura.
Verificación: un proceso diario que toma una muestra aleatoria de 1.000 claves de PostgreSQL, las
busca en DynamoDB y compara campo a campo, publicando una métrica personalizada
MercadoFresco/Migracion/DiscrepanciasCarrito en el espacio MercadoFresco/Tienda, con alarma a
alertas-mercadofresco si supera 5 en 24 horas. Comparar la muestra, no el total: comparar 78 GB
diarios cuesta más que la migración.
Reversión: en las fases 1 y 2 es inmediata porque PostgreSQL sigue siendo la fuente de lectura
—basta desactivar la escritura doble—. En la fase 3 es una bandera de configuración en Parameter Store
(/mercadofresco/produccion/carrito/origen) que se cambia sin desplegar. A partir de la fase 4 la
reversión ya no es trivial: por eso la fase 4 no empieza hasta acumular siete días limpios en la 3.
Las tres métricas: (1) TiempoConfirmacionPedido p99 del espacio MercadoFresco/Tienda, que debe
bajar al desaparecer las escrituras de sesión de la instancia; (2) WriteIOPS y CPUUtilization de
mercadofresco-pedidos, que deben caer al perder 180.000 escrituras diarias y el VACUUM asociado; y
(3) el coste mensual con etiqueta Componente=basedatos en Cost Explorer, comparando el mes anterior
con el posterior. Las tres estaban ya instrumentadas desde el módulo 5: por eso esta migración se puede
defender con datos.
Conclusión
Esta lección no ha creado ni un recurso, y es probablemente la más importante del módulo. Sabes por qué
una base de datos para todo acaba fallando —interferencia de rendimiento a través del cache
compartido, acoplamiento de disponibilidad y escalado del mínimo común múltiplo— y conoces la
persistencia políglota con sus contrapartidas honestas: más servicios que operar, más superficie de
seguridad y la desaparición del JOIN entre almacenes.
Tienes el mapa de las nueve familias —relacional, clave-valor, documental, en memoria, columnar, series temporales, grafos, búsqueda y libro mayor— con su servicio de AWS y el caso de MercadoFresco al que aplicaría, y sabes que solo cuatro entran en este módulo porque las otras cinco no tienen todavía una carga que las justifique.
Y sobre todo tienes el orden correcto de las preguntas: primero el patrón de acceso —no el modelo de datos—, luego volumen y latencia en percentiles, luego si las consultas se conocen de antemano o son exploratorias, luego consistencia decidida por operación y no por sistema —eventual para mostrar que quedan pocas fresas, fuerte para descontarlas—, y solo al final transacciones, esquema y coste. Con la distinción OLTP frente a OLAP como primera bifurcación, y con el cambio de mentalidad que exige NoSQL: se diseña desde las consultas, duplicar es la técnica y la integridad la garantiza la aplicación.
La decisión queda tomada y es la hoja de ruta del módulo: DynamoDB para el carrito y las sesiones, por acceso exclusivo por clave, escala de escritura y un TTL que resuelve los 55 GB de basura; Aurora para pedidos y stock, por transacciones ACID y consultas exploratorias sin reescribir nada; Redshift Serverless para los informes de Sara, por columnar, masivamente paralelo y, sobre todo, fuera de producción; y ElastiCache para el catálogo, porque el problema nunca fue que la consulta fuera lenta sino que se repetía 4.100 veces por minuto para devolver lo mismo. Con las estrategias para llegar hasta ahí sin parar la tienda: doble escritura, patrón estrangulador y corte controlado cuando se pueda, con DMS y su replicación continua como herramienta y SCT reservado para las migraciones heterogéneas que MercadoFresco no necesita.
Empezamos a ejecutar por la carga más independiente y la que más alivia a la instancia actual. En
06-02, «Amazon DynamoDB», sacaremos el carrito y las sesiones de PostgreSQL: modelaremos la tabla
mercadofresco-carritos desde sus consultas y no desde sus entidades, veremos claves de partición y de
ordenación, diseño de tabla única, Query frente a Scan, índices secundarios, capacidad bajo demanda
frente a aprovisionada con las cuentas del pico del viernes, y el TTL que hará que los 55 GB de
carritos abandonados sean un problema que ya no puede volver a existir.
Curso de AWS
Módulo 1: Introducción a AWS
- ¿Qué es AWS?
- Configuración de tu cuenta de AWS
- Infraestructura global de AWS
- Consola de administración de AWS
- AWS CLI y SDKs
Módulo 2: Servicios principales de AWS
Módulo 3: Redes y entrega de contenido
- Amazon VPC
- Grupos de seguridad y listas de control de acceso
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Módulo 4: Seguridad e identidad
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager y Parameter Store
- AWS Shield
- AWS WAF
Módulo 5: Monitorización y gestión
- Amazon CloudWatch
- AWS X-Ray y trazabilidad distribuida
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Módulo 6: Bases de datos
- Cómo elegir la base de datos adecuada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Módulo 7: Integración de aplicaciones
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrones de integración: idempotencia, reintentos y colas de mensajes fallidos
