Patrón Content Delivery Network (CDN)
Distribuye contenido estático y en vivo a través de servidores edge geográficamente dispersos para reducir latencia, mejorar disponibilidad y descargar infraestructura de origen.
Descripción General
Una Content Delivery Network coloca copias cacheadas de tu contenido en servidores repartidos por el mundo, de modo que cada request la responde la ubicación edge más cercana al usuario. La ganancia de velocidad es sobre todo física: un usuario en Tokio que pega contra un origen en Virginia paga unos 150-200 ms de latencia de ida y vuelta por request, mientras que un nodo edge en Tokio responde en unos 20 ms. Multiplica eso por las decenas de requests que necesita una página y la diferencia deja de ser sutil.
Más allá de la distancia, un CDN absorbe tráfico de lectura para que tu origen no tenga que hacerlo, amortigua picos y ataques DDoS, y cada vez más ejecuta código en el edge (Cloudflare Workers, Lambda@Edge, Fastly Compute). Lo que empezó como “cache para imágenes” hoy cubre terminación TLS, filtrado de bots, geo-routing y, para algunas apps, el manejo completo de requests.
Cuándo Usar
Usa el Patrón CDN cuando:
- Los usuarios están geográficamente distribuidos y la latencia importa
- Los assets estáticos (imágenes, CSS, JS, fuentes, videos) representan la mayor parte del tráfico. Esto combina de forma natural con el Patrón de Static Content Hosting
- Necesitas absorber picos de tráfico sin escalar la infraestructura de origen
- Se requiere protección DDoS o funcionalidad WAF en el edge
- El edge computing para A/B testing, geo-routing o autenticación te da algo que el origen no puede
Si tu problema es el volumen de requests y no la distancia, como un cliente machacando tu API, el Patrón de Throttling encaja mejor.
Cuándo Evitar
- Los usuarios están en la misma región que el origen. Si todos están ya a 20 ms del servidor, el salto extra por el edge no compra nada.
- El contenido no se puede cachear y dominan los datos por usuario. Los payloads personalizados aún ganan por la descarga de TLS, pero pierdes el beneficio principal.
- Todo es interno. Un CDN privado delante de una intranet casi nunca justifica el costo.
- La complejidad de invalidación supera el beneficio de latencia. Publicar decenas de páginas personalizadas por segundo puede convertir el purgado de cache en un problema de ingeniería propio.
- La terminación TLS en el edge viola tus reglas de compliance; raro, pero real en algunas industrias reguladas.
Solución
Un despliegue de CDN tiene tres piezas móviles: la distribución que se pone delante del origen, los headers cache-control que le dicen al edge qué guardar, y la ruta de invalidación para cuando el contenido cambia. Los ejemplos de abajo cubren las tres.
JavaScript (CloudFront + S3 Origin)
// Configuración de AWS SDK v3 para invalidación de CDN
import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront';
const cloudfront = new CloudFrontClient({ region: 'us-east-1' });
/**
* Invalida el cache del CDN para paths específicos después de un deployment
*/
async function invalidateCache(distributionId, paths = ['/*']) {
const command = new CreateInvalidationCommand({
DistributionId: distributionId,
InvalidationBatch: {
CallerReference: Date.now().toString(),
Paths: {
Quantity: paths.length,
Items: paths
}
}
});
const response = await cloudfront.send(command);
console.log(`Invalidación creada: ${response.Invalidation.Id}`);
return response.Invalidation.Id;
}
// Uso: invalidar después de desplegar assets estáticos
await invalidateCache('E1234567890ABC', ['/assets/*', '/index.html']);
Python (CDN Cache Warmup + Edge Logic)
import requests
import hashlib
from typing import List, Dict
from concurrent.futures import ThreadPoolExecutor
class CDNManager:
"""Gestiona interacciones con el CDN: warmup de cache, purgado y health checks"""
def __init__(self, base_url: str, api_key: str = None):
self.base_url = base_url.rstrip('/')
self.api_key = api_key
self.edge_locations = [
"us-east", "us-west", "eu-west", "eu-central",
"ap-southeast", "ap-northeast", "sa-east"
]
def generate_cache_key(self, path: str, params: Dict = None) -> str:
"""Genera una clave de cache determinística para una URL"""
content = f"{path}:{sorted(params.items()) if params else ''}"
return hashlib.sha256(content.encode()).hexdigest()[:16]
def warmup_cache(self, paths: List[str]) -> Dict[str, bool]:
"""Pre-puebla los caches edge del CDN solicitando desde cada ubicación"""
results = {}
def fetch(path):
try:
response = requests.get(
f"{self.base_url}{path}",
headers={"X-Cache-Warmup": "true"},
timeout=30
)
return path, response.status_code == 200
except Exception:
return path, False
with ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(fetch, p) for p in paths]
for future in futures:
path, success = future.result()
results[path] = success
return results
def purge_cache(self, path: str) -> bool:
"""Purga un path específico del cache del CDN"""
# La implementación varía según el proveedor (Fastly, Cloudflare, etc.)
# Este ejemplo usa un endpoint de purge genérico
try:
response = requests.post(
f"{self.base_url}/__purge",
json={"path": path},
headers={"Authorization": f"Bearer {self.api_key}"} if self.api_key else {}
)
return response.status_code == 200
except Exception as e:
print(f"Purge falló para {path}: {e}")
return False
def get_cache_status(self, path: str) -> Dict:
"""Verifica el estado de cache HIT/MISS para un path"""
response = requests.get(f"{self.base_url}{path}")
return {
"path": path,
"cache_status": response.headers.get("X-Cache", "unknown"),
"age": response.headers.get("Age", "0"),
"ttl_remaining": response.headers.get("Cache-Control", "")
}
# Uso
cdn = CDNManager("https://cdn.example.com", api_key="secret-key")
# Warmup de paths críticos después del deployment
warmup_results = cdn.warmup_cache([
"/assets/main.css",
"/assets/app.js",
"/api/config"
])
print(f"Cache warmup: {sum(warmup_results.values())}/{len(warmup_results)} exitosos")
# Verificar estado del cache
status = cdn.get_cache_status("/assets/main.css")
print(f"Cache: {status['cache_status']}, Age: {status['age']}s")
Terraform (AWS CloudFront Distribution)
# Infrastructure as Code para distribución CloudFront CDN
resource "aws_cloudfront_distribution" "cdn" {
enabled = true
is_ipv6_enabled = true
comment = "StackPractices CDN"
default_root_object = "index.html"
price_class = "PriceClass_All" # Distribución global
# Origen: bucket S3 para assets estáticos
origin {
domain_name = aws_s3_bucket.assets.bucket_regional_domain_name
origin_id = "S3-assets"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
}
}
# Origen: ALB para API dinámica
origin {
domain_name = aws_lb.main.dns_name
origin_id = "ALB-api"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
# Comportamiento por defecto: assets estáticos desde S3
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-assets"
compress = true
forwarded_values {
query_string = false
cookies { forward = "none" }
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 86400 # 1 día
max_ttl = 31536000 # 1 año
}
# Comportamiento ordenado: llamadas API sin caching
ordered_cache_behavior {
path_pattern = "/api/*"
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "ALB-api"
compress = true
forwarded_values {
query_string = true
headers = ["Authorization", "Origin"]
cookies { forward = "all" }
}
viewer_protocol_policy = "https-only"
min_ttl = 0
default_ttl = 0
max_ttl = 0
}
# Edge caching para tipos de asset específicos
ordered_cache_behavior {
path_pattern = "/assets/*"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-assets"
compress = true
forwarded_values {
query_string = true
cookies { forward = "none" }
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 86400 # 1 día
default_ttl = 604800 # 1 semana
max_ttl = 31536000 # 1 año
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.cdn.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
tags = {
Environment = "production"
Service = "cdn"
}
}
Headers Cache-Control
La infraestructura por sí sola no es el patrón: los headers que envía tu origen son el contrato que hace que el edge cache funcione:
# Assets estáticos con fingerprint - seguros de cachear "para siempre"
Cache-Control: public, max-age=31536000, immutable
# Páginas HTML - el navegador revalida, pero el CDN puede guardar una copia corta
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=60
# Respuestas personalizadas - nunca almacenar en el edge
Cache-Control: private, no-store
max-age habla con el cliente, mientras que s-maxage habla con los caches compartidos: el CDN. El truco clásico es max-age=0, s-maxage=300: el navegador siempre revalida para que el usuario vea HTML fresco, pero el edge guarda una copia de cinco minutos que absorbe el tráfico repetido. stale-while-revalidate va un paso más y permite que el edge sirva la copia obsoleta mientras la refresca en segundo plano, así que un origen lento nunca llega al usuario.
Explicación
Cómo fluye un request en realidad importa para todo lo que sigue. En modo pull (el default en CloudFront, Cloudflare y similares), el enrutamiento por DNS o anycast manda al usuario al punto de presencia más cercano. El edge busca el request en su cache: con HIT responde en milisegundos; con MISS lo reenvía aguas arriba (al shield regional si hay uno configurado, si no directo a tu origen) y luego guarda la respuesta según el Cache-Control y se la sirve gratis al siguiente request idéntico.
La cache key decide tu hit ratio. El CDN hashea la URL más los inputs del request que tú pongas en whitelist (parámetros de query, headers, cookies) para decidir si dos requests son “el mismo objeto”. Por defecto, no pongas nada en whitelist. Cada input extra multiplica las variantes cacheadas, y parámetros como ?utm_source=newsletter son el asesino clásico del hit ratio: quita los parámetros de tracking en el edge o fragmentarán tu cache en miles de entradas de un solo uso.
Tres temporizadores cooperan en la frescura. max-age es para navegadores, s-maxage es para caches compartidos, y stale-while-revalidate / stale-if-error compran resiliencia cuando el origen tiene problemas. La configuración de TTL del lado del CDN es un fallback para cuando el origen no manda headers, pero los headers del origen deberían ser la fuente de verdad, porque versionan con tus deploys en lugar de quedarse en un dashboard. La Guía Completa de Estrategia de Caching en CDN cubre el diseño de TTLs y claves en profundidad.
Planifica la invalidación antes de necesitarla. Las URLs con fingerprint (app.a3f8b2.js) son la respuesta limpia, porque la URL cambia cuando cambia el contenido, así que no hay nada que purgar. Para URLs estables, llama a la API de purge del proveedor desde el pipeline de deploy; las invalidaciones por path o wildcard se propagan en segundos o minutos según el vendor. El purgado por tags (surrogate keys de Fastly, cache tags de Cloudflare) es la opción precisa cuando muchos objetos expiran a la vez.
El origin shielding colapsa las estampidas. Sin él, cincuenta PoPs fallan a la vez en un deploy y tu origen se come cincuenta fetches concurrentes. Una capa shield designada lo convierte en un solo fetch, que es también por qué el shielding combina bien con health checks sobre el propio origen.
Variantes
| Variante | Caso de Uso | Ejemplo |
|---|---|---|
| CDN de assets estáticos | Imágenes, CSS, JS, fuentes | CloudFront + S3 |
| Aceleración de edge | Respuestas de API, páginas HTML | Cloudflare Argo, Fastly |
| Video streaming | Segmentos HLS/DASH, streams en vivo | AWS MediaPackage, Akamai |
| Edge computing | A/B testing, auth, personalización | Cloudflare Workers, Lambda@Edge |
| Multi-CDN | Resiliencia y optimización de costos | CloudFront + Fastly failover |
Lo que funciona
- Pon fingerprint a los assets y cachealos para siempre.
app.a3f8b2.jsconmax-age=31536000, immutablesignifica que cada deploy tiene una URL nueva y nunca purgas. Este único hábito elimina la mayoría del dolor de invalidación. - Ajusta el TTL a la volatilidad, no a un default. Assets versionados: un año; APIs: segundos según tolerancia a datos viejos; HTML: un
s-maxagecorto con revalidación. - Mantén la cache key mínima. Pon query params, headers y cookies en whitelist de forma deliberada. Cada input que añades multiplica variantes y arrastra el hit ratio hacia abajo.
- Planifica el fallback antes del lanzamiento. El origen tiene que mantenerse sano para servir si el CDN falla o muere: monitóralo directamente, no a través del CDN.
- Mira el cache-hit ratio, no solo la latencia. Por debajo de ~80% casi siempre es un problema de clave o TTL, no de tráfico. Fastly y Cloudflare lo exponen por path.
Errores Comunes
- Olvidar la ruta de invalidación. El bug clásico post-deploy: el HTML nuevo referencia
app.v2.jspero el CDN sigue sirviendo el HTML viejo porque nadie lo purgó. Automatiza el purge en el pipeline de deploy. - Cachear páginas personalizadas públicamente. Sin
private/no-store, el dashboard de un usuario puede servirse al siguiente. La mayoría de CDNs saltan el cache al verSet-CookieoAuthorization; verifica que el tuyo lo haga. - Dejar que los parámetros de tracking fragmenten el cache.
utm_*,fbclid,gclidcrean cada uno una entrada nueva para contenido idéntico. Quítalos en el edge. - Comprimir solo en el origen. Activa compresión en el edge (Brotli donde el cliente lo soporte) y deja que el CDN negocie por cliente.
- Tratar el CDN como un proxy. Si cada request es MISS, has construido un salto extra con factura incluida. Un hit ratio sano es lo que hace el trabajo.
Ejemplos del Mundo Real
Netflix Open Connect
Netflix opera sus propios appliances CDN dentro de redes de ISPs. Poner el contenido dentro de la infraestructura del propio ISP reduce costos de tránsito y entrega streams 4K con buffering mínimo. Es un recordatorio de que a suficiente escala, “usar un CDN” se convierte en “ser el CDN”.
StackPractices.com
Este sitio corre sobre GitHub Pages con Cloudflare delante. El HTML, CSS y JS estáticos se cachean en el edge de Cloudflare, lo que mantiene la carga del origen casi en cero y los tiempos de carga consistentes en todo el mundo.
Shopify Storefronts
Shopify sirve millones de storefronts a través de Fastly. Los assets de tema cacheados en ubicaciones edge entregan cargas de página sub-segundo a nivel global aunque la plataforma origen esté centralizada; el edge caching es lo que hace viable esa arquitectura en primer lugar.
Ver también
- Guía Completa: Estrategia de Caching en CDN — TTLs, claves de cache e invalidación en profundidad
- Plantilla de Reglas de Cache CDN — documento de política de cache listo para adaptar
- Repositorio companion — artefactos ejecutables: Terraform de CloudFront, herramientas de warmup/purge y recetas de Cache-Control
- Documentación de caching de AWS CloudFront
- RFC 9111: HTTP Caching
Preguntas frecuentes
¿Un CDN solo funciona para contenido estático?
No. Eso era cierto en la era temprana de Akamai. Los CDNs modernos aceleran respuestas en vivo terminando TLS más cerca del usuario, manteniendo conexiones calientes hacia tu origen, y enrutando por enlaces de backbone privados en lugar del internet público. Un cache miss sigue costando un viaje de ida y vuelta, pero es uno más rápido. El edge computing va más lejos y puede responder sin llamar al origen en absoluto.
¿Cómo manejo la invalidación de caché?
La mejor invalidación es la que nunca haces: pon fingerprint a los assets estáticos (app.a3f8b2.js) y la URL cambia cuando cambia el contenido. Para URLs estables, llama a la API de purge del CDN desde tu pipeline de deploy (por path o wildcard). El purgado por tags (surrogate keys de Fastly, cache tags de Cloudflare) es la opción precisa cuando muchos objetos relacionados expiran a la vez.
¿Cuál es la diferencia entre CDN pull y push?
Un CDN pull trae el contenido de tu origen en el primer request y cachea el resultado, así que hay cero trabajo inicial, y solo el primer visitante de cada ubicación edge paga la latencia del origen. Un CDN push te obliga a subir el contenido tú mismo; solo vale la pena para archivos muy grandes o contenido que casi no cambia. Casi todo CDN moderno es pull con origin shielding opcional.
¿Qué diferencia hay entre max-age y s-maxage?
max-age aplica al cliente; s-maxage aplica a los caches compartidos como el CDN y gana cuando ambos están presentes. Cache-Control: max-age=0, s-maxage=300 significa "navegador, revalida siempre; edge, guarda esto cinco minutos". Ese es el truco estándar para HTML que debe mantenerse fresco para el usuario pero cacheable en el edge.
¿Qué pasa si el CDN se cae?
Menos de lo que temes, más de lo que esperas. Los PoPs son masivamente redundantes, pero las caídas completas del proveedor pasan: la caída de Fastly en junio de 2021 dejó fuera una buena parte de la web durante una hora. Mitigaciones: un origen sano para servir directamente, failover por DNS o health checks, y para los realmente paranoicos, un segundo CDN en modo multi-CDN.
¿Debería usar un CDN para una aplicación interna?
Normalmente no. Si los usuarios están en una red corporativa a pocos saltos del origen, el edge añade un intermediario sin ganancia de latencia. Las excepciones: oficinas repartidas por continentes, o usar el edge para terminar TLS y auth antes de que el tráfico llegue a tu red.
Recursos Relacionados
Patrón de Hosting de Contenido Estático
Despliega archivos estáticos en una red de entrega de contenido o almacenamiento de objetos para descargar servidores de origen, reducir latencia y mejorar disponibilidad.
PatternPatron de Throttling
Limita la tasa a la que un sistema procesa solicitudes o consume recursos para prevenir sobrecarga, asegurar uso justo y mantener rendimiento predecible.
GuideGuía Completa de Estrategia de Caché en CDN
Caché en CDN para apps web y APIs: edge caching, claves de caché, headers, invalidación, surrogate keys, contenido obsoleto y multi-CDN.
PatternPatrón Gatekeeper: Seguridad Centralizada en el Borde
Centraliza autenticación, limitación de tasa y sanitización de entrada en el borde del sistema. Patrón Gatekeeper con ejemplos en Python, Java y JavaScript.
PatternPatrón de Consolidación de Recursos de Cómputo
Combina cargas de trabajo en menos recursos de cómputo para reducir costos, mejorar la utilización y simplificar las operaciones.
PatternPatron de Monitoreo de Endpoints de Salud
Expone endpoints de salud ligeros para que orquestadores, balanceadores de carga y herramientas de monitoreo verifiquen la disponibilidad del servicio.