AWS Certified CloudOps Engineer - Associate
Caché con CloudFront y ElastiCache
Dónde poner una caché para que saque carga real: la cache key y las reglas de TTL que deciden qué guarda CloudFront en el edge, y las estrategias de lazy loading, write-through y TTL que deciden qué sostiene ElastiCache delante de tu base de datos.
- Decidir si una carga de trabajo necesita caché en el edge, caché delante de la base de datos, o las 2
- Predecir cuánto tiempo guarda CloudFront un objeto dados los TTL de la cache policy y los headers Cache-Control del origen
- Subir el cache hit ratio de CloudFront achicando la cache key, y elegir entre invalidación y nombres de archivo versionados
- Comparar lazy loading, write-through y TTL como estrategias de población de ElastiCache y nombrar la falla que carga cada una
- Leer las métricas de CloudWatch de ElastiCache para elegir entre escalar hacia arriba, agregar réplicas y agregar shards
Tu página de catálogo corre una consulta que tarda 300 ms y devuelve las mismas 200 filas para cada visitante. A 400 pedidos por segundo, la base de datos ejecuta esa consulta 400 veces por segundo y quema CPU produciendo una respuesta que ya produjo. La consulta no tiene nada malo. El problema es que estás recalculando una constante, y el arreglo es guardar la respuesta en algún lugar más cercano que la cosa que la calculó.
AWS te da 2 lugares muy distintos para guardarla, y la skill 2.1.2 del SOA-C03 nombra a los 2: Amazon CloudFront en el edge y Amazon ElastiCache al lado de tu aplicación. No son intercambiables, y elegir mal significa sumar un servicio que no saca ninguna carga.
Dos cachés, dos distancias
| CloudFront | ElastiCache | |
|---|---|---|
| Dónde vive | Edge locations de AWS, cerca del visitante | Dentro de tu VPC, cerca de tu aplicación |
| Qué guarda | Respuestas HTTP completas, indexadas por una cache key | Lo que tu código ponga ahí, indexado por un string |
| Quién escribe en ella | CloudFront, de forma automática, ante un miss | Tu código de aplicación, de forma explícita |
| Qué protege | Al origen y al camino de red hacia él | A la base de datos |
| Cuánto cuesta un miss | 1 pedido al origen | 1 consulta a la base más 1 escritura en caché |
| Cómo la ajustas | Cache policies, TTLs, la cache key | Estrategia, TTL, tipo de nodo, cantidad de shards |
La frase que decide cuál eliges: CloudFront es una caché que configuras, ElastiCache es una caché que programas. Si los mismos bytes salen hacia muchos visitantes por HTTP, CloudFront puede absorber ese tráfico sin tocar el código. Si lo caro es el resultado de una consulta, un objeto de sesión o un ranking calculado que solo tu aplicación entiende, ninguna caché de edge ayuda, porque CloudFront no puede mirar adentro de la respuesta para saber que es reutilizable.
Los sistemas grandes corren las 2. CloudFront se queda con las respuestas públicas repetidas, ElastiCache con las lecturas internas repetidas, y la base de datos solo ve lo que de verdad varía.
La cache key de CloudFront define qué significa un "hit"
CloudFront guarda cada objeto bajo una cache key. Un pedido es hit solo si produce la misma cache key que un pedido anterior y ese objeto sigue vigente en esa edge location. Todo el ajuste de caché en CloudFront vuelve a una regla: menos valores en la cache key significa más hits.
Controlas la key con una cache policy asociada a un cache behavior. La policy dice qué headers, cookies y query strings entran en la key, y además carga los ajustes de TTL.
Vale la pena memorizar las policies administradas porque marcan los 2 extremos del rango:
| Cache policy administrada | Min TTL | Max TTL | Default TTL | Contenido de la cache key |
|---|---|---|---|---|
CachingOptimized | 1 s | 31.536.000 s (365 días) | 86.400 s (24 h) | Nada más que el header Accept-Encoding normalizado |
CachingDisabled | 0 s | 0 s | 0 s | Nada |
CachingOptimized es la respuesta por defecto para assets estáticos. CachingDisabled es la respuesta correcta para cualquier cosa realmente por usuario, y funciona porque sus 3 TTLs son 0, no porque no reenvíe nada.
Hay 3 errores de cache key que cuestan hit ratio real, y cada uno tiene su arreglo:
- Mayúsculas y orden en los query strings.
?parameter1=Ay?parameter1=ason 2 keys distintas, y también lo son?parameter1=a¶meter2=by?parameter2=b¶meter1=a. El mismo objeto, hasta 4 entradas de caché. Estandariza un solo criterio de mayúsculas y un solo orden en la aplicación que arma las URLs. - Reenviar todas las cookies. Por cada cookie que reenvías, CloudFront cachea una copia separada por cada combinación de nombre y valor. Dos cookies con 3 valores posibles cada una son hasta 9 copias del mismo archivo
.css. Separa contenido estático y dinámico en cache behaviors distintos y reenvía cookies solo en el dinámico. - Cachear por
User-Agent. Tiene una cantidad enorme de valores distintos, así que en la práctica desactiva la caché mientras aparenta ser una configuración que funciona. Si necesitas respuestas por tipo de dispositivo, cachea sobre los headers de dispositivo de CloudFront:CloudFront-Is-Desktop-Viewer,CloudFront-Is-Mobile-Viewer,CloudFront-Is-SmartTV-VieweryCloudFront-Is-Tablet-Viewer. Cuatro valores en vez de miles, y puedes reenviar solo los que de verdad cambian la respuesta.
Queda una palanca más fuera de la key. Si la compresión no está en juego, asocia un custom origin header llamado Accept-Encoding con valor vacío. CloudFront entonces saca ese header de la cache key por completo en vez de partir cada objeto en variantes Gzip, Brotli y sin comprimir.
Cuánto dura un objeto: el origen propone, la policy decide
Esta es la parte que los operadores se equivocan, y conviene recorrerla despacio.
En la cache policy viven 3 ajustes: Minimum TTL, Maximum TTL y Default TTL. El origen además puede mandar Cache-Control: max-age, Cache-Control: s-maxage o un header Expires. Ninguno de los 2 lados gana solo. El origen propone una duración y la policy la recorta dentro del rango.
Recorre el ejemplo con una policy de Minimum TTL 60, Maximum TTL 86400 y Default TTL 3600:
| Respuesta del origen | CloudFront cachea por | Por qué |
|---|---|---|
Cache-Control: max-age=10 | 60 s | Está debajo del mínimo, así que sube al Minimum TTL |
Cache-Control: max-age=7200 | 7200 s | Está dentro del rango, así que se respeta tal cual |
Cache-Control: max-age=31536000 | 86400 s | Está arriba del máximo, así que baja al Maximum TTL |
Sin header Cache-Control | 3600 s | No se propuso nada, así que aplica el Default TTL |
Tres detalles alrededor de esa tabla:
s-maxagele gana amax-ageen el edge. Cuando el origen manda los 2, CloudFront recortas-maxagey el navegador usamax-age. Así cacheas un objeto una hora en el edge y un minuto en el navegador.Expireses la opción más débil. Si el origen mandaCache-Control: max-ageyExpiresa la vez, CloudFront usa solomax-age. AWS recomiendamax-age.- El visitante no puede forzar un refresh. CloudFront ignora
Cache-ControlyPragmaen los pedidos del visitante, así que una recarga forzada en el navegador no limpia el edge.
Ahora el error de concepto que manda bugs a producción. Es tentador suponer que un Cache-Control: no-store del origen siempre le impide cachear a CloudFront. No lo hace cuando el Minimum TTL de la cache policy es mayor que 0. En ese caso CloudFront cachea el objeto durante el TTL mínimo aunque el origen haya dicho que no, y así una página de cuenta personalizada termina servida al siguiente visitante que pega en esa misma edge location. Tanto CachingOptimized (Minimum TTL de 1 segundo) como la policy Amplify (Minimum TTL de 2 segundos) llevan esta advertencia en la documentación de AWS. Si el contenido nunca debe cachearse, la respuesta es una policy con TTL mínimo 0.
Servir contenido viejo a propósito
Dos directivas te dejan cambiar un poco de frescura por latencia y por sobrevivir a una caída del origen:
Cache-Control: max-age=3600, stale-while-revalidate=600, stale-if-error=86400
- Durante la primera hora, CloudFront sirve desde la caché de forma normal.
- Después de eso,
stale-while-revalidate=600deja que CloudFront le entregue al visitante la copia vieja de inmediato mientras trae una fresca en segundo plano, por hasta 10 minutos. stale-if-error=86400deja que CloudFront siga sirviendo la copia vieja por hasta 24 horas si el origen es inalcanzable o devuelve un 5xx.
Las 2 están topeadas por el Maximum TTL de la cache policy, lo que sea menor. Pasado el TTL máximo el objeto desaparece del edge sin importar lo que pidieron las directivas.
Invalidación contra nombres de archivo versionados
Cuando necesitas sacar contenido de la caché antes de que expire tienes 2 opciones, y AWS recomienda la que la gente elige segunda.
La invalidación saca archivos de las cachés del edge ahora mismo. Los primeros 1.000 paths de invalidación por mes son gratis por cuenta AWS en todas sus distribuciones, y después pagas por path. Un path con comodín * cuenta como 1 path sin importar cuántos archivos elimine, así que /* es una sola unidad facturable. El comodín tiene que ser el último caracter; un asterisco en cualquier otra posición se toma de forma literal.
Dos trampas de invalidación que arruinan un despliegue:
- Los query strings son parte del path. Si la cache key incluye query strings,
/images/logo.jpgno invalida/images/logo.jpg?v=2. Usa/images/logo.jpg*. - Los paths de directorio necesitan las 2 formas. Si tus URLs no son consistentes con la barra final, invalida
/imagesy/images/.
Los nombres de archivo versionados significan publicar app.a3f91c.js en vez de pisar app.js. AWS lo recomienda como enfoque principal para contenido que cambia seguido, y la razón no es solo el costo. Una invalidación limpia CloudFront pero no hace nada con la copia que está en el navegador del visitante o en un proxy corporativo; un nombre nuevo cambia la URL, así que todas las cachés de la cadena hacen miss. También vuelve trivial el rollback y hace legibles los access logs, porque la línea del log nombra la versión que se sirvió.
Origin Shield es la otra palanca estructural. Pone una capa de caché más delante del origen para que todas las capas de CloudFront, edge locations y regional edge caches, pasen por una sola ubicación. El origen entonces puede servir 1 pedido por objeto en vez de 1 por cada caché regional.
ElastiCache: la caché que tienes que programar
CloudFront se puebla solo. ElastiCache no: tu código decide qué entra, cuándo y por cuánto tiempo. AWS documenta 3 estrategias, y el examen evalúa el modo de falla de cada una más que su definición.
El lazy loading escribe en la caché recién después de un miss.
get_customer(customer_id)
customer_record = cache.get(customer_id)
if (customer_record == null)
customer_record = db.query("SELECT * FROM Customers WHERE id = {0}", customer_id)
cache.set(customer_id, customer_record)
return customer_record
Solo entra a la caché lo que alguien pidió, y una falla de nodo es sobrevivible: un nodo vacío recién creado igual devuelve respuestas correctas, apenas más lento, mientras los misses lo vuelven a llenar. Los costos son una penalidad de 3 viajes por miss (leer la caché, consultar la base, escribir la caché) y datos viejos, porque nada actualiza la caché cuando la base cambia por debajo.
El write-through escribe en la caché en cada escritura a la base de datos.
save_customer(customer_id, values)
customer_record = db.query("UPDATE Customers WHERE id = {0}", customer_id, values)
cache.set(customer_id, customer_record)
return success
Los datos cacheados nunca quedan viejos, y la latencia extra cae sobre las escrituras, donde los usuarios la toleran mejor. Los costos son la imagen espejo de lazy loading: datos faltantes en cualquier nodo nuevo, porque un nodo fresco no tiene nada hasta que esas filas se vuelvan a escribir, y desperdicio de caché, porque estás cacheando escrituras que quizá nadie lea.
Agregar un TTL a cada escritura es lo que hace que el par funcione junto. Un TTL topea cuánto puede envejecer una entrada cargada de forma lazy y expulsa las entradas write-through que nadie lee. El patrón de producción son las 3 cosas: write-through por corrección, lazy loading por resiliencia, TTL para acotar las 2.
cache.set(customer_id, customer_record, 300) # expira en 5 minutos
Un TTL nunca garantiza frescura. Garantiza un techo de obsolescencia, que es una promesa distinta y mucho más alcanzable. Dilo así en la revisión de diseño en vez de dejar que "cacheamos 5 minutos" se escuche como "la caché es correcta".
Elegir el motor, y por qué cambia tus opciones de escalado
ElastiCache admite Memcached, Valkey y Redis OSS. El motor que eliges determina qué acciones de escalado existen siquiera, así que no es una cuestión de preferencia.
| Memcached | Valkey / Redis OSS (cluster mode disabled) | Valkey / Redis OSS (cluster mode enabled) | |
|---|---|---|---|
| Tipos de datos | Strings y objetos simples | Complejos (listas, hashes, sets, sorted sets) | Complejos |
| Multihilo | Sí | No | No |
| Replicación (read replicas) | No | Sí | Sí |
| Failover automático | No | Opcional | Obligatorio |
| Particionado de datos entre shards | Sí, del lado del cliente | No | Sí |
| Resharding online | No | No | Sí |
| Backup y restore | Clusters basados en nodos: no | Sí | Sí |
Elige Memcached cuando quieras el modelo más simple posible, nodos grandes multi core y caché de objetos plana. Elige Valkey o Redis OSS cuando necesites replicación, failover, persistencia, sorted sets o pub/sub. La consecuencia práctica: un cluster Memcached no se arregla agregando réplicas, porque no tiene. Sus únicas palancas son un tipo de nodo más grande o más nodos.
Leer las métricas y después elegir la acción de escalado
Las preguntas de escalado de ElastiCache suelen ser diagnósticas. Una métrica te dice qué recurso falta, y el recurso más el motor te dicen la acción.
| Métrica | Qué significa | Hacia dónde apunta |
|---|---|---|
CPUUtilization | Porcentaje de CPU a nivel host | En nodos chicos, el techo de la carga de trabajo |
EngineCPUUtilization | Uso del único core del motor | La señal real en nodos con 4 vCPUs o más |
Evictions | Claves eliminadas para hacer lugar | Falta memoria para el working set |
SwapUsage y FreeableMemory | Presión de memoria | FreeableMemory debajo de 100 MB, o SwapUsage arriba de FreeableMemory, significa nodo en problemas |
ReplicationLag | Cuánto atrasa una réplica respecto del primario | Las lecturas en la réplica devuelven datos viejos |
TrafficManagementActive | Un valor de 1 significa que ElastiCache está frenando comandos entrantes | El nodo está subdimensionado para la carga |
El umbral de CPU es donde la gente se cae, así que trabájalo. Valkey y Redis OSS corren el motor en un solo hilo, así que un nodo puede estar completamente saturado mientras la CPU del host marca bastante menos de 100 por ciento. AWS da la cuenta: fija el umbral en 90 dividido la cantidad de cores. En un nodo de 2 cores eso da 45 por ciento. Un cluster parado en 46 por ciento de CPU con latencia en alza no está sano y ocioso, está clavado. En tipos de nodo con 4 vCPUs o más, usa EngineCPUUtilization y la división desaparece. Memcached es multihilo, así que su umbral sí ronda el 90 por ciento.
Una vez que sabes qué recurso falta, la acción sale del motor y de la carga de trabajo:
- Muchas lecturas y por arriba del umbral, en Valkey o Redis OSS: agrega read replicas.
- Muchas escrituras, cluster mode disabled: escala a un tipo de nodo más grande. Hay un solo primario, así que más escrituras necesitan un primario más grande.
- Muchas escrituras, cluster mode enabled: agrega shards, lo que reparte las escrituras entre más nodos primarios.
- Cualquier presión, en Memcached: tipo de nodo más grande, o más nodos.
Cómo ocurre el escalado
En cluster mode enabled, el resharding online cambia la cantidad de shards mientras el cluster sigue atendiendo pedidos. Agregar shards sube la capacidad de lectura y escritura, quitarlos baja el costo, y el rebalanceo empareja el keyspace entre los shards existentes. Hay 3 límites que conviene saber: los shards nuevos reciben la misma cantidad de nodos que el shard más chico que exista, no puedes fijar keyspaces por shard de forma online (eso requiere el camino offline de backup y restore), y las claves con items de más de 256 MB tras la serialización no se migran, lo que puede dejar shards desbalanceados. Antes de quitar shards, ElastiCache verifica que los shards restantes puedan sostener los datos y cancela la operación en vez de perder claves.
El escalado vertical por tipo de nodo también es una operación online para Valkey y Redis OSS. El resharding offline, el camino de backup y restore, es el que se apaga, y aceptas ese downtime solo cuando necesitas las cosas que solo él permite: cambiar tipo de nodo, versión del motor, cantidad de réplicas por shard y keyspaces en un solo movimiento.
ElastiCache Serverless elimina la decisión de shards. Vigila CPU, memoria y red de forma continua y agrega shards por su cuenta, y tú miras 2 métricas en vez de una lista de nodos: BytesUsedForCache para almacenamiento y ElastiCacheProcessingUnits (ECPUs) para cómputo. Puedes topear las 2 para acotar el costo, pero entiende qué hace un tope en el límite: al llegar al máximo de almacenamiento, ElastiCache expulsa por LRU las claves que tienen TTL y después devuelve errores de falta de memoria, y al llegar al máximo de ECPUs frena los pedidos. AWS recomienda una alarma de CloudWatch al 75 por ciento de cualquier máximo que fijes, para que te enteres antes que tus usuarios.
Consejos para el examen
- "La misma respuesta para muchos visitantes por HTTP" es CloudFront. "Resultado de consulta caro reutilizado por la aplicación" es ElastiCache. Un enunciado que describe una base de datos con presión de lectura no está preguntando por el edge.
- Con números de TTL, haz el recorte: un
max-agedebajo del Minimum TTL sube al mínimo, arriba del Maximum TTL baja al máximo, y el Default TTL aplica solo cuando el origen no mandamax-age. Las respuestas equivocadas eligen el número que se mencionó último. - "El origen manda no-store y sin embargo se sirve contenido viejo" apunta a una cache policy con Minimum TTL mayor que 0, no a una falla de CloudFront.
- Son 1.000 paths de invalidación gratis por mes por cuenta, y un path con comodín cuenta como 1. Si el enunciado insiste en actualizaciones frecuentes, la respuesta buscada suele ser nombres de archivo versionados, no más invalidaciones.
- Cualquier opción que suba el cache hit ratio agregando headers, cookies o query strings a la cache key está mal. Los hits vienen de una key más chica.
- Lazy loading permite datos viejos y sobrevive a nodos vacíos. Write-through siempre está fresco y falla con nodos vacíos. El TTL es lo que hace viable correr las 2. Aparea el síntoma con la estrategia, no la definición.
- Valkey y Redis OSS son de un solo hilo: el umbral de la alarma de CPU es 90 dividido la cantidad de cores, y
EngineCPUUtilizationes la señal más limpia en nodos grandes. - Memcached no tiene réplicas, ni failover, ni cluster mode. Cualquier respuesta que ofrezca eso para un cluster Memcached está mal solo por el límite entre motores.
- Muchas lecturas significa réplicas, muchas escrituras con cluster mode disabled significa un nodo más grande, y muchas escrituras con cluster mode enabled significa más shards.
La regla para llevarte de esta lección: nombra qué es lo que se repite antes de nombrar el servicio. Las respuestas HTTP repetidas van al edge, los resultados de consulta repetidos van a memoria al lado de la aplicación, y lo que de verdad varía en cada pedido no va a ningún lado que no sea la base de datos. Esa última categoría es la que la caché no puede rescatar, y ahí arranca la lección siguiente, escalando la base relacional en sí.
