Queda la última de las cuatro cargas que diagnosticamos en 06-01, y es la más absurda de todas. El catálogo de MercadoFresco recibe 4.100 consultas por minuto en hora punta y, según las trazas de X-Ray de 05-02, el 94 % devuelven exactamente el mismo resultado que la anterior. Los precios y las descripciones cambian una vez al día, a las 06:00, cuando llega la carga de la lonja.
Aurora resuelve cada una de esas consultas correctamente en unos 6 milisegundos. El problema no es que sea lenta: es que hacerlo 4.100 veces por minuto para no aportar ninguna información nueva es trabajo desperdiciado, y ese desperdicio se paga tres veces —en latencia para el cliente, en capacidad de Aurora que hay que pagar aunque no produzca nada, y en riesgo, porque esa carga constante deja al clúster sin margen justo cuando llega el pico del viernes—.
Amazon ElastiCache es el servicio de caché en memoria gestionado de AWS, compatible con Redis (y su bifurcación de código abierto Valkey) y con Memcached. Aquí el catálogo se sirve desde memoria en microsegundos, las sesiones dejan de necesitar sesiones pegajosas en el ALB —el cabo suelto que 03-03 dejó abierto— y el módulo se cierra con la capa de datos de MercadoFresco completa.
Aviso de coste. Un nodo de ElastiCache se paga por hora exista o no tráfico, igual que una instancia EC2. Un
cache.r7g.largeolvidado cuesta unos 130 USD al mes. Borra los grupos de réplica que crees para practicar. Todos los datos son ficticios.
Contenido
- Por qué cachear, y qué no se debe cachear
- Redis frente a Memcached
- Arquitectura: nodos, grupos de réplica y modo clúster
- Particionamiento, ranuras hash, conmutación por error y puntos de enlace
- Patrones de caché: cache-aside, write-through, write-behind y read-through
- El catálogo de MercadoFresco con cache-aside
- TTL, invalidación y coherencia
- Estampida de caché y claves calientes
- Estructuras de Redis aplicadas a la tienda
- Contadores atómicos y el límite del stock
- Sesiones en Redis: adiós a las sesiones pegajosas
- Seguridad: red, cifrado y control de acceso
- Métricas, alarmas y la medición del antes y el después
- MemoryDB y DAX: cuándo no es ElastiCache
- Costes y ahorro medido
- Errores comunes y consejos
- Ejercicios
- La capa de datos completa de MercadoFresco
- Conclusión
Por qué cachear, y qué no se debe cachear
Una caché guarda en memoria el resultado de una operación cara para no repetirla. Aporta tres cosas distintas y conviene separarlas:
| Beneficio | Antes | Después |
|---|---|---|
| Latencia | 6 ms por consulta a Aurora | 0,3 ms desde memoria |
| Coste por consulta | Capacidad de Aurora (ACU) | Nodo fijo, coste marginal casi nulo |
| Protección | Todo el tráfico llega a la base de datos | La base solo ve los fallos de caché |
El tercero es el que más se subestima. Una caché con un 95 % de aciertos convierte 4.100 consultas por minuto en 205: la base de datos deja de estar al límite y recupera el margen que necesita para el pico.
Qué se debe cachear, en orden de rentabilidad: datos que se leen mucho más de lo que se escriben (el catálogo, 4.100:1), resultados de cálculos caros, datos que toleran estar ligeramente desactualizados y objetos que se reconstruyen a partir de varias fuentes. Qué no se debe cachear: datos que cambian en cada lectura, datos cuya exactitud momentánea es crítica —el stock al cobrar—, datos que solo se leen una vez (una búsqueda con filtros muy específicos que nadie repetirá) y datos personales sensibles sin una razón clara, porque una caché es una copia más que proteger y que borrar bajo el RGPD.
Redis frente a Memcached
| Redis / Valkey | Memcached | |
|---|---|---|
| Estructuras de datos | Cadenas, hashes, listas, conjuntos, conjuntos ordenados, flujos | Solo cadenas |
| Persistencia | Sí (instantáneas y AOF) | No |
| Réplicas | Sí, con conmutación automática | No |
| Alta disponibilidad | Multi-AZ con conmutación | Ninguna |
| Particionamiento | Nativo, con modo clúster | En el cliente |
| Operaciones atómicas | Muchas (INCR, ZADD, transacciones, scripts Lua) |
Pocas |
| Publicación y suscripción | Sí | No |
| Modelo de hilos | Un hilo por núcleo lógico (mayormente monohilo) | Multihilo |
| Casos ideales | Casi todos | Caché pura, objetos grandes, multihilo |
La elección para MercadoFresco es Redis, por tres razones concretas y no por popularidad. Necesitamos estructuras y no solo cadenas: conjuntos ordenados para el ranking del viernes, hashes para el carrito rápido, contadores atómicos. Necesitamos alta disponibilidad: si la caché de sesiones cae, todos los clientes pierden la sesión a la vez, y con Memcached no hay réplica posible. Y necesitamos operaciones atómicas para los contadores, que en Memcached son limitadas.
Memcached sigue teniendo su nicho —caché puramente volátil, muy simple, con objetos grandes y donde el multihilo aporta más rendimiento por nodo—, pero no es este caso. Sobre Valkey: es la bifurcación de código abierto de Redis tras su cambio de licencia, compatible a nivel de protocolo, con soporte de ElastiCache y a menor precio. Para un despliegue nuevo es la opción por defecto razonable, y lo que se explica aquí aplica igual.
Arquitectura: nodos, grupos de réplica y modo clúster
Un nodo es la unidad mínima: una instancia con memoria y un proceso de Redis. Un fragmento (shard) es un nodo primario más de 0 a 5 réplicas con los mismos datos. Y un grupo de réplica es el conjunto de fragmentos que forman el despliegue. De ahí las dos topologías posibles:
graph TD
subgraph MCD["Modo clúster DESACTIVADO · 1 fragmento"]
P1["Primario<br/>todo el conjunto de datos"] --> R1["Réplica AZ b"]
P1 --> R2["Réplica AZ a"]
end
subgraph MCA["Modo clúster ACTIVADO · 3 fragmentos"]
F1["Fragmento 1<br/>ranuras 0-5460"] --> FR1["Réplica"]
F2["Fragmento 2<br/>ranuras 5461-10922"] --> FR2["Réplica"]
F3["Fragmento 3<br/>ranuras 10923-16383"] --> FR3["Réplica"]
end
| Modo clúster desactivado | Modo clúster activado | |
|---|---|---|
| Fragmentos | 1 | Hasta 500 |
| Límite de datos | La memoria de un nodo | La suma de todos los fragmentos |
| Escalado | Vertical (nodo mayor) | Horizontal (más fragmentos) |
| Cliente | Cualquiera | Debe soportar el protocolo de clúster |
| Operaciones multiclave | Sin restricción | Solo dentro del mismo fragmento |
MercadoFresco empieza con el modo clúster desactivado. El catálogo son 1,2 GB y las sesiones no
llegan a 2 GB: todo cabe holgadamente en un nodo cache.t4g.medium de 3,09 GiB, con una réplica en la
otra AZ. Activar el modo clúster añadiría complejidad —restricciones en operaciones multiclave, cliente
compatible— sin resolver ningún problema que hoy exista. Es exactamente el criterio de 06-01: no añadas
complejidad que no puedas justificar con una medición.
Particionamiento, ranuras hash, conmutación por error y puntos de enlace
Con el modo clúster activado, Redis divide el espacio de claves en 16.384 ranuras hash. Cada clave
se asigna a una ranura calculando CRC16(clave) mod 16384, y cada fragmento posee un rango de ranuras.
Añadir un fragmento redistribuye ranuras sin parar el servicio.
Las etiquetas hash fuerzan que varias claves caigan en la misma ranura poniendo entre llaves la
parte que se usa para el cálculo: carrito:{4471}:lineas y carrito:{4471}:total van juntas porque
ambas hashean solo 4471. Es lo que permite operar sobre varias claves relacionadas en un clúster
particionado.
Multi-AZ con conmutación automática promueve una réplica si el primario falla, en 15-60 segundos y actualizando el punto de enlace principal solo. Hay que configurarlo explícitamente y tener claro qué implica: si la persistencia no está activada, lo que hubiera solo en el primario se pierde. Para una caché es aceptable —se repuebla desde Aurora—; para sesiones no lo es tanto, y por eso MercadoFresco activa réplicas y persistencia en el grupo de sesiones.
Los puntos de enlace disponibles son cuatro:
| Punto de enlace | Cuándo existe | Uso |
|---|---|---|
| Principal | Modo clúster desactivado | Escrituras y lecturas que exigen el último dato |
| De lectura | Modo clúster desactivado | Reparte lecturas entre réplicas |
| De configuración | Modo clúster activado | El único que usa la aplicación; el cliente descubre la topología |
| De nodo | Siempre | Diagnóstico; nunca en la aplicación |
Igual que con Aurora, usar el punto de enlace de nodo en la aplicación es el error que se paga en la primera conmutación por error.
Patrones de caché: cache-aside, write-through, write-behind y read-through
| Patrón | Quién escribe en la caché | Ventaja | Inconveniente |
|---|---|---|---|
| Cache-aside (carga diferida) | La aplicación, tras un fallo de lectura | Solo se cachea lo que se usa; resistente a fallos de caché | Primer acceso lento; puede haber datos obsoletos |
| Write-through | La aplicación, al escribir | La caché siempre está al día | Se cachea todo, se use o no; encarece la escritura |
| Write-behind | La caché, de forma diferida | Escrituras muy rápidas | Riesgo de pérdida de datos; complejo |
| Read-through | La propia caché, transparente | Código de aplicación limpio | Requiere una capa que lo implemente |
MercadoFresco usa cache-aside para el catálogo —el patrón por defecto y el más robusto: si la caché desaparece, la aplicación sigue funcionando, solo que más lenta— combinado con write-through en la carga de la lonja de las 06:00, que actualiza la caché a la vez que la base de datos para que el primer cliente de la mañana no la encuentre vacía. Write-behind se descarta explícitamente: escribir primero en memoria y volcar después a Aurora significa que un fallo del nodo pierde pedidos.
El catálogo de MercadoFresco con cache-aside
import json, hashlib, random
import redis
from redis.exceptions import RedisError
# decode_responses=True devuelve str en lugar de bytes: más cómodo con JSON.
# socket_timeout bajo es esencial: si la caché no responde, hay que ir a la base
# de datos rápido, no bloquear la petición del cliente.
cache = redis.Redis(
host="mercadofresco-catalogo.abc123.ng.0001.euw1.cache.amazonaws.com",
port=6379, ssl=True, decode_responses=True,
socket_timeout=0.15, socket_connect_timeout=0.15,
health_check_interval=30,
)
TTL_BASE = 3600 # 1 hora; los precios cambian una vez al día
def clave_producto(sku: str) -> str:
# Un prefijo con versión permite invalidar todo el catálogo cambiando "v3".
return f"catalogo:v3:producto:{sku}"
def obtener_producto(sku: str, conexion_aurora) -> dict:
clave = clave_producto(sku)
# 1. Intentar la caché. Cualquier fallo de Redis NO debe romper la tienda.
try:
crudo = cache.get(clave)
if crudo is not None:
return json.loads(crudo) # acierto de caché: ~0,3 ms
except RedisError:
pass # se registra y se sigue
# 2. Fallo de caché: leer de Aurora (la fuente de verdad).
with conexion_aurora.cursor() as cur:
cur.execute("""
SELECT p.sku, p.nombre, p.descripcion, p.precio, p.categoria,
p.origen, p.alergenos, p.unidad_venta
FROM productos p
WHERE p.sku = %s AND p.activo = true
""", (sku,))
fila = cur.fetchone()
if fila is None:
return None
producto = dict(zip(
["sku", "nombre", "descripcion", "precio", "categoria",
"origen", "alergenos", "unidad_venta"], fila))
# 3. Poblar la caché con TTL aleatorizado (ver estampida más abajo).
try:
ttl = TTL_BASE + random.randint(-300, 300)
cache.setex(clave, ttl, json.dumps(producto, default=str))
except RedisError:
pass
return productoCinco decisiones deliberadas en ese código:
- Los fallos de Redis nunca rompen la petición. Un
try/exceptalrededor de cada operación y un tiempo de espera de 150 ms. Una caché caída debe degradar el rendimiento, jamás la disponibilidad: es el error más grave que se comete al introducir una caché. - La clave lleva versión (
catalogo:v3:). Cambiar el número invalida todo el catálogo de golpe sin recorrer claves, que es lo que hay que hacer cuando cambia el formato del objeto cacheado. setexen vez deset+expire. Una sola operación atómica; con dos, un fallo intermedio deja una clave sin caducidad, y esas claves inmortales llenan la caché.- El TTL se aleatoriza ±5 minutos para evitar que todo caduque a la vez.
- Se serializa a JSON, que es legible y depurable. Con volumen alto, los formatos binarios ahorran memoria y CPU, pero conviene medirlo antes de complicarlo.
TTL, invalidación y coherencia
Toda caché tiene el mismo problema de fondo: el dato cacheado puede diferir del real. Hay tres formas de gestionarlo y se combinan: la caducidad por TTL (la clave expira sola, incoherencia hasta el TTL, complejidad mínima), la invalidación explícita (al escribir se borra la clave, incoherencia casi cero) y el write-through (al escribir se actualiza la clave, incoherencia cero). En MercadoFresco la política es explícita y por tipo de dato:
| Dato | TTL | Invalidación | Justificación |
|---|---|---|---|
| Ficha de producto | 1 h ± 5 min | Sí, al cambiar precio | Cambia una vez al día |
| Listado de categoría | 15 min | No | Cambia poco, tolera desfase |
| Stock aproximado | 30 s | No | «Quedan pocas unidades» tolera desfase |
| Ranking de más vendidos | 5 min | No | Es informativo |
| Sesión de usuario | 30 min deslizante | Sí, al cerrar sesión | Seguridad |
La invalidación explícita en la carga de la lonja:
def actualizar_precio(sku: str, nuevo_precio, conexion_aurora):
# 1. La fuente de verdad SIEMPRE se escribe primero. Si falla, no se toca la caché.
with conexion_aurora.cursor() as cur:
cur.execute("UPDATE productos SET precio = %s WHERE sku = %s",
(nuevo_precio, sku))
conexion_aurora.commit()
# 2. Después se invalida. Se BORRA, no se actualiza: así el siguiente lector
# recarga el objeto completo y no hay riesgo de dejar un objeto a medias.
try:
cache.delete(clave_producto(sku))
except RedisError:
pass # el TTL lo arreglará en menos de una horaEl orden importa: base de datos primero, caché después. Al revés, un fallo entre ambas operaciones deja la caché con un dato que la base de datos nunca llegó a tener, y ese error persiste hasta que alguien lo note. Y se borra en lugar de actualizar porque una escritura parcial en la caché es un dato corrupto que se sirve como bueno.
Estampida de caché y claves calientes
La estampida de caché (thundering herd) ocurre cuando una clave muy consultada caduca y todas las peticiones simultáneas fallan a la vez y van juntas a la base de datos. Con 4.100 consultas por minuto y una clave popular, eso son decenas de consultas idénticas en el mismo instante.
Tres mitigaciones, de menor a mayor complejidad: TTL aleatorizado, ya aplicado arriba, que impide que muchas claves caduquen en el mismo segundo y resuelve barato la mayor parte del problema; bloqueo de repoblación, con un solo proceso recargando la clave mientras los demás sirven el valor antiguo o esperan brevemente; y refresco anticipado, que recarga antes de caducar con probabilidad creciente según se acerca el vencimiento.
def obtener_con_bloqueo(clave: str, cargar, ttl: int = 3600):
valor = cache.get(clave)
if valor is not None:
return json.loads(valor)
# SET NX EX: solo un proceso consigue el bloqueo. El EX de 10 s garantiza
# que el bloqueo se libera aunque el proceso que lo tomó muera.
bloqueo = f"lock:{clave}"
if cache.set(bloqueo, "1", nx=True, ex=10):
try:
dato = cargar() # única consulta a Aurora
cache.setex(clave, ttl + random.randint(-300, 300),
json.dumps(dato, default=str))
return dato
finally:
cache.delete(bloqueo)
# No obtuvimos el bloqueo: esperar un poco y reintentar la caché.
time.sleep(0.05)
valor = cache.get(clave)
return json.loads(valor) if valor else cargar()Una clave caliente es el otro problema: una sola clave que concentra tanto tráfico que satura el
nodo que la contiene. En modo clúster no se puede repartir, porque una clave vive en una ranura. Las
soluciones son replicar la clave con sufijos (ranking:viernes:0 … :4, eligiendo uno al azar en la
lectura) o cachearla también en la memoria del proceso de la aplicación con un TTL de pocos segundos.
Es el mismo concepto que la clave caliente de DynamoDB en 06-02.
Estructuras de Redis aplicadas a la tienda
Aquí es donde Redis se distancia de una caché genérica: las estructuras permiten resolver problemas que con cadenas exigirían leer, modificar y reescribir el objeto entero.
# 1. CADENAS · la ficha de producto serializada, con caducidad.
cache.setex("catalogo:v3:producto:PESC-SALM-001", 3600, json.dumps(producto))
# 2. HASHES · el carrito rápido. Cada campo es un SKU y cada valor, las unidades.
# Cambiar una línea NO exige reescribir el carrito entero: HINCRBY es atómico.
cache.hincrby("carrito:rapido:4471", "PESC-SALM-001", 2)
cache.hincrby("carrito:rapido:4471", "VERD-TOMA-014", -1)
cache.expire("carrito:rapido:4471", 1800)
carrito = cache.hgetall("carrito:rapido:4471") # {'PESC-SALM-001': '2', ...}
# 3. LISTAS · las últimas búsquedas del cliente, con tope de 10.
cache.lpush("busquedas:4471", "salmón noruego")
cache.ltrim("busquedas:4471", 0, 9) # descarta lo que sobra
ultimas = cache.lrange("busquedas:4471", 0, 9)
# 4. CONJUNTOS ORDENADOS · el ranking de más vendidos del viernes.
# ZINCRBY suma al marcador de forma atómica; el orden se mantiene solo.
cache.zincrby("ranking:viernes:2026-08-07", 2, "PESC-SALM-001")
top10 = cache.zrevrange("ranking:viernes:2026-08-07", 0, 9, withscores=True)
cache.expire("ranking:viernes:2026-08-07", 604800) # una semana| Estructura | Operaciones clave | Uso en MercadoFresco | Alternativa en Aurora |
|---|---|---|---|
| Cadena | SET, GET, SETEX |
Ficha de producto | SELECT por clave |
| Hash | HSET, HINCRBY, HGETALL |
Carrito rápido | Tabla de líneas |
| Lista | LPUSH, LTRIM, LRANGE |
Últimas búsquedas | Tabla con LIMIT 10 |
| Conjunto ordenado | ZINCRBY, ZREVRANGE |
Ranking del viernes | GROUP BY + ORDER BY |
| Contador | INCR, DECR, INCRBY |
Vistas, límites de tasa | UPDATE ... SET n = n + 1 |
El conjunto ordenado merece un comentario: calcular el ranking de más vendidos en SQL exige agregar y
ordenar todas las líneas del día, mientras que con ZINCRBY el orden se mantiene incrementalmente en
cada venta y consultar el top 10 cuesta tiempo logarítmico. Es la diferencia entre un cuadro de mando
que se actualiza cada cinco minutos y uno que va en vivo.
Y una advertencia sobre el carrito rápido: el carrito autoritativo sigue estando en DynamoDB (06-02). El hash de Redis es una copia caliente para pintar la cabecera. Si el nodo se reinicia, el carrito no se pierde porque la verdad está en otro sitio; si se usara Redis como fuente única, un reinicio sería una pérdida de ventas.
Contadores atómicos y el límite del stock
INCR y DECR son atómicos: mil procesos concurrentes producen el resultado correcto sin bloqueos.
Eso los hace ideales para contadores de vistas, limitación de tasa por IP y stock aproximado.
# Reservar unidades en la caché ANTES de tocar la base de datos:
# descarta rápidamente la mayoría de intentos cuando ya no queda producto.
restante = cache.decrby("stock:aprox:PESC-SALM-001", unidades)
if restante < 0:
cache.incrby("stock:aprox:PESC-SALM-001", unidades) # deshacer
raise SinStock("Producto agotado")Advertencia que no admite matices. Esto no es el control de stock. Es un filtro previo, una optimización que evita que miles de peticiones lleguen a Aurora cuando un producto ya está agotado. El stock real sigue siendo transaccional en Aurora y se descuenta dentro de la transacción de confirmación del pedido, con `UPDATE stock SET unidades = unidades - :n WHERE sku = :s AND unidades
= :n` comprobando que afectó a una fila. Si esa comprobación falla, el pedido se rechaza aunque la caché dijera que había existencias.
La razón es la de siempre: una caché puede perder datos. Un reinicio del nodo, una conmutación por error o una expulsión por falta de memoria pueden dejar el contador en un valor incorrecto, y con producto fresco eso significa vender cajas de fresas que no existen: llamar al cliente, reembolsar y perderlo. Es exactamente la distinción de 06-01: eventual para mostrar, fuerte para decidir.
Sesiones en Redis: adiós a las sesiones pegajosas
En 03-03 dejamos un cabo suelto. El ALB alb-mercadofresco-tienda tiene stickiness.enabled=false
porque la aplicación es sin estado, y dijimos entonces que «MercadoFresco acabará usando ElastiCache».
Este es ese momento.
El problema que resuelve: si la sesión vive en la memoria de la instancia EC2 que atendió al usuario, ese usuario tiene que volver siempre a la misma instancia. Eso obliga a activar sesiones pegajosas en el ALB, y las sesiones pegajosas rompen tres cosas: el reparto de carga se desequilibra, al reducirse el ASG los usuarios de la instancia terminada pierden la sesión, y los despliegues continuos expulsan usuarios. Con las sesiones en Redis, cualquier instancia puede atender a cualquier usuario:
# La sesión vive en Redis; la instancia EC2 no guarda nada del usuario.
def cargar_sesion(id_sesion: str) -> dict:
clave = f"sesion:{id_sesion}"
datos = cache.get(clave)
if datos is None:
return {}
cache.expire(clave, 1800) # TTL deslizante: se renueva con la actividad
return json.loads(datos)
def guardar_sesion(id_sesion: str, datos: dict):
cache.setex(f"sesion:{id_sesion}", 1800, json.dumps(datos))| Con sesión local | Con sesión en Redis | |
|---|---|---|
| Sesiones pegajosas en el ALB | Necesarias | Innecesarias |
| Reparto de carga | Desequilibrado | Uniforme |
| Reducir el ASG | Expulsa usuarios | Transparente |
| Despliegue continuo | Expulsa usuarios | Transparente |
| Instancia sustituible | No del todo | Sí, completamente |
Este es el cierre del hilo que viene de 02-01: el ASG asg-mercadofresco-tienda puede por fin escalar,
reducirse y reemplazar instancias con total libertad, porque ninguna instancia contiene nada que le
pertenezca a un cliente. Con dos precauciones: el grupo de sesiones usa réplicas y persistencia,
porque perder todas las sesiones a la vez es un incidente visible; y los datos de sesión se limitan a lo
imprescindible —identificador de cliente y preferencias—, nunca datos de pago ni información personal
que obligaría a tratar la caché como un almacén sujeto al RGPD.
Seguridad: red, cifrado y control de acceso
aws elasticache create-replication-group \
--replication-group-id mercadofresco-catalogo \
--replication-group-description "Cache de catalogo y sesiones" \
--engine valkey --engine-version 7.2 \
--cache-node-type cache.t4g.medium \
--num-cache-clusters 2 \
--automatic-failover-enabled --multi-az-enabled \
--cache-subnet-group-name sng-mercadofresco-datos \
--security-group-ids sg-mercadofresco-cache \
--at-rest-encryption-enabled \
--transit-encryption-enabled --transit-encryption-mode required \
--kms-key-id alias/mercadofresco-datos \
--snapshot-retention-limit 5 --snapshot-window "03:00-04:00" \
--tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
Key=Componente,Value=cache Key=Propietario,Value=luis \
Key=CentroCoste,Value=plataforma \
--region eu-west-1 --profile mercadofresco-devLas cinco medidas que hacen que esto sea seguro. Subredes privadas: sng-mercadofresco-datos no
tiene ruta a internet, y una caché nunca debe ser accesible desde fuera —los incidentes históricos de
Redis expuesto son numerosos y graves—. Grupo de seguridad dedicado: sg-mercadofresco-cache
permite el puerto 6379 solo desde sg-mercadofresco-tienda y desde el rol de las Lambda, referenciando
grupos de seguridad y no rangos de IP, como en 03-02. Cifrado en reposo y en tránsito con
alias/mercadofresco-datos, en modo required, porque preferred aceptaría conexiones sin TLS y eso
convierte el cifrado en opcional de hecho. RBAC: la tienda recibe un usuario que puede leer y
escribir catalogo:* y sesion:* pero no puede ejecutar FLUSHALL, KEYS ni CONFIG, que es el
mínimo privilegio de 04-01 aplicado a la caché. Y credenciales en Secrets Manager, como todo desde
04-03, nunca en variables de entorno en claro.
Métricas, alarmas y la medición del antes y el después
| Métrica | Qué indica | Alarma sugerida |
|---|---|---|
CacheHitRate |
Porcentaje de aciertos | < 80 % durante 15 min |
Evictions |
Claves expulsadas por falta de memoria | > 0 sostenido |
DatabaseMemoryUsagePercentage |
Memoria usada frente a maxmemory |
> 80 % |
CPUUtilization / EngineCPUUtilization |
Carga del proceso Redis | > 70 % |
CurrConnections |
Conexiones abiertas | Subida anómala |
ReplicationLag |
Retardo de la réplica | > 1 s |
Dos merecen explicación. Evictions mayor que cero de forma sostenida es siempre un problema:
Redis está borrando claves que aún no habían caducado porque no le cabe más, la tasa de aciertos cae y
la carga vuelve a Aurora; la solución es más memoria o menos datos, no ignorarlo. Y
EngineCPUUtilization es más informativa que CPUUtilization porque el proceso es fundamentalmente
monohilo: un nodo con 4 vCPU puede mostrar 25 % de CPU total con el hilo del motor al 100 %.
La medición del antes y el después, con los paneles del módulo 5:
| Métrica | Origen | Antes | Después |
|---|---|---|---|
TiempoConfirmacionPedido p99 |
MercadoFresco/Tienda |
880 ms | 310 ms |
| Latencia de la ficha de producto | X-Ray | 240 ms | 28 ms |
| Consultas/min a Aurora (catálogo) | AWS/RDS |
4.100 | 215 |
| ACU medias de Aurora | AWS/RDS |
2,4 | 1,5 |
CacheHitRate |
AWS/ElastiCache |
— | 94,8 % |
Este es el momento de recordar por qué el módulo 5 iba antes que el 6: sin esas métricas, ninguna de las cuatro decisiones de este módulo se podría defender ante dirección con datos. La latencia de la ficha es lo que el cliente nota; el resto es lo que convence al que firma la factura.
MemoryDB y DAX: cuándo no es ElastiCache
| Servicio | Qué es | Cuándo |
|---|---|---|
| ElastiCache | Caché en memoria; los datos pueden perderse | Cachear una fuente de verdad que está en otro sitio |
| MemoryDB | Base de datos en memoria duradera, compatible con Redis | Cuando Redis es la fuente de verdad y no puede perder datos |
| DAX | Caché específica de DynamoDB, misma API | Lecturas repetitivas muy intensas sobre DynamoDB |
MemoryDB replica cada escritura en un registro transaccional distribuido en varias AZ antes de confirmarla, con durabilidad comparable a la de una base de datos, y cuesta bastante más. MercadoFresco no lo necesita: el catálogo está en Aurora y el carrito en DynamoDB, así que la caché siempre puede repoblarse. DAX ya se descartó en 06-02: el carrito no tiene lecturas repetitivas —cada cliente lee el suyo—, así que la tasa de aciertos sería baja, y además ElastiCache sirve datos de varias fuentes mientras que DAX solo entiende de DynamoDB.
Costes y ahorro medido
| Concepto | Cantidad | Coste mensual |
|---|---|---|
cache.t4g.medium primario |
730 h × 0,073 | 53,29 USD |
cache.t4g.medium réplica |
730 h × 0,073 | 53,29 USD |
| Copias de seguridad (5 días) | ~3 GB | 0,25 USD |
| Transferencia entre AZ | Réplica | ~2 USD |
| Total ElastiCache | ≈108,83 USD |
Y lo que devuelve, medido en la factura de Aurora del mes siguiente:
| Concepto | Antes | Después | Diferencia |
|---|---|---|---|
| ACU medias de Aurora | 2,4 | 1,5 | −0,9 ACU |
| Coste de cómputo de Aurora | 210 USD | 131 USD | −79 USD |
| E/S de Aurora | 36 USD | 22 USD | −14 USD |
| Coste de ElastiCache | 0 | 109 USD | +109 USD |
| Neto | +16 USD/mes |
Conviene ser honesto: la caché no se paga sola en euros, cuesta 16 USD netos al mes. Lo que compra es la latencia de la ficha dividida por ocho —de 240 ms a 28 ms, que el cliente percibe—, un margen de capacidad en Aurora que antes no existía para absorber el pico del viernes, y las sesiones distribuidas que permiten al ASG escalar y reducirse sin expulsar a nadie. Como en Aurora, la justificación no es el ahorro: es lo que se compra con el gasto.
Limpieza. Al terminar de practicar:
aws elasticache delete-replication-group --replication-group-id <id>, con--final-snapshot-identifiersi quieres conservar los datos. Un grupo olvidado con dos nodos son más de 100 USD al mes por nada. Comprueba con Cost Explorer que la etiquetaComponente=cachedesaparece al mes siguiente.
Errores Comunes y Consejos
Dejar que un fallo de la caché rompa la tienda. El error más grave y el más común. Toda operación
de caché va envuelta en try/except y con tiempo de espera corto. Una caché caída degrada el
rendimiento; nunca la disponibilidad.
Cachear el resultado antes de confirmarlo en la base de datos. Base de datos primero, caché después. Al revés, un fallo intermedio deja en la caché un dato que nunca existió.
Actualizar la caché en vez de borrarla al invalidar. Borrar es idempotente y sencillo; actualizar puede dejar un objeto parcialmente escrito que se sirve como bueno durante una hora.
Usar la caché como fuente de verdad del stock. Ya está dicho con toda claridad: el stock real es transaccional en Aurora. La caché solo filtra intentos evidentes.
Ignorar Evictions. Un valor sostenido por encima de cero significa que Redis borra claves vivas
por falta de memoria. La tasa de aciertos cae y la carga vuelve a la base de datos, con lo que la caché
deja de cumplir su función mientras se sigue pagando.
Ejecutar KEYS * en producción. Recorre todo el espacio de claves y, como Redis es esencialmente
monohilo, bloquea el servidor mientras lo hace. Se usa SCAN, que itera por lotes. Lo mejor es que
el RBAC prohíba KEYS directamente.
Poner claves sin TTL. Una clave sin caducidad no se va nunca. Suficientes de ellas y la memoria se llena, con lo que empiezan las expulsiones. La regla es simple: toda clave lleva TTL, salvo excepción justificada y documentada.
Cachear datos personales sin pensarlo. Una caché es una copia más de datos personales sujeta al RGPD: hay que cifrarla, restringir el acceso y poder borrarla ante una solicitud de supresión. Cachea identificadores y preferencias, no direcciones ni datos de pago.
Consejo: mide la tasa de aciertos por tipo de clave, no solo la global. Un 94 % global puede esconder un 99 % en el catálogo y un 40 % en los listados, y ese 40 % indica un TTL mal elegido o un patrón que no se repite lo suficiente para merecer caché.
Consejo: prueba el modo degradado. Apaga la caché en preproducción con carga y comprueba que la tienda sigue funcionando, más lenta pero funcionando. Si se cae, la caché se ha convertido en una dependencia crítica sin que nadie lo decidiera.
Ejercicios
Ejercicio 1: diseñar la política de caché de una funcionalidad nueva
MercadoFresco lanza «mi lista habitual»: una pantalla que muestra los 20 productos que un cliente más ha comprado en los últimos 6 meses, con su precio actual, su disponibilidad y una marca si están en oferta. Calcularla en Aurora exige agregar el historial del cliente (1,2 s) y consultar precio y stock de 20 productos.
Diseña la estrategia: qué se cachea y con qué clave, qué TTL para cada pieza, qué patrón usas para cada una, qué se invalida y cuándo, cómo evitas la estampida el lunes por la mañana, y qué estructura de Redis eliges para cada elemento. Justifica por qué no cacheas la respuesta completa de la pantalla en una sola clave.
Ejercicio 2: diagnosticar una caché que no ayuda
Dos semanas después de desplegar la caché, las métricas son estas: CacheHitRate 41 %,
DatabaseMemoryUsagePercentage 97 %, Evictions 8.400 por hora, EngineCPUUtilization 88 %,
CurrConnections 2.900 y creciendo, y la latencia p99 de la tienda ha empeorado respecto a antes de
la caché. Se descubre además que el equipo cachea los resultados de búsqueda con clave
busqueda:<texto completo>:<filtros> y TTL de 24 horas.
Responde: (a) cuál es la causa raíz y cómo la deduces de las métricas; (b) por qué la latencia ha
empeorado en vez de mejorar; (c) qué papel juega CurrConnections; (d) cuatro medidas ordenadas por
impacto; (e) qué política habría evitado el problema desde el diseño.
Ejercicio 3: cerrar el círculo del stock
Escribe el flujo completo de «añadir al carrito y confirmar pedido» para una caja de fresas de la que quedan 3 unidades, indicando en cada paso qué almacén interviene (ElastiCache, DynamoDB, Aurora), qué tipo de consistencia se usa y por qué. Debe cubrir: mostrar la ficha con «últimas unidades», añadir al carrito, el paso a pago, la confirmación con descuento de stock, y qué se le muestra a un cliente que llega tarde. Indica además qué pasa en cada paso si ElastiCache se cae en ese momento.
Soluciones
Solución 1
No se cachea la pantalla completa en una sola clave porque mezcla piezas con ritmos de cambio radicalmente distintos: la lista de productos habituales cambia como mucho una vez al día, pero el precio y la disponibilidad cambian cada pocos minutos. Con una sola clave hay que elegir entre un TTL corto —que desperdicia la agregación cara recalculándola constantemente— o uno largo —que muestra precios obsoletos—. La regla general es cachear por ritmo de cambio, no por pantalla.
| Pieza | Clave | Estructura | TTL | Patrón | Invalidación |
|---|---|---|---|---|---|
| Lista de SKU habituales | habitual:v1:<id_cliente> |
Conjunto ordenado (puntuación = compras) | 24 h ± 1 h | Cache-aside con bloqueo | Al confirmar un pedido, ZINCRBY incremental |
| Ficha de producto | catalogo:v3:producto:<sku> |
Cadena JSON | 1 h ± 5 min | Cache-aside | Al cambiar precio |
| Stock aproximado | stock:aprox:<sku> |
Contador | 30 s | Write-through en la carga | No |
| Marca de oferta | ofertas:v1:activas |
Conjunto | 10 min | Cache-aside | Al publicar campaña |
La pantalla se compone leyendo el conjunto ordenado (una operación) y luego las 20 fichas con una
sola llamada MGET, no veinte GET: la reducción de viajes de red es lo que convierte 20 × 0,3 ms
en 0,4 ms.
Estampida del lunes por la mañana: el riesgo es que miles de claves habitual:* caduquen a la vez.
Se mitiga con las tres técnicas combinadas: TTL aleatorizado ±1 hora, bloqueo de repoblación con SET NX EX para que solo un proceso agregue por cliente, y —dado que la agregación cuesta 1,2 s— un
refresco anticipado programado de madrugada para los clientes más activos, que llegan por la mañana con
la clave ya caliente.
Incremental en vez de recalcular: al confirmar un pedido, en lugar de invalidar la lista se hace
ZINCRBY habitual:v1:<cliente> <unidades> <sku>. La lista se mantiene actualizada sin volver a
ejecutar nunca la agregación de 1,2 s, salvo cuando la clave caduque de verdad. Es el mejor uso de un
conjunto ordenado en toda la lección.
Solución 2
(a) Causa raíz: la caché de resultados de búsqueda. Las métricas lo dicen en cadena. La clave
busqueda:<texto completo>:<filtros> tiene cardinalidad prácticamente infinita: cada combinación de
texto libre y filtros genera una clave nueva que casi nadie repetirá. Con TTL de 24 horas, esas claves
se acumulan sin parar hasta llenar la memoria (DatabaseMemoryUsagePercentage 97 %), y entonces Redis
empieza a expulsar (Evictions 8.400/h) —y expulsa también las claves del catálogo, que sí eran
útiles—. De ahí el CacheHitRate del 41 %: se cachea muchísimo que no se reutiliza y se pierde lo que
sí. Es el caso de manual de cachear datos que se leen una sola vez.
(b) Por qué la latencia empeoró. Ahora cada petición paga dos costes en vez de uno: primero
consulta Redis, falla el 59 % de las veces, y luego consulta Aurora igualmente. A eso se suma que
Aurora recibe casi la misma carga que antes —porque los aciertos útiles se han desplomado— con lo que
no ha mejorado su latencia, y que el nodo de Redis está saturado (EngineCPUUtilization 88 %), así que
incluso las operaciones de caché son lentas. Una caché con mala tasa de aciertos es peor que no tener
caché: añade latencia y no quita carga.
(c) CurrConnections creciendo. Indica que las conexiones no se están reutilizando: o falta un
pool de conexiones, o cada invocación Lambda abre una nueva y no la cierra. Cada conexión consume
memoria del nodo —agravando el problema de memoria— y CPU en el hilo del motor. Con 2.900 conexiones y
creciendo, el nodo acabará rechazando conexiones nuevas.
(d) Cuatro medidas por impacto.
- Dejar de cachear las búsquedas de texto libre. Es la causa raíz y su corrección libera memoria de inmediato. Si se quiere cachear búsqueda, solo las consultas más frecuentes —el top 100 medido en los registros— con TTL de minutos, no las 400.000 combinaciones posibles.
- Configurar la política de expulsión adecuada (
allkeys-lruovolatile-lru) y revisarmaxmemory. Con LRU, al menos se expulsa lo menos usado en lugar de lo que toque. - Introducir un pool de conexiones con límite máximo en la aplicación y en las Lambda, reutilizando el cliente entre invocaciones.
- Revisar el dimensionamiento una vez corregido lo anterior: si con solo el catálogo y las sesiones la memoria sigue por encima del 80 %, hay que subir de tipo de nodo.
(e) La política de diseño que lo evita. Dos reglas explícitas, escritas antes de la primera línea de
código. Primera: solo se cachea lo que se lee muchas más veces de las que se escribe, y esa
proporción se mide antes de cachear —el catálogo era 4.100:1; una búsqueda de texto libre es
aproximadamente 1:1—. Segunda: toda clave tiene TTL acotado y toda familia de claves tiene un límite
de cardinalidad conocido; si no se puede acotar cuántas claves distintas va a generar un patrón, no se
cachea con ese patrón. Añadir una alarma sobre Evictions > 0 desde el primer día habría avisado en
horas en lugar de en semanas.
Solución 3
1. Mostrar la ficha con «últimas unidades». ElastiCache, consistencia eventual. Se lee
catalogo:v3:producto:PESC-FRES-002 y stock:aprox:PESC-FRES-002 (TTL 30 s). Si el contador es 3, se
pinta «quedan pocas unidades». Un desfase de 30 segundos aquí no daña a nadie. Si ElastiCache se cae:
fallo de caché, se lee de Aurora, la ficha tarda 240 ms en vez de 28. Funciona, más lento.
2. Añadir al carrito. DynamoDB, escritura en mercadofresco-carritos con UpdateItem, más una
actualización del hash carrito:rapido:<cliente> en ElastiCache para pintar la cabecera. No se
descuenta stock: añadir al carrito no reserva nada, porque reservar al añadir provoca que carritos
abandonados bloqueen producto real. Se hace un DECRBY sobre stock:aprox solo como filtro
orientativo si la política de negocio lo pide, aceptando que es aproximado. Si ElastiCache se cae: el
carrito se guarda igual en DynamoDB —que es la verdad— y la cabecera se pinta leyendo de DynamoDB. No
se pierde nada.
3. Paso a pago. DynamoDB con lectura fuertemente consistente (ConsistentRead=True) del
carrito: de esta lectura depende lo que se cobra, así que no vale la eventual. Se recalculan los
precios leyendo de Aurora, no de la caché, porque el importe que se cobra no puede basarse en un
precio de hasta una hora de antigüedad. Si ElastiCache se cae: no afecta, este paso no la usa.
4. Confirmación y descuento de stock. Aurora, transacción con consistencia fuerte, y es el único paso que decide:
BEGIN;
UPDATE stock SET unidades = unidades - 3
WHERE sku = 'PESC-FRES-002' AND unidades >= 3; -- debe afectar a 1 fila
-- si afecta a 0 filas: ROLLBACK y rechazar el pedido
INSERT INTO pedidos (...) VALUES (...);
INSERT INTO lineas_pedido (...) VALUES (...);
COMMIT;Después del COMMIT, y solo después, se invalida stock:aprox:PESC-FRES-002 en la caché y se hace
ZINCRBY en el ranking del viernes. El orden es el de siempre: fuente de verdad primero, caché
después. Si ElastiCache se cae: el pedido se confirma correctamente; el contador aproximado quedará
desfasado hasta que caduque a los 30 segundos, sin ninguna consecuencia.
5. El cliente que llega tarde. Vio «quedan pocas unidades» porque leyó la caché, añadió al carrito
y, al confirmar, el UPDATE afecta a 0 filas porque otro cliente se llevó las tres cajas. La respuesta
correcta es no confirmar el pedido y mostrar un mensaje claro —«las fresas se han agotado mientras
completabas el pedido»— con la sugerencia de un producto equivalente y la opción de continuar sin ese
artículo. Nunca se confirma un pedido basándose en una lectura de caché.
El principio que recorre los cinco pasos, y que es la síntesis del módulo entero: eventual para mostrar, fuerte para decidir. La caché acelera lo que el cliente ve; la transacción de Aurora decide lo que el cliente compra. Y cada almacén tiene un modo de fallo acotado: si la caché cae, todo sigue funcionando más lento; si DynamoDB cae, se pierden carritos pero no pedidos; solo si Aurora cae se para la venta, y por eso Aurora es la única de las tres con seis copias en tres zonas de disponibilidad.
La capa de datos completa de MercadoFresco
Las cuatro cargas que diagnosticamos en 06-01 tienen ya su motor, y cada asignación se sostiene sobre una medición:
| Carga | Patrón medido | Motor | Resultado medido |
|---|---|---|---|
| Pedidos y stock | OLTP transaccional, SQL ad hoc | Aurora PostgreSQL | Conmutación de 96 s a 20 s; réplica de 8 s a 15 ms |
| Carrito y sesiones | Clave-valor, 180.000 escrituras/día | DynamoDB | 78 GB → 8 GB; 51 → 8 USD/mes; sin VACUUM |
| Informes | OLAP, 6-40 M de filas agregadas | Redshift Serverless | 4-6 min → 2-6 s; fuera de producción |
| Catálogo | 4.100 lecturas/min, 94 % idénticas | ElastiCache (Valkey) | 240 ms → 28 ms; 4.100 → 215 consultas/min |
graph TD
CF["CloudFront E2QWERTY123ABC"] --> ALB["alb-mercadofresco-tienda<br/>sin sesiones pegajosas"]
ALB --> ASG["asg-mercadofresco-tienda<br/>instancias sin estado"]
ASG --> EC["ElastiCache · mercadofresco-catalogo<br/>catálogo, sesiones, ranking, contadores"]
ASG --> DDB["DynamoDB · mercadofresco-carritos<br/>carrito y sesiones · TTL 30 días"]
ASG --> AUR["Aurora · aurora-mercadofresco-pedidos<br/>pedidos, stock, catálogo · fuente de verdad"]
EC -.->|fallo de caché| AUR
AUR -->|canalización nocturna e integración sin ETL| RS["Redshift · mercadofresco-analitica<br/>hechos_pedidos y dimensiones"]
DDB -.->|Streams: carritos abandonados| S3["S3 · mercadofresco-informes-analitica"]
S3 --> RS
RS --> SARA["Informes de Sara<br/>ticket medio, cohortes, ranking del viernes"]
Y el balance económico completo del módulo, que conviene mirar de frente:
| Componente | Antes | Después |
|---|---|---|
RDS mercadofresco-pedidos + réplica |
118 USD | — |
| Aurora Serverless v2 | — | 153 USD |
DynamoDB mercadofresco-carritos |
— | 8 USD |
| Redshift Serverless | — | 16 USD |
| ElastiCache | — | 109 USD |
| Total capa de datos | 118 USD | 286 USD |
La capa de datos cuesta 2,4 veces más. A cambio: la latencia p99 de la tienda pasa de 1.900 ms a 310 ms, la conmutación por error de 96 a 20 segundos, los informes de Sara de seis minutos a seis segundos, la tabla de sesiones deja de crecer sin control, y el ASG puede escalar y reducirse sin expulsar a nadie. Para una tienda que hace 900 pedidos por hora en el pico del viernes, con un ticket medio de decenas de euros, 168 USD adicionales al mes es una decisión que se justifica sola. Lo importante es que ahora está justificada con números, y esa es la diferencia entre arquitectura e intuición.
Conclusión
El catálogo se sirve desde memoria. Sabes por qué cachear —latencia, coste por consulta y, sobre todo, protección de la base de datos, porque un 95 % de aciertos convierte 4.100 consultas por minuto en 205 y devuelve a Aurora el margen que necesitaba para el pico— y sabes qué no cachear: lo que cambia en cada lectura, lo que exige exactitud momentánea, lo que se lee una sola vez y los datos personales sin una razón clara.
Conoces Redis/Valkey frente a Memcached y por qué MercadoFresco elige el primero: estructuras de datos más allá de las cadenas, réplicas con conmutación automática y operaciones atómicas. Dominas la arquitectura —nodo, fragmento, grupo de réplica, modo clúster activado y desactivado con sus 16.384 ranuras hash y sus etiquetas para coubicar claves, Multi-AZ con conmutación— y la decisión de empezar con el modo clúster desactivado, porque 1,2 GB de catálogo caben de sobra en un nodo y añadir complejidad sin medición es el error que este módulo lleva cinco lecciones combatiendo. Con los puntos de enlace principal, de lectura, de configuración y de nodo, y la regla de que el de nodo jamás aparece en la aplicación.
Manejas los cuatro patrones de caché y la elección razonada: cache-aside para el catálogo
porque es el más robusto —si la caché desaparece, la tienda sigue—, write-through en la carga de la
lonja de las 06:00 para que el primer cliente no encuentre la caché vacía, y write-behind descartado
explícitamente porque perder escrituras de negocio no es aceptable. Con el código completo y sus
cinco decisiones: try/except en toda operación con tiempo de espera de 150 ms, clave con versión para
invalidar el catálogo entero de golpe, setex atómico para que no queden claves inmortales, TTL
aleatorizado y serialización depurable. Y con el orden que no se negocia: base de datos primero,
caché después, y borrar en vez de actualizar al invalidar.
Sabes qué es una estampida de caché y las tres mitigaciones —TTL aleatorizado, bloqueo de
repoblación con SET NX EX, refresco anticipado— y reconoces la clave caliente como el mismo
problema que en DynamoDB con otro nombre. Aplicas las estructuras de Redis a problemas reales: cadenas
para la ficha, hashes para el carrito rápido con HINCRBY atómico, listas con LTRIM para las
últimas búsquedas, conjuntos ordenados con ZINCRBY para que el ranking del viernes se mantenga
solo en cada venta, y contadores atómicos para el stock aproximado —con la advertencia que no admite
matices: el stock real sigue siendo transaccional en Aurora, porque una caché puede perder datos y
vender fresas que no existen significa llamar al cliente, reembolsar y perderlo—.
Y has cerrado el cabo suelto de 03-03: con las sesiones en Redis, el ALB no necesita sesiones
pegajosas, el reparto de carga es uniforme, y el ASG asg-mercadofresco-tienda puede escalar,
reducirse y reemplazar instancias sin expulsar a nadie, porque ninguna instancia contiene ya nada que
pertenezca a un cliente. Todo ello en subredes privadas con sg-mercadofresco-cache, cifrado en
reposo y en tránsito en modo required, RBAC que prohíbe FLUSHALL y KEYS, credenciales en Secrets
Manager, alarmas sobre CacheHitRate, Evictions, EngineCPUUtilization y
DatabaseMemoryUsagePercentage, y la comparación honesta con MemoryDB y DAX para saber cuándo
el problema no es de caché. Coste: 109 USD que devuelven 93 en Aurora, y una latencia de ficha dividida
por ocho.
Con esto se cierra el módulo 6. Las cuatro cargas tienen su motor y cada decisión se sostiene sobre una medición del módulo 5: Aurora para pedidos y stock, DynamoDB para el carrito y las sesiones, Redshift Serverless para los informes de Sara, ElastiCache para el catálogo. La capa de datos cuesta 2,4 veces más y vale cada euro, y lo importante es que ahora se puede demostrar.
Pero al repartir los datos entre cuatro almacenes ha aparecido un problema nuevo, y esta vez no es de rendimiento. La tienda hace demasiadas cosas de forma síncrona dentro de la petición del cliente. Cuando alguien pulsa «Confirmar pedido», el mismo hilo cobra la tarjeta, escribe en Aurora, actualiza DynamoDB, invalida la caché, avisa al almacén para que prepare la caja, manda el correo de confirmación, notifica al repartidor y publica el evento para la analítica. Ocho cosas encadenadas, y si cualquiera de ellas falla, el pedido entero se rompe: si el proveedor de correo tarda cuatro segundos, el cliente espera cuatro segundos; si el sistema del almacén está caído, se pierde una venta que estaba pagada. Ya no hay una base de datos que consultar para arreglarlo, porque el problema no está en ningún almacén: está en que todo depende de todo, al mismo tiempo.
En el módulo 7, «Integración de aplicaciones», empezando por 07-01, «Amazon SQS», veremos cómo desacoplar esas ocho tareas: colas para que el pedido se confirme en cuanto está cobrado y lo demás ocurra después, temas para que un mismo evento llegue a varios interesados sin que la tienda sepa quiénes son, eventos para enrutar según su contenido, y flujos de trabajo para orquestar procesos largos con reintentos y compensaciones. El objetivo es que confirmar un pedido vuelva a ser una sola cosa rápida y fiable, y que el resto del mundo se entere a su ritmo.
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
