Cuándo usar ElastiCache Redis: acelera lecturas lentas en RDS con una capa de caché

Tu base de datos RDS empieza a mostrar latencias altas y el dashboard de CloudWatch confirma lo que ya sospechabas: las mismas consultas se ejecutan cientos de veces por minuto devolviendo exactamente los mismos datos. Añadir ElastiCache Redis como capa de caché delante de RDS es una de las intervenciones más efectivas en producción para este patrón específico, pero aplicarla mal genera inconsistencias de datos que son difíciles de depurar.

TL;DR — Cuándo usar ElastiCache Redis con RDS

SeñalIndica cachéAcción
Mismas consultas SELECT repetidas con alta frecuenciaCache-aside sobre esas queries
Datos que cambian raramente (catálogos, configuración)TTL largo, invalidación explícita
Datos de sesión de usuarioRedis como session store
Escrituras frecuentes con lectura inmediata del mismo registroCon cuidadoWrite-through o invalidar en escritura
Consultas con JOINs complejos y resultados únicos por usuarioPocoEvaluar hit ratio antes de implementar

Cómo funciona ElastiCache Redis como capa de caché

ElastiCache Redis es un servicio gestionado que ejecuta Redis en infraestructura AWS. Desde la perspectiva de tu aplicación, es un servidor Redis estándar accesible por endpoint dentro de tu VPC. No existe integración automática entre RDS y ElastiCache — la lógica de caché vive completamente en el código de tu aplicación.

El patrón más común en producción es cache-aside (también llamado lazy loading): la aplicación consulta Redis primero; si el dato existe (cache hit), lo devuelve directamente sin tocar RDS; si no existe (cache miss), consulta RDS, almacena el resultado en Redis con un TTL, y devuelve la respuesta. Redis nunca escribe en RDS ni RDS notifica a Redis — esa coordinación es responsabilidad tuya.

graph TD A["Cliente / Aplicación"] --> B{"¿Clave en Redis?"} B -- "Cache HIT" --> C["Devolver dato desde Redis"] B -- "Cache MISS" --> D["Consultar RDS"] D --> E["Obtener resultado"] E --> F["Escribir en Redis con TTL"] F --> G["Devolver dato al cliente"] C --> H["Respuesta rápida
RDS no consultado"] G --> I["Respuesta normal
RDS consultado una vez"]
  1. Cache hit: La aplicación consulta Redis y obtiene el dato directamente. RDS no recibe ninguna query.
  2. Cache miss: Redis no tiene el dato. La aplicación consulta RDS, obtiene el resultado, lo escribe en Redis con TTL y responde al cliente.
  3. Expiración TTL: Cuando el TTL vence, Redis elimina la clave. La siguiente petición genera un cache miss y el ciclo se repite.
  4. Invalidación explícita: Cuando la aplicación escribe en RDS, debe eliminar o actualizar la clave correspondiente en Redis para evitar datos obsoletos.

Arquitectura de despliegue en AWS

ElastiCache Redis y RDS deben estar en la misma VPC para minimizar latencia y evitar tráfico público. Los Security Groups son el control de acceso principal: el SG de tu capa de aplicación debe tener permiso de salida al puerto 6379 del SG de ElastiCache.

graph LR subgraph Internet CLT["Clientes"] end subgraph VPC_Privada["VPC — Subnets privadas"] APP["Capa de aplicación
EC2 / ECS / Lambda"] REDIS["ElastiCache Redis
Replication Group
Puerto 6379"] RDS["Amazon RDS
PostgreSQL / MySQL
Puerto 5432 / 3306"] end CLT --> APP APP -- "1. Consulta caché" --> REDIS REDIS -- "Cache HIT" --> APP APP -- "Cache MISS" --> RDS RDS -- "Resultado" --> APP APP -- "Escribe en caché" --> REDIS
  1. Clientes: Peticiones HTTP/S que llegan a la capa de aplicación (EC2, ECS, Lambda).
  2. ElastiCache Redis: Cluster en modo Single-AZ o Multi-AZ con réplica de lectura, dentro de la misma VPC y subnet privada.
  3. RDS: Solo recibe tráfico cuando hay cache miss. Con una tasa de hit ratio alta, la carga sobre RDS cae drásticamente.
  4. Security Groups: El SG de la aplicación tiene regla de salida al SG de ElastiCache (puerto 6379) y al SG de RDS (puerto 3306 o 5432).

Implementación del patrón cache-aside con ElastiCache Redis

Antes de escribir código, necesitas el endpoint de tu cluster ElastiCache. Puedes obtenerlo con la CLI una vez creado el cluster:

aws elasticache describe-cache-clusters \
  --cache-cluster-id mi-cluster-redis \
  --show-cache-node-info \
  --query 'CacheClusters[0].CacheNodes[0].Endpoint' \
  --region us-east-1

Si usas un Replication Group (recomendado para producción), el endpoint primario se obtiene así:

aws elasticache describe-replication-groups \
  --replication-group-id mi-replication-group \
  --query 'ReplicationGroups[0].NodeGroups[0].PrimaryEndpoint' \
  --region us-east-1

El siguiente ejemplo en Python implementa cache-aside con redis-py. El TTL y la serialización son las dos decisiones que más impactan en la corrección del sistema:

🔽 Código Python — patrón cache-aside con ElastiCache Redis
import json
import redis
import psycopg2

# Configuración de conexiones
redis_client = redis.Redis(
    host='mi-replication-group.abc123.ng.0001.use1.cache.amazonaws.com',
    port=6379,
    decode_responses=True,
    ssl=True  # Recomendado en producción con in-transit encryption
)

def get_product(product_id: int) -> dict:
    cache_key = f'product:{product_id}'

    # 1. Intentar obtener de Redis
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)  # Cache hit

    # 2. Cache miss — consultar RDS
    conn = psycopg2.connect(
        host='mi-rds.abc123.us-east-1.rds.amazonaws.com',
        database='tienda',
        user='appuser',
        password='...',
        port=5432
    )
    with conn.cursor() as cur:
        cur.execute(
            'SELECT id, nombre, precio, stock FROM productos WHERE id = %s',
            (product_id,)
        )
        row = cur.fetchone()

    if not row:
        return None

    product = {
        'id': row[0],
        'nombre': row[1],
        'precio': float(row[2]),
        'stock': row[3]
    }

    # 3. Almacenar en Redis con TTL de 300 segundos
    redis_client.setex(
        name=cache_key,
        time=300,
        value=json.dumps(product)
    )

    return product  # Cache miss resuelto


def update_product_price(product_id: int, new_price: float) -> None:
    # Actualizar en RDS
    conn = psycopg2.connect(
        host='mi-rds.abc123.us-east-1.rds.amazonaws.com',
        database='tienda',
        user='appuser',
        password='...',
        port=5432
    )
    with conn.cursor() as cur:
        cur.execute(
            'UPDATE productos SET precio = %s WHERE id = %s',
            (new_price, product_id)
        )
    conn.commit()

    # Invalidar clave en Redis para evitar datos obsoletos
    cache_key = f'product:{product_id}'
    redis_client.delete(cache_key)

Creación del cluster ElastiCache Redis con la CLI

Para entornos de producción, un Replication Group con al menos una réplica de lectura y Multi-AZ habilitado es el punto de partida mínimo. Un cluster de nodo único no tiene failover automático.

🔽 CLI — crear Replication Group ElastiCache Redis
aws elasticache create-replication-group \
  --replication-group-id mi-replication-group \
  --replication-group-description 'Cache de lecturas para RDS produccion' \
  --num-cache-clusters 2 \
  --cache-node-type cache.r7g.large \
  --engine redis \
  --engine-version 7.1 \
  --cache-subnet-group-name mi-subnet-group-privado \
  --security-group-ids sg-0abc123def456789a \
  --automatic-failover-enabled \
  --multi-az-enabled \
  --at-rest-encryption-enabled \
  --transit-encryption-enabled \
  --region us-east-1

El subnet group debe crearse previamente referenciando subnets privadas de tu VPC:

aws elasticache create-cache-subnet-group \
  --cache-subnet-group-name mi-subnet-group-privado \
  --cache-subnet-group-description 'Subnets privadas para ElastiCache' \
  --subnet-ids subnet-0abc123 subnet-0def456 \
  --region us-east-1

Permisos IAM necesarios para gestionar ElastiCache Redis

Las operaciones de plano de control de ElastiCache (crear, describir, modificar clusters) requieren permisos IAM. El acceso de datos al puerto Redis en sí se controla mediante Security Groups, no IAM. Esta distinción es importante: un rol con permisos IAM de ElastiCache pero sin regla en el SG no puede conectarse al puerto 6379.

🔽 Política IAM — permisos mínimos para gestión de ElastiCache
{
  'Version': '2012-10-17',
  'Statement': [
    {
      'Sid': 'ElastiCacheReadDescribe',
      'Effect': 'Allow',
      'Action': [
        'elasticache:DescribeCacheClusters',
        'elasticache:DescribeReplicationGroups',
        'elasticache:DescribeCacheSubnetGroups',
        'elasticache:ListTagsForResource'
      ],
      'Resource': '*'
    },
    {
      'Sid': 'ElastiCacheModify',
      'Effect': 'Allow',
      'Action': [
        'elasticache:CreateReplicationGroup',
        'elasticache:ModifyReplicationGroup',
        'elasticache:DeleteReplicationGroup',
        'elasticache:CreateCacheSubnetGroup',
        'elasticache:AddTagsToResource'
      ],
      'Resource': 'arn:aws:elasticache:us-east-1:123456789012:replicationgroup:mi-replication-group'
    }
  ]
}

Las acciones Describe* y List* de ElastiCache requieren Resource: * porque no admiten restricción a nivel de recurso individual según la Service Authorization Reference de AWS.

Métricas clave para validar que ElastiCache Redis está funcionando

Después del despliegue, el hit ratio es la métrica que confirma si la caché está siendo efectiva. Si el hit ratio es bajo, la caché está añadiendo latencia sin reducir carga en RDS — exactamente lo contrario de lo que buscas.

ElastiCache publica métricas en CloudWatch. Las más relevantes para este escenario:

aws cloudwatch get-metric-statistics \
  --namespace AWS/ElastiCache \
  --metric-name CacheHits \
  --dimensions Name=CacheClusterId,Value=mi-replication-group-0001-001 \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-15T01:00:00Z \
  --period 300 \
  --statistics Sum \
  --region us-east-1
aws cloudwatch get-metric-statistics \
  --namespace AWS/ElastiCache \
  --metric-name CacheMisses \
  --dimensions Name=CacheClusterId,Value=mi-replication-group-0001-001 \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-15T01:00:00Z \
  --period 300 \
  --statistics Sum \
  --region us-east-1

Calcula el hit ratio manualmente: CacheHits / (CacheHits + CacheMisses). Un ratio por debajo del 80% en un caso de uso de lectura repetitiva sugiere que las claves de caché no están bien diseñadas o el TTL es demasiado corto para el patrón de acceso real.

El error que más se repite en producción con ElastiCache Redis

El escenario más frecuente que genera incidentes: el equipo despliega cache-aside, el hit ratio sube al 90%, todo parece bien. Tres días después, los usuarios reportan que ven precios desactualizados. El equipo revisa RDS — los datos están correctos. Revisa Redis — los datos están obsoletos. La causa: la lógica de invalidación en escritura no cubre todos los paths de actualización.

En concreto, la actualización de precio se hacía desde dos lugares: la API principal (que sí invalidaba la clave Redis) y un job batch nocturno que actualizaba directamente en RDS sin pasar por la capa de aplicación. El job batch nunca llamaba a redis_client.delete(). Los datos en Redis permanecían válidos según el TTL (24 horas en ese caso), pero eran incorrectos.

Redis no sabe que RDS cambió. No hay canal de notificación entre ellos. Si algo escribe en RDS sin pasar por tu capa de invalidación, Redis servirá datos obsoletos hasta que expire el TTL. Cada path de escritura en tu sistema debe invalidar Redis — sin excepciones.

La corrección fue reducir el TTL a un valor aceptable para el negocio (5 minutos en ese caso) y añadir la llamada de invalidación al job batch. El TTL actúa como red de seguridad, no como mecanismo principal de consistencia.

graph TD subgraph PathA["Path A — API (correcto)"] A1["Escritura en RDS"] --> A2["redis.delete(clave)"] A2 --> A3["Redis actualizado"] end subgraph PathB["Path B — Job batch (incorrecto)"] B1["Escritura en RDS"] --> B2["Sin invalidación Redis"] B2 --> B3["Redis sirve dato obsoleto
hasta expirar TTL"] end A3 --> OK["Consistencia garantizada"] B3 --> ERR["Ventana de inconsistencia
= TTL restante"]
  1. Path A (API): Actualiza RDS e invalida Redis correctamente. Los usuarios ven el dato actualizado en la siguiente petición.
  2. Path B (job batch sin invalidación): Actualiza RDS pero no toca Redis. Redis sigue sirviendo el valor anterior hasta que el TTL expira. Ventana de inconsistencia = TTL restante.
  3. Corrección: Todo path de escritura debe incluir la invalidación de Redis. El TTL es la red de seguridad, no el mecanismo principal.

Cuándo ElastiCache Redis no es la solución correcta

La caché no resuelve todos los problemas de rendimiento en RDS. Antes de añadir complejidad operacional, verifica si el problema real es otro:

  • Queries lentas por falta de índices: Una query que tarda 3 segundos con índice incorrecto seguirá tardando 3 segundos en el primer cache miss. Soluciona el índice primero.
  • Datos altamente personalizados por usuario: Si cada usuario ve datos únicos, el hit ratio será cercano a cero. La caché no ayuda.
  • Escrituras muy frecuentes sobre los mismos registros: Si el dato cambia cada pocos segundos, el TTL tiene que ser tan corto que la caché pierde utilidad.
  • Conexiones agotadas en RDS: Si el problema es too many connections, considera RDS Proxy antes que ElastiCache.

Próximos pasos y recursos para ElastiCache Redis en producción

Con la capa de caché funcionando y el hit ratio validado, los siguientes pasos operacionales son configurar alarmas CloudWatch sobre EngineCPUUtilization y DatabaseMemoryUsagePercentage, revisar la política de evicción (maxmemory-policy) según tu patrón de acceso, y evaluar si necesitas Redis Cluster Mode para escalar horizontalmente.

Glosario de términos clave — ElastiCache Redis

TérminoDefinición operacional
Cache-aside (Lazy Loading)Patrón donde la aplicación gestiona la caché: consulta Redis primero, carga desde RDS en caso de miss y almacena el resultado en Redis.
Cache hit / Cache missHit: el dato solicitado existe en Redis. Miss: no existe y debe consultarse la fuente de datos original.
TTL (Time To Live)Tiempo en segundos que Redis mantiene una clave antes de eliminarla automáticamente. Controla la ventana máxima de datos obsoletos.
Replication GroupConfiguración de ElastiCache con nodo primario y réplicas de lectura. Soporta failover automático con Multi-AZ.
Eviction Policy (maxmemory-policy)Comportamiento de Redis cuando la memoria está llena. Define qué claves eliminar para dar espacio a nuevas entradas.

Related Posts

Comentarios

Entradas populares de este blog

EC2 sin acceso a Internet en VPC personalizada: Internet Gateway y Route Table

Actualizar Contenido en CloudFront: Cómo Crear una Invalidación para Limpiar el Caché del Edge

Aumentar el Timeout de Lambda: Configuración, Límites y Diagnóstico en Producción