Configuración de Caché HTTP en HAProxy

HAProxy incorpora desde la versión 1.8 un módulo de caché HTTP nativo que permite almacenar respuestas de los backends y servirlas directamente, reduciendo la latencia y la carga en los servidores de aplicación sin necesidad de herramientas adicionales. Con soporte para control de TTL, gestión de la cabecera Vary, y configuración de tamaño de caché, la caché de HAProxy es una solución ligera ideal para complementar arquitecturas de balanceo de carga en Linux.

Requisitos Previos

  • Ubuntu 20.04/22.04 o CentOS/Rocky Linux 8+
  • HAProxy 2.0 o superior (la caché es más robusta a partir de 2.x)
  • Al menos 512 MB de RAM dedicados a la caché
  • Backends HTTP funcionando correctamente
  • Acceso root o usuario con privilegios sudo

Instalación de HAProxy

# Ubuntu/Debian: instalar HAProxy desde repositorio oficial
sudo apt-get install -y software-properties-common
sudo add-apt-repository ppa:vbernat/haproxy-2.8
sudo apt-get update
sudo apt-get install -y haproxy=2.8.\*

# CentOS/Rocky Linux
sudo yum install -y haproxy

# Verificar versión instalada
haproxy -v

# Habilitar e iniciar el servicio
sudo systemctl enable haproxy
sudo systemctl start haproxy

Configuración Básica de Caché

La caché en HAProxy se define en tres secciones: global, cache y las referencias en frontend/backend:

sudo cat > /etc/haproxy/haproxy.cfg << 'EOF'
# ================================================================
# Configuración de HAProxy con Caché HTTP
# ================================================================

global
    log /dev/log local0
    log /dev/log local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
    stats timeout 30s
    user haproxy
    group haproxy
    daemon
    
    # Máximas conexiones globales
    maxconn 50000

defaults
    log global
    mode http
    option httplog
    option dontlognull
    timeout connect 5s
    timeout client  50s
    timeout server  50s
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 503 /etc/haproxy/errors/503.http

# ================================================================
# DEFINICIÓN DE LA CACHÉ
# ================================================================
cache mi_cache
    # Tamaño total de la caché en bytes (aquí: 100 MB)
    total-max-size 100
    
    # Tamaño máximo de un objeto individual (en bytes)
    max-object-size 1048576
    
    # Tiempo de vida por defecto (segundos) cuando el backend no especifica
    max-age 60
    
    # Número máximo de entradas en la caché
    # (cada entrada tiene un overhead de ~200 bytes en memoria)

# ================================================================
# FRONTEND: Punto de entrada de peticiones
# ================================================================
frontend http_frontend
    bind *:80
    bind *:443 ssl crt /etc/haproxy/certs/
    
    # Redirigir HTTP a HTTPS
    http-request redirect scheme https unless { ssl_fc }
    
    # Activar compresión HTTP
    compression algo gzip
    compression type text/html text/plain text/css application/json
    
    default_backend app_servers

# ================================================================
# BACKEND: Servidores de aplicación con caché habilitada
# ================================================================
backend app_servers
    balance roundrobin
    
    # ---- ACTIVAR CACHÉ EN ESTE BACKEND ----
    http-request cache-use mi_cache
    http-response cache-store mi_cache
    
    # Servidores backend
    server app1 192.168.1.10:8080 check inter 5s rise 2 fall 3
    server app2 192.168.1.11:8080 check inter 5s rise 2 fall 3
    server app3 192.168.1.12:8080 check inter 5s rise 2 fall 3 backup

EOF

sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl restart haproxy

Gestión del TTL

Controlar cuánto tiempo se almacenan las respuestas:

# Añadir reglas de TTL al bloque backend
sudo cat >> /etc/haproxy/haproxy.cfg << 'EOF'

# ================================================================
# BACKEND CON CONTROL GRANULAR DE TTL
# ================================================================
backend api_backend
    balance leastconn
    
    # Activar caché
    http-request cache-use mi_cache
    http-response cache-store mi_cache
    
    # Reglas de TTL por tipo de respuesta
    # (Estas reglas añaden/modifican la cabecera Cache-Control antes de almacenar)
    
    # Para respuestas JSON de la API: cachear 30 segundos
    http-response set-header Cache-Control "public, max-age=30" \
        if { res.hdr(Content-Type) -m sub application/json }
    
    # Para imágenes: cachear 1 hora
    http-response set-header Cache-Control "public, max-age=3600" \
        if { res.hdr(Content-Type) -m beg image/ }
    
    # Para respuestas de error: no cachear
    http-response set-header Cache-Control "no-store" \
        if { status 400:599 }
    
    # Para rutas específicas: TTL corto
    http-response set-header Cache-Control "public, max-age=10" \
        if { path_beg /api/realtime/ }
    
    server api1 127.0.0.1:3000 check
    server api2 127.0.0.1:3001 check

EOF

Configurar TTL máximo en la definición de la caché:

# Editar la sección cache para establecer límites
cat > /tmp/cache-config.txt << 'EOF'
cache mi_cache
    total-max-size 200    # 200 MB total
    max-object-size 2097152  # 2 MB por objeto
    max-age 300          # TTL máximo: 5 minutos (anula valores mayores del backend)
    # HAProxy respeta el menor de: max-age del backend o este valor
EOF

echo "Añadir esta configuración a /etc/haproxy/haproxy.cfg"

Gestión de la Cabecera Vary

La cabecera Vary indica que la respuesta varía según cabeceras del cliente:

# HAProxy gestiona automáticamente la caché cuando el backend envía Vary
# Por ejemplo: Vary: Accept-Encoding, Accept-Language

# Ver si el backend envía Vary
curl -I https://mi-app.com/api/datos | grep -i vary

# Configurar límite de variantes en la caché
# En el bloque cache, añadir:
# process-vary on  (activo por defecto en HAProxy 2.x)

# Ejemplo: backend que varía por idioma
# Si el backend envía "Vary: Accept-Language", HAProxy almacena
# versiones separadas para cada idioma

# Para deshabilitar el soporte de Vary (si el backend no lo gestiona bien):
# En el bloque backend:
# http-response del-header Vary

# Para forzar una variante específica:
# http-request set-header Accept-Language "es" if { req.hdr(Accept-Language) -m sub es }

Ejemplo de configuración para contenido que varía:

# En el bloque backend del servidor de contenido:
backend content_backend
    # Caché con soporte para Vary
    http-request cache-use mi_cache
    http-response cache-store mi_cache
    
    # Normalizar Accept-Encoding para reducir variantes en caché
    # (solo permitir gzip o identity)
    http-request set-header Accept-Encoding "gzip" \
        if { req.hdr(Accept-Encoding) -m sub gzip }
    http-request set-header Accept-Encoding "" \
        if ! { req.hdr(Accept-Encoding) -m sub gzip }
    
    server content1 127.0.0.1:4000 check

Optimización del Tamaño

Calibrar la caché según el hardware disponible:

# Calcular el tamaño óptimo de la caché

# Regla general:
# - Reservar máximo el 30-40% de la RAM disponible para la caché
# - Para 4 GB de RAM → máximo 1.2 GB de caché
# - Para 8 GB de RAM → máximo 2.4 GB de caché

# Ver RAM disponible
free -h

# Estimar número de objetos que caben en la caché
# Overhead por objeto: ~200 bytes de metadatos + tamaño del objeto
# Para una caché de 100 MB con objetos de 10 KB de promedio:
# 100 MB / 10 KB = ~10.000 objetos

# Configuración optimizada para 500 MB de caché
cat > /etc/haproxy/conf.d/cache.cfg << 'EOF'
cache mi_cache
    # 500 MB de caché total
    total-max-size 500
    
    # Objetos de hasta 5 MB (para imágenes grandes)
    max-object-size 5242880
    
    # TTL máximo de 10 minutos
    max-age 600
EOF

# Incluir el archivo adicional de configuración
# Añadir en haproxy.cfg: include /etc/haproxy/conf.d/*.cfg

Monitorizar el uso de memoria de la caché:

# Ver estadísticas via el socket de administración
echo "show cache" | sudo socat stdio /run/haproxy/admin.sock

# Salida típica:
# 0x... mi_cache: 45 entries (42 unexpired), 1024000/104857600 bytes used
# Aquí: 45 entradas, 42 activas, 1 MB de 100 MB usados

Caché en Frontend y Backend

Configurar la caché con reglas diferentes según la ruta:

sudo cat > /etc/haproxy/haproxy.cfg << 'HAPROXY_EOF'
global
    log /dev/log local0
    maxconn 50000
    daemon

defaults
    mode http
    log global
    option httplog
    timeout connect 5s
    timeout client 50s
    timeout server 50s

# ----------------------------------------------------------------
# Dos zonas de caché con diferentes configuraciones
# ----------------------------------------------------------------
cache cache_estatica
    total-max-size 500    # 500 MB para contenido estático
    max-object-size 10485760  # 10 MB por objeto
    max-age 86400         # 24 horas

cache cache_api
    total-max-size 50     # 50 MB para respuestas de API
    max-object-size 65536  # 64 KB por objeto de API
    max-age 60            # 60 segundos

# ----------------------------------------------------------------
# Frontend principal
# ----------------------------------------------------------------
frontend web
    bind *:443 ssl crt /etc/haproxy/certs/
    
    # Usar ACL para enrutar a diferentes backends
    acl es_api path_beg /api/
    acl es_estatico path_reg \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg)$
    
    use_backend api_cached if es_api
    use_backend static_cached if es_estatico
    default_backend app_default

# ----------------------------------------------------------------
# Backend para API con caché de corta duración
# ----------------------------------------------------------------
backend api_cached
    # Usar caché de API (TTL corto)
    http-request cache-use cache_api
    http-response cache-store cache_api
    
    # No cachear peticiones autenticadas
    http-request cache-use cache_api unless { req.hdr(Authorization) -m found }
    http-response cache-store cache_api unless { req.hdr(Authorization) -m found }
    
    server api1 127.0.0.1:3000 check
    server api2 127.0.0.1:3001 check

# ----------------------------------------------------------------
# Backend para contenido estático con caché larga
# ----------------------------------------------------------------
backend static_cached
    # Usar caché estática (TTL largo)
    http-request cache-use cache_estatica
    http-response cache-store cache_estatica
    
    # Añadir cabecera de estado de caché para diagnóstico
    http-response set-header X-HAProxy-Cache "HIT" if { res.age gt 0 }
    
    server static1 127.0.0.1:9000 check

# ----------------------------------------------------------------
# Backend por defecto (sin caché)
# ----------------------------------------------------------------
backend app_default
    server app1 127.0.0.1:8080 check
    server app2 127.0.0.1:8081 check

HAPROXY_EOF

sudo haproxy -c -f /etc/haproxy/haproxy.cfg
sudo systemctl reload haproxy

Monitorización de la Caché

Verificar el funcionamiento y rendimiento de la caché:

# Habilitar el panel de estadísticas de HAProxy
sudo cat >> /etc/haproxy/haproxy.cfg << 'EOF'

# Panel de estadísticas de HAProxy
listen stats
    bind *:8404
    stats enable
    stats uri /stats
    stats refresh 10s
    stats show-node
    stats realm "HAProxy Statistics"
    stats auth admin:password_stats_seguro
    # Información de caché disponible en el panel
EOF

sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy

# Acceder al panel: http://servidor:8404/stats

# Comandos via socket de administración
# Ver estado de la caché
echo "show cache" | sudo socat stdio /run/haproxy/admin.sock

# Ver estadísticas completas
echo "show info" | sudo socat stdio /run/haproxy/admin.sock | grep -i cache

# Script de monitorización de caché
cat > /usr/local/bin/haproxy-cache-monitor.sh << 'EOF'
#!/bin/bash
# Monitorización de caché HAProxy

SOCKET="/run/haproxy/admin.sock"

echo "=== Estado de la Caché HAProxy ==="
echo ""
echo "show cache" | socat stdio "$SOCKET"
echo ""
echo "=== Estadísticas de conexiones ==="
echo "show info" | socat stdio "$SOCKET" | grep -E "MaxConn|CurrConns|Requests"
EOF

chmod +x /usr/local/bin/haproxy-cache-monitor.sh

# Verificar tasa de aciertos via el log
sudo tail -f /var/log/haproxy.log | grep -oP '"[A-Z]+ [^"]*" [0-9]+ [0-9]+'

Solución de Problemas

La caché no almacena respuestas:

# Verificar que HAProxy 2.x está instalado
haproxy -v

# La caché HTTP de HAProxy solo almacena respuestas que:
# 1. Son respuestas a GET o HEAD (no POST)
# 2. El código de respuesta es cacheble (200, 206, 301, 302, etc.)
# 3. El backend envía Cache-Control con max-age > 0
# 4. No tienen Set-Cookie en la respuesta
# 5. No tienen Authorization en la petición

# Verificar que el backend envía cabeceras cacheables
curl -I https://mi-app.com/datos | grep -E "Cache-Control|Set-Cookie"

Error "cache section not found":

# Verificar la versión de HAProxy (caché requiere >= 1.8)
haproxy -v | grep version

# Verificar que la sección cache está en el archivo correcto
sudo haproxy -c -f /etc/haproxy/haproxy.cfg 2>&1

La caché crece demasiado rápido:

# Reducir el TTL máximo
# En la sección cache: max-age 30

# Reducir el tamaño máximo por objeto
# max-object-size 65536 (64 KB)

# Ver qué URLs se cachean más
echo "show cache" | sudo socat stdio /run/haproxy/admin.sock

Conclusión

La caché HTTP integrada en HAProxy proporciona una solución eficiente para reducir la carga en los backends sin necesidad de herramientas adicionales como Varnish o Nginx, simplificando la arquitectura de la infraestructura. Su integración nativa con el sistema de balanceo de carga permite reglas sofisticadas que combinan decisiones de enrutamiento y caché en un único componente, reduciendo la latencia de respuesta y mejorando la resiliencia del sistema ante picos de tráfico en entornos de producción Linux.