Patrón Content Delivery Network (CDN)
Distribuye contenido estático y dinámico a través de servidores edge geográficamente dispersos para reducir latencia, mejorar disponibilidad y descargar infraestructura de origen.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Patrón Content Delivery Network (CDN)
Descripción General
El Patrón Content Delivery Network (CDN) distribuye contenido a través de una red geográficamente dispersa de servidores edge, colocando copias cacheadas de assets más cerca de los usuarios finales. En lugar de que cada request viaje a un único servidor de origen, los usuarios son enrutados a la ubicación edge más cercana, reduciendo dramáticamente la latencia, mejorando la disponibilidad y descargando tráfico de la infraestructura de origen.
Los CDNs sirven contenido estático (imágenes, CSS, JavaScript, videos) desde caches edge e incrementalmente soportan aceleración de contenido dinámico, edge computing (Cloudflare Workers, Lambda@Edge), y protección DDoS. Un CDN bien configurado puede reducir los tiempos de carga de página en un 50% o más y absorber picos de tráfico que abrumarían un servidor de origen.
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 tráfico significativo
- Necesitas manejar picos de tráfico sin escalar infraestructura de origen
- Se requiere protección DDoS y funcionalidad WAF en el edge
- El edge computing (A/B testing, geo-routing, autenticación) es beneficioso
Cuándo Evitar
- Todos los usuarios están en la misma región geográfica que el servidor de origen
- El contenido es altamente dinámico y no puede ser cacheado (datos personalizados en tiempo real)
- La aplicación es completamente interna sin usuarios externos
- La complejidad de invalidación de cache supera el beneficio de latencia
Solución
JavaScript (CloudFront + S3 Origin)
// AWS SDK v3 configuration para invalidación de CDN
import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront';
const cloudfront = new CloudFrontClient({ region: 'us-east-1' });
/**
* Invalida cache de CDN para paths específicos después de 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 deployment de 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 de CDN para cache warmup, purging 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-pobla caches edge de CDN solicitando a través de 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:
"""Purge un path específico del cache de CDN"""
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 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 de 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)} exitoso")
# Verificar estado de 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
# Origin: S3 bucket 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
}
}
# Origin: 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"]
}
}
# Default cache behavior: 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
}
# Ordered cache behavior: API calls 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"
}
}
Explicación
Un CDN opera sobre tres principios:
- Distribución geográfica: Servidores edge en cientos de ubicaciones mundiales sirven contenido desde el punto más cercano al usuario
- Jerarquía de caching: El contenido se cachea en edge, regional y tiers de origen con expiración basada en TTL
- Enrutamiento de requests: Enrutamiento basado en DNS o anycast dirige usuarios a la ubicación edge óptima
El comportamiento de cache se controla a través de:
- Headers TTL:
Cache-Control: max-age=3600le dice al CDN cuánto tiempo cachear - Claves de cache: Identificadores únicos que determinan cuándo el contenido se considera idéntico
- Invalidación: Eliminación explícita de contenido cacheado cuando se vuelve obsoleto
Variantes
| Variante | Caso de Uso | Ejemplo |
|---|---|---|
| CDN de assets estáticos | Imágenes, CSS, JS, fuentes | CloudFront + S3 |
| Aceleración dinámica | 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 costo | CloudFront + Fastly failover |
Mejores Prácticas
- Usa nombres de archivo versionados para cache-busting.
app.v2.jsen lugar deapp.jscon caching agresivo. - Establece TTLs apropiados. Assets estáticos: 1 año. HTML: corto o sin cache. API: dependiente de contexto.
- Configura claves de cache personalizadas cuidadosamente. Parámetros de query, headers y cookies afectan la tasa de cache hit.
- Implementa degradación graceful. Si el CDN falla, los requests deberían fallback al origen.
- Monitorea el ratio de cache hit. Por debajo de 80% sugiere problemas de configuración; por encima de 95% es excelente.
Errores Comunes
- Olvidar invalidar después de deployment. Los usuarios ven contenido obsoleto porque el cache no fue purgado.
- Over-caching de contenido dinámico. Páginas personalizadas cacheadas públicamente filtran datos entre usuarios.
- Ignorar variaciones de clave de cache.
?utm_source=xy?utm_source=ycrean entradas cacheadas duplicadas. - No comprimir en el edge. Gzip/Brotli debería ser aplicado por el CDN, no solo el origen.
- Single point of failure. Usar un único proveedor CDN sin fallback a origen es riesgoso.
Ejemplos del Mundo Real
Netflix Open Connect
Netflix despliega sus propios appliances CDN dentro de redes ISP. Esto reduce costos de tránsito y entrega video 4K con buffering mínimo colocando contenido dentro de la infraestructura del ISP.
StackPractices.com
Este sitio usa GitHub Pages con Cloudflare al frente. HTML estático, CSS y JS son cacheados en edge de Cloudflare, reduciendo carga de origen y mejorando tiempos de carga globales.
Shopify Storefronts
Shopify usa Fastly para servir millones de storefronts. Los assets de tema de cada tienda son cacheados en ubicaciones edge, habilitando tiempos de carga de página sub-segundo globalmente aunque la plataforma origen esté centralizada.
Preguntas Frecuentes
Q: Un CDN solo funciona para contenido estático? A: No. Los CDNs modernos aceleran contenido dinámico optimizando conexiones TCP, routing y terminación TLS. El edge computing también habilita lógica dinámica en el edge.
Q: Cómo manejo la invalidación de cache?
A: Usa nombres de archivo versionados para assets estáticos (inmutable). Para recursos nombrados, usa APIs de purge de CDN o establece TTLs cortos. Un patrón común es Cache-Control: max-age=0, s-maxage=3600 para CDNs mientras mantienes el navegador sin cachear.
Q: Cuál es la diferencia entre pull y push CDN? A: Los CDNs pull obtienen contenido del origen en el primer request. Los CDNs push requieren que subas contenido explícitamente. La mayoría de CDNs modernos son pull-based con shielding de origen opcional.
Q: Debería usar un CDN para una aplicación interna? A: Usualmente no, a menos que los usuarios estén distribuidos entre oficinas. Las aplicaciones internas típicamente se benefician más de optimizar el origen que de la distribución geográfica.