MySQL vs Redis: Redis no es tu base de datos — pero puede reducir la carga de MySQL en un 80%

¿Te fue útil?

MySQL está sudando, tus CPUs están al máximo y el equipo de producto acaba de “lanzar una pequeña funcionalidad” que de alguna manera duplicó el tráfico. Añades réplicas. Luego más réplicas. Y después descubres que el cuello de botella no eran las escrituras: tus lecturas están haciendo el trabajo más estúpido imaginable, millones de veces por hora.

Redis no salvará un esquema roto ni consultas malas. Pero si tu carga consiste mayormente en lecturas repetibles, objetos calculados, búsquedas de sesión, comprobaciones de límite de tasa o “¿tiene este usuario el permiso X?”, Redis puede reducir enormemente el tráfico hacia MySQL. Bien hecho, un 80% no es un cuento de hadas. Es martes.

El mito: “Redis vs MySQL” es la pelea equivocada

Cuando la gente dice “MySQL vs Redis”, normalmente quieren decir “estoy harto de que MySQL sea lento, ¿puedo cambiarlo por algo más rápido?” Eso es como reemplazar tu departamento de contabilidad por una caja registradora porque la registradora es más rápida. La registradora es fantástica. También no es un departamento de contabilidad.

MySQL es una base de datos relacional duradera con transacciones, restricciones, índices secundarios, joins y décadas de memoria operacional. Redis es un servidor de estructuras de datos en memoria: extremadamente rápido, predecible con baja latencia y excelente para datos efímeros o derivados que puedes reconstruir.

Así que la pregunta correcta no es cuál “gana”. Es:

  • ¿Qué datos deben ser correctos, duraderos y consultables de maneras ricas? Ponlos en MySQL.
  • ¿Qué datos están calientes, son repetitivos, derivados, temporales o se usan para coordinación? Ponlos en Redis.
  • ¿De dónde viene la carga: coste de CPU por ejecutar consultas, I/O de disco, bloqueos, viajes de red o comportamiento de la aplicación?

Redis no es tu base de datos principal—todavía. En la mayoría de sistemas no debería serlo. Pero Redis puede ser tu mejor multiplicador de rendimiento, porque cambia la forma de tu carga: menos consultas caras, menos viajes de ida y vuelta, menos contención y menos presión sobre el buffer pool y los discos.

Para qué es realmente bueno cada sistema

MySQL: la fuente de la verdad con bordes afilados

MySQL está diseñado para corrección y almacenamiento a largo plazo. Persiste datos en disco, registra cambios y proporciona semántica transaccional. También te permite hacer preguntas complicadas, que es precisamente por lo que la gente pide accidentalmente consultas complicadas 50.000 veces por minuto.

Usa MySQL para:

  • Datos que no puedes perder: pedidos, saldos, permisos, estado de cuentas, eventos de facturación.
  • Invariantes multi-fila: “solo una suscripción activa por usuario”, claves foráneas, unicidad.
  • Historial auditable de cambios (a menudo vía tablas append-only o pipelines basados en binlog).
  • Consultas que requieren joins o escaneos por rango en índices secundarios.

MySQL falla de maneras conocidas: contención de bloqueos, índices malos, escaneos enormes, fallos del buffer pool y almacenamiento lento. También falla socialmente: “solo una columna más”, “solo un join más” y “ya lo cacheamos luego”, que es código para “pasaremos la factura a SRE más tarde”.

Redis: el acelerador del camino caliente y la capa de coordinación

Redis es rápido porque es simple donde importa: mantiene datos en memoria, hace operaciones baratas y mantiene el protocolo directo. Es un lugar brillante para:

  • Caché: objetos computados, fragmentos renderizados, decisiones de autorización, respuestas de API.
  • Sesiones y tokens: búsquedas rápidas con TTLs.
  • Limitación de tasa: contadores atómicos por clave con expiración.
  • Coordinación distribuida: locks (con cuidado), tablas de líderes, leaderboards, colas/streams, claves de deduplicación.
  • Feature flags y snapshots de configuración: lecturas pequeñas, muchas veces.

Redis falla de maneras distintas: presión de memoria, sorpresas por eliminación (eviction), configuración de persistencia incorrecta, tropiezos en failover y picos de latencia por claves grandes o comandos lentos. Además, la causa más grande de outages de Redis no es Redis en sí: son las aplicaciones que asumen que “caché” significa “siempre está ahí”.

Una cita digna de tatuaje para quien opere ambos: “Todo falla, todo el tiempo.” —Werner Vogels

Un chiste corto, porque nos lo hemos ganado: Redis es como el espresso—increíble para productividad, pero si construyes toda tu dieta alrededor de él, temblarás y te arrepentirás.

Hechos interesantes y un poco de historia

  • Redis nació de una necesidad real de producto: fue creado por Salvatore Sanfilippo para resolver problemas de rendimiento y escalabilidad en un contexto de analítica web, no como un ejercicio académico.
  • InnoDB de MySQL cambió el juego operacional: cuando InnoDB se volvió el predeterminado, la recuperación ante fallos y el comportamiento transaccional pasaron a ser la norma en lugar de un “modo serio” opcional.
  • La popularidad de Redis creció con el almacenamiento de sesiones: la adopción masiva temprana a menudo empezaba por “mover sesiones fuera de MySQL”, porque es de bajo riesgo y reduce inmediatamente el churn de escrituras.
  • La bifurcación Memcached vs Redis: Memcached impulsó una historia simple de solo caché; Redis trajo tipos de datos más ricos y operaciones atómicas, lo que lo hizo útil más allá del caching.
  • La persistencia en Redis llegó después en espíritu: Redis es primero en memoria; existen snapshots RDB y logs AOF, pero la durabilidad es un trade-off configurado, no una garantía por defecto.
  • La replicación de MySQL modeló arquitecturas: la capacidad de repartir lecturas a réplicas (y luego mejoras como semi-sync y GTID) influyó en el playbook de “escalado de lecturas” mucho antes de que Redis se colocara delante de MySQL.
  • La invalidación de caché sigue siendo un problema humano sin resolver: lo más difícil no es el algoritmo; es la disciplina organizacional sobre dónde vive la verdad y quién posee las reglas de invalidación.
  • Redis introdujo scripting Lua para atomicidad: es poderoso, pero también una forma de crear “lógica de aplicación oculta” que nadie despliega de forma segura.

Cómo Redis reduce la carga de MySQL sin engañarte

La mayor parte de la carga de MySQL es repetición auto-infligida

En un sistema web típico de producción, una gran parte del tráfico de lectura se repite. El mismo perfil de usuario. El mismo fragmento del catálogo de productos. El mismo conjunto de permisos. La misma evaluación de feature flag. El mismo widget de “qué hay en la barra superior” que golpea cinco tablas porque alguien quería que fuera “flexible”.

MySQL puede manejar muchas lecturas, especialmente desde caché (buffer pool) y con índices adecuados. Pero aún ejecuta parsing, planificación, gestión de bloqueos y lógica de ejecución. Multiplica eso por muchas instancias de la app y obtienes muerte por mil SELECTs educados.

Redis ayuda cuando tu carga de lectura es:

  • Caliente: las mismas claves se solicitan una y otra vez.
  • Derivable: puedes reconstruir entradas de caché desde MySQL o tolerar recomputaciones ocasionales.
  • Granularidad gruesa: un acierto de caché reemplaza varias consultas o una consulta con muchos joins.
  • Sensible a latencia: recortar 10–30ms importa porque se multiplica en llamadas descendentes.

Pero Redis no ayuda cuando necesitas:

  • Analítica ad hoc con predicados de consulta flexibles.
  • Joins complejos que cambian cada sprint.
  • Consistencia fuerte entre múltiples entidades sin un diseño cuidadoso.
  • Retención larga con almacenamiento barato por GB.

El patrón de reducción del 80%: cachea el límite caro

Las mayores ganancias ocurren cuando caches en un límite que naturalmente agrega trabajo. No cachees filas individuales si tu cuello de botella es una consulta que toca 8 tablas. Cachea el objeto resultado que tu servicio realmente necesita: un user context hidratado, un fragmento renderizado o un paquete de autorizaciones.

Eso hace dos cosas:

  1. Reemplaza múltiples llamadas a MySQL con un solo GET en Redis.
  2. Hace la clave de caché estable y fácil de razonar (para invalidación y TTL).

Patrones de escritura: elige uno, no improvises

  • Cache-aside (carga perezosa): la app lee Redis, en miss lee MySQL y luego popula Redis. El más común; el más fácil de introducir; el más difícil de mantener consistente sin disciplina.
  • Write-through: la app escribe en caché y DB en el mismo flujo de la petición (a menudo DB primero, luego caché). Útil para lecturas predecibles, pero debes manejar fallos parciales.
  • Write-behind: la app escribe en Redis y vacía asíncronamente a la DB. Rápido, pero arriesgado; estás esencialmente construyendo un sistema de base de datos a propósito.

Para la mayoría de equipos: empieza con cache-aside, añade invalidación explícita en escrituras y aplica TTLs para limitar el radio de desastre de errores.

Patrones de caché y coordinación que funcionan en producción

1) Cache-aside con invalidación explícita

En lecturas:

  1. GET key desde Redis
  2. si hit: devolverlo
  3. si miss: consultar MySQL, serializar, SETEX en Redis, devolver

En escrituras:

  • Commit a MySQL primero.
  • Luego borrar o actualizar las claves de caché que dependen de las filas cambiadas.
  • Usa TTLs de todos modos. Los TTLs no son consistencia, son control de daños.

Si la invalidación te parece “demasiado difícil”, eso no es motivo para evitar el caching. Es motivo para definir propiedad: qué servicio posee el namespace de claves de caché y qué escrituras disparan invalidación.

2) Evitar la estampida: single-flight y soft TTL

La estampida de caché ocurre cuando una clave caliente expira y 5.000 peticiones se lanzan a MySQL a la vez. La base de datos no se vuelve “más correcta” bajo estrés; solo se vuelve más lenta y muere.

Arréglalo con:

  • Coalescencia de peticiones (single-flight): una petición reconstruye la clave mientras las otras esperan.
  • Soft TTL: sirve datos ligeramente obsoletos por una ventana corta mientras se hace un refresco en background.
  • Jitter: aleatoriza TTLs para evitar expiraciones sincronizadas.

3) Caché negativa (cachear el “no encontrado”)

Si bots o clientes rotos siguen pidiendo IDs que no existen, MySQL hará esa búsqueda indefinidamente. Cachea 404s con TTLs cortos (segundos a minutos). Es un seguro barato.

4) Usa Redis para “decisiones”, no para “verdad”

Buenas claves Redis representan decisiones: “el usuario X está rate-limited”, “la sesión Y es válida hasta T”, “conjunto de feature flags F para la cohorte C”. Estas son limitadas por tiempo y derivadas.

Malas claves Redis representan la verdad: “saldo de cuenta”, “conteo de inventario”, “la única copia de un token de restablecimiento de contraseña sin estrategia de persistencia”. Puedes hacerlo, pero ahora estás operando una base de datos y fingiendo que no lo eres.

5) Contadores y limitación de tasa: atómicos y aburridos

La limitación de tasa es territorio natural de Redis porque INCR y EXPIRE son baratos y atómicos. Usa una ventana fija si debes, ventana deslizante si eres sofisticado, pero mantén la implementación lo suficientemente simple como para que alguien la depure a las 3 a.m.

6) Colas y streams: sabe lo que estás comprando

Las listas, pub/sub y streams de Redis pueden implementar colas de trabajo. Son geniales para fanout de baja latencia y pipelines ligeros. Pero si necesitas procesamiento exactamente-una-vez, retención duradera, reequilibrio de grupos de consumidores y garantías multi-DC, ya no estás comprando Redis—estás buscando un sistema de logs.

Durabilidad de Redis: lo que hace y lo que no hace

La persistencia de Redis es real, pero no es el mismo contrato que una base de datos relacional. Necesitas decidir tu modelo de fallos primero, y luego configurar la persistencia en consecuencia.

RDB snapshots

RDB escribe snapshots puntuales en disco. Es compacto y rápido para reinicios, pero puedes perder datos desde el último snapshot. Eso está bien para cachés; cuestionable para colas; aterrador para libros contables.

AOF (Append Only File)

AOF registra cada escritura. Con políticas de fsync puedes reducir la ventana de pérdida de datos a cambio de sobrecarga de escritura. AOF rewrite compacta el log periódicamente. Está más cerca de durabilidad, pero aún no es una base de datos relacional con restricciones y garantías transaccionales entre filas.

Replicación y failover

La replicación de Redis es asíncrona en la mayoría de despliegues comunes. El failover puede perder escrituras recientes. Si pones la “verdad” en Redis, debes aceptar que esa verdad puede revertirse por un failover. Si esa frase te hace perder el estómago, bien—mantén la verdad en MySQL.

Segundo chiste corto (y último): Llamar a Redis tu base de datos porque activaste AOF es como llamar a una tienda una casa porque compraste un buen saco de dormir.

Playbook de diagnóstico rápido

Si tu sistema está lento y sospechas “base de datos”, puedes perder días adivinando. No lo hagas. Ejecuta un triaje rápido que te diga dónde gastar la próxima hora.

Primero: ¿MySQL está sobrecargado o simplemente esperando?

  • Revisa conexiones y threads de MySQL: si threads_running está alto y estable, MySQL está ocupado; si está bajo pero las consultas son lentas, puede que estés esperando I/O o bloqueos.
  • Revisa los fingerprints de las consultas principales: un patrón de consulta malo puede dominar la CPU aunque cada llamada sea “solo 30ms”.
  • Revisa la latencia de disco: si el almacenamiento es lento, el caching no arreglará escrituras o misses del buffer pool.

Segundo: ¿Redis realmente ayuda o está ocultando dolor?

  • Tasa de hits de Redis: una tasa baja de hits significa que estás pagando red y serialización sin beneficio.
  • Picos de latencia en Redis: claves grandes, comandos lentos o fsync de persistencia pueden causar latencia en la cola que se filtra a MySQL (vía reintentos y timeouts).
  • Evictions: las evictions indican que tu política de caché está tomando decisiones por ti, usualmente malas.

Tercero: ¿la aplicación es la verdadera culpable?

  • Concurrencia: un despliegue que duplica workers puede duplicar la carga en la base incluso si el tráfico es estable.
  • Consultas N+1: un endpoint puede ejecutar silenciosamente cientos de consultas por petición.
  • Tormentas de reintentos: timeouts mal configurados pueden multiplicar la carga cuando las cosas ya van lentas.

Tareas prácticas con comandos: medir, decidir, cambiar

A continuación hay tareas prácticas que puedes ejecutar en sistemas reales. Cada una incluye un comando, qué significa la salida y qué decisión tomar después. Nada mágico, solo evidencia.

Task 1: Identify top MySQL statements by total time

cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 5\G"
...output...

Qué significa la salida: DIGEST_TEXT muestra la forma normalizada de la consulta; COUNT_STAR es ejecuciones; total_s es el tiempo acumulado empleado.

Decisión: Si un digest domina el tiempo total, apúntalo para caching o indexado antes de añadir hardware. Si muchos digests están empatados, busca problemas sistémicos (timeouts, N+1).

Task 2: Confirm MySQL is CPU-bound or waiting on I/O

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_running'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
...output...

Qué significa la salida: Threads_running alto implica carga activa. Buffer_pool_reads (lecturas físicas) vs read_requests (lecturas lógicas) da intuición sobre el hit del cache.

Decisión: Si las lecturas físicas están subiendo y la latencia de almacenamiento es alta, arregla I/O o aumenta el buffer pool antes de apostar todo a Redis.

Task 3: Find lock contention in MySQL

cr0x@server:~$ mysql -e "SELECT * FROM sys.innodb_lock_waits ORDER BY wait_age_secs DESC LIMIT 10\G"
...output...

Qué significa la salida: Muestra transacciones bloqueantes y en espera, y cuánto tiempo llevan atascadas.

Decisión: Si los waits por locks son comunes, cachear lecturas puede ayudar pero no resolverá la contención de escrituras. Arregla el alcance de las transacciones, índices o problemas de aislamiento.

Task 4: Inspect the MySQL slow query log quickly

cr0x@server:~$ sudo pt-query-digest /var/log/mysql/mysql-slow.log --limit 10
...output...

Qué significa la salida: Clases de consultas principales por tiempo total, tiempo medio, filas examinadas y varianza.

Decisión: Si filas examinadas es enorme para búsquedas simples, necesitas índices o arreglar la consulta. Si las consultas son previsibles y repetitivas, son candidatas a caché.

Task 5: Verify MySQL index usage for a hot query

cr0x@server:~$ mysql -e "EXPLAIN SELECT * FROM orders WHERE user_id=123 AND status='open'\G"
...output...

Qué significa la salida: Observa type (ALL es malo), key usada, filas estimadas y extra (Using filesort / temporary puede perjudicar).

Decisión: Si el plan es un scan o usa el índice equivocado, arregla esquema/consulta primero. Redis no debe ser un parche para escaneos evitables.

Task 6: Check Redis hit rate and keyspace behavior

cr0x@server:~$ redis-cli INFO stats | egrep 'keyspace_hits|keyspace_misses|instantaneous_ops_per_sec'
...output...

Qué significa la salida: Hits y misses te permiten calcular la tasa de aciertos. ops/sec te dice el volumen de tráfico.

Decisión: Si la tasa de aciertos es baja, tu estrategia de caching es errónea: las claves son demasiado granulares, TTLs muy cortos o estás cacheando los objetos equivocados.

Task 7: Detect Redis evictions (the silent correctness tax)

cr0x@server:~$ redis-cli INFO stats | egrep 'evicted_keys|expired_keys'
...output...

Qué significa la salida: evicted_keys > 0 significa que Redis está eliminando claves bajo presión de memoria. expired_keys es normal para uso de TTL.

Decisión: Si ocurren evictions, sube maxmemory, cambia la política de eviction, reduce tamaños de valor o replantea qué cacheas. Asume que la eviction rompe supuestos hasta demostrar lo contrario.

Task 8: Check Redis memory fragmentation and allocator behavior

cr0x@server:~$ redis-cli INFO memory | egrep 'used_memory_human|used_memory_rss_human|mem_fragmentation_ratio|maxmemory_human'
...output...

Qué significa la salida: RSS mucho mayor que used_memory implica fragmentación o overhead del allocator; maxmemory te dice el techo.

Decisión: Alta fragmentación puede requerir tuning (comportamiento de jemalloc, active defrag) o remodelar cargas (evita churn grande de claves de tamaño similar).

Task 9: Find Redis slow commands

cr0x@server:~$ redis-cli SLOWLOG GET 10
...output...

Qué significa la salida: Lista ejecuciones de comandos lentos con duración y argumentos (a menudo truncados).

Decisión: Si ves KEYS, grandes HGETALL, consultas de rangos pesadas o scripts Lua que toman milisegundos, has construido una granada de latencia. Reemplaza con patrones SCAN, valores más pequeños o claves precomputadas.

Task 10: Validate Redis persistence settings (AOF/RDB)

cr0x@server:~$ redis-cli CONFIG GET appendonly save appendfsync
...output...

Qué significa la salida: appendonly on/off, horarios de save para RDB, appendfsync policy (always/everysec/no).

Decisión: Para caché puro, puedes desactivar persistencia para reducir overhead. Para datos de coordinación (locks, rate limits), la persistencia puede ser opcional pero entiende el comportamiento en reinicio. Para la “verdad”, replantea todo el diseño.

Task 11: Measure Redis latency distribution under load

cr0x@server:~$ redis-cli --latency-history -i 1
...output...

Qué significa la salida: Reporta min/avg/max de latencia con el tiempo. Los picos correlacionan con fork para RDB, fsync AOF o comandos grandes.

Decisión: Si los picos de max latency se alinean con eventos de persistencia, ajusta persistencia, mueve a almacenamiento más rápido o aisla Redis en nodos dedicados.

Task 12: Check Linux pressure signals that affect both MySQL and Redis

cr0x@server:~$ vmstat 1 5
...output...

Qué significa la salida: si/so indican swapping (malo); wa muestra I/O wait; r muestra hilos ejecutables; free/buff/cache indica la postura de memoria.

Decisión: Si swap no es cero, para y arregla la memoria. Redis más swap es cómo conviertes microsegundos en minutos.

Task 13: Confirm disk latency for MySQL data volume

cr0x@server:~$ iostat -x 1 3
...output...

Qué significa la salida: r_await/w_await muestran latencia de lectura/escritura; %util muestra saturación.

Decisión: Si la latencia es alta y util está cerca del 100%, estás I/O-bound. Caching de lecturas puede ayudar, pero si las escrituras son el problema necesitas almacenamiento, batching o cambios de esquema.

Task 14: Watch MySQL connection churn (often a hidden tax)

cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Connections'; SHOW GLOBAL STATUS LIKE 'Aborted_connects'; SHOW GLOBAL STATUS LIKE 'Threads_connected';"
...output...

Qué significa la salida: Conexiones que suben rápido sugiere falta de pooling; Aborted_connects implica problemas de auth/red.

Decisión: Arregla pooling y timeouts antes de añadir Redis. Si no, solo construirás una forma más rápida de saturar MySQL.

Task 15: Validate Redis key distribution and hot keys

cr0x@server:~$ redis-cli --hotkeys
...output...

Qué significa la salida: Estima claves frecuentemente accedidas (muestreo best-effort).

Decisión: Si una clave es extremadamente caliente, shárdala (claves por usuario), añade caché local o rediseña para evitar contadores globales que serializan tráfico.

Tres mini-historias corporativas (anonimizadas, lo bastante reales)

Mini-historia 1: El incidente causado por una suposición equivocada

La compañía tenía un backend monolítico en MySQL y añadió Redis “para sesiones”. Funcionó. Así que extendieron Redis para almacenar las atribuciones de usuario—qué características había pagado cada usuario. El razonamiento era simple: las atribuciones se leen constantemente, las escrituras son raras, Redis es rápido y habilitaron AOF, así que era “lo bastante durable”.

Entonces ocurrió un failover durante un evento de vecino ruidoso en el host de virtualización. Redis promovió una réplica que estaba ligeramente atrasada. Una pequeña ventana de cambios de atribuciones—upgrades y downgrades—desapareció. Algunos usuarios perdieron acceso; otros obtuvieron acceso que no debían tener. Llegaron tickets de soporte. Finanzas empezó a hacer preguntas con el tono que la gente usa cuando intenta mantenerse cortés.

Al principio, el equipo buscó “corrupción de datos”. Redis no estaba corrupto. Hizo exactamente lo que prometía: rápido, en memoria, replicación asíncrona a menos que se configure otra cosa, y persistencia que todavía tiene ventanas de pérdida.

El problema raíz fue la suposición de que “AOF significa base de datos”. Lo solucionaron moviendo las atribuciones de nuevo a MySQL como fuente de verdad, y luego cacheando el paquete de atribuciones computado en Redis con TTLs cortos e invalidación explícita en escrituras de atribuciones. También añadieron una ruta de chequeo de sanidad: si Redis dice que un usuario tiene atribuciones pero MySQL no está de acuerdo, MySQL gana y Redis se corrige.

Después de eso, los failovers volvieron a ser aburridos. Ese es el objetivo.

Mini-historia 2: La optimización que salió mal

Otro equipo estaba orgulloso de su tasa de hits en caché. Tenían un endpoint popular: “obtener dashboard de usuario”. Cacheaban toda la respuesta JSON en Redis por 30 minutos. La carga en MySQL cayó drásticamente. Los gráficos parecían una diapositiva de éxito.

Dos meses después, producto lanzó un widget de “notificaciones en vivo” dentro de ese dashboard. Necesitaba actualizarse inmediatamente cuando una notificación se marcaba como leída. En su lugar, los usuarios seguían viendo notificaciones antiguas hasta 30 minutos. El equipo intentó invalidar la caché en lecturas de notificaciones, pero la caché del dashboard dependía de varias tablas subyacentes y tipos de eventos. La invalidación se volvió una telaraña de “si X cambia, borra claves A, B, C, excepto cuando…”. Vinieron los bugs. Luego un incidente: un deploy accidentalmente dejó de ejecutar invalidación para una de las fuentes de datos subyacentes, y dashboards obsoletos se propagaron como un rumor.

El postmortem fue incómodo porque la “optimización” de caché había aumentado la superficie de correctitud. Habían cacheado demasiado arriba en la pila sin un modelo de propiedad limpio para la invalidación. La carga de MySQL era menor, pero la confianza del usuario también.

La solución fue dividir el dashboard en componentes más pequeños cacheables con distintos TTLs y triggers de invalidación: bloque de perfil (poco cambiante), bloque de recomendaciones (basado en TTL), notificaciones (no cacheadas, o cacheadas con TTL muy corto e invalidación fuerte). También introdujeron claves versionadas por cambio de estado del usuario, de modo que nuevas escrituras movían el tráfico a nuevas claves sin necesitar borrar las viejas inmediatamente.

La carga de MySQL subió un poco respecto a la línea base “héroe”. Los incidentes bajaron mucho. Es un mejor trade-off.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Un equipo de plataforma maduro trató a Redis como una dependencia con su propio SLO, dashboards y planificación de capacidad. No es emocionante. Efectivo. Hicieron pruebas de carga rutinarias que incluían modos de fallo de Redis: caché fría, caché parcial y reinicios de Redis durante pico.

Una tarde, una actualización de kernel provocó un reboot loop en un subconjunto de nodos del cluster Redis. La disponibilidad de Redis se degradó. La aplicación no cayó. La latencia aumentó, pero se mantuvo dentro del SLO de cara al usuario para la mayoría de endpoints.

¿Por qué? Dos decisiones aburridas. Primero, la app tenía timeouts estrictos para Redis (un dígito en milisegundos) y caía a MySQL en miss o error solo para un conjunto limitado de endpoints. Segundo, aplicaron circuit breakers: si los errores de Redis exceden un umbral, la app deja de intentar Redis por una ventana de cooldown corta, evitando tormentas de reintentos.

MySQL sí vio más tráfico de lectura, pero tenían margen porque la estrategia de caché apuntaba a las lecturas de mayor churn y ya habían arreglado las peores consultas. El modo “cache caída” fue degradado, no catastrófico.

El equipo pudo arreglar el cluster sin un incidente público. Sin heroísmos. Solo sistemas que se comportan como adultos.

Errores comunes: síntomas → causa raíz → solución

1) Síntoma: la carga de MySQL no bajó después de añadir Redis

Causa raíz: baja tasa de hits, caché en la granularidad equivocada o lógica cache-aside faltante bajo concurrencia.

Solución: Mide keyspace_hits/misses, identifica qué se está cacheando y cachea al nivel de objeto boundary. Añade coalescencia de peticiones para detener manadas.

2) Síntoma: la memoria de Redis crece hasta que empiezan las evictions

Causa raíz: falta de TTLs, demasiadas claves únicas (alta cardinalidad) o valores más grandes de lo esperado (blobs JSON, arrays sin comprimir).

Solución: Añade TTLs por defecto, limita tamaños de payload, usa hashes para campos relacionados y define maxmemory + una política de eviction que encaje con “caché” en lugar de “almacenar para siempre”.

3) Síntoma: P99 de latencia empeoró tras “cachear”

Causa raíz: Redis está introduciendo latencia en cola debido a fsync de persistencia, comandos lentos, claves grandes o saltos de red; además los reintentos de la aplicación amplifican el problema.

Solución: Revisa redis-cli –latency-history y SLOWLOG. Reduce operaciones con claves grandes, ajusta persistencia y aplica timeouts agresivos con circuit breakers.

4) Síntoma: datos obsoletos aleatorios, difíciles de reproducir

Causa raíz: bugs de invalidación, caches multi-writer o usar TTLs como única “estrategia de consistencia”.

Solución: Elige un único propietario de caché por namespace, implementa invalidación explícita en escrituras y usa claves versionadas cuando sea factible.

5) Síntoma: MySQL está bien, pero CPU de Redis está alta

Causa raíz: demasiadas operaciones por petición, patrones chatos de acceso o scripts Lua haciendo trabajo pesado.

Solución: Agrupa lecturas (MGET/pipelining), cachea objetos más ricos para reducir el número de llamadas y mantén los scripts Lua pequeños y bien probados.

6) Síntoma: tras reinicio de Redis, la app colapsa y MySQL la sigue

Causa raíz: caché fría más estampida, sin limitación de tasa en la reconstrucción y sin comportamiento de circuit breaker.

Solución: Usa soft TTL, single-flight por clave, warming en background para claves críticas y limita la concurrencia de reconstrucción.

7) Síntoma: acusaciones de pérdida de datos tras failover de Redis

Causa raíz: tratar a Redis como almacén autoritativo para estado crítico del negocio con replicación asíncrona.

Solución: Pon la verdad en MySQL (u otro almacén duradero), cachea vistas derivadas en Redis y documenta la ventana de pérdida tolerable para claves de coordinación.

8) Síntoma: el cluster Redis está estable pero los clientes ven timeouts

Causa raíz: agotamiento de pools de conexión, cambios de DNS/endpoints, presión de puertos NAT o timeouts de cliente mal configurados.

Solución: Instrumenta pools de cliente, mantén timeouts realistas, reutiliza conexiones y evita patrones de connect/disconnect por petición.

Listas de verificación / plan paso a paso

Paso a paso: introducir Redis para reducir la carga de MySQL

  1. Elige un objetivo: un endpoint o un digest de consulta que domine el tiempo total de MySQL, no “toda la base de datos”.
  2. Define el objeto cacheado: lo que el servicio realmente necesita (p. ej., “user_context:v3:{user_id}”).
  3. Decide el modelo de consistencia: solo TTL, invalidación en escritura o claves versionadas.
  4. Pon un TTL con jitter: empieza conservador (minutos), añade ±10–20% de jitter para evitar expiraciones sincronizadas.
  5. Implementa cache-aside: GET, en miss SELECT, SETEX, devolver.
  6. Añade protección contra stampede: single-flight por clave (lock key con TTL corto) o coalescencia en proceso.
  7. Instrumenta: ratio de hits de caché, reducción de QPS en MySQL, latencias P50/P95/P99, tasas de error, tasas de eviction.
  8. Comportamiento ante fallos: define qué pasa cuando Redis falla. Fallo rápido con fallback para lecturas críticas; circuit breaker para evitar tormentas de reintentos.
  9. Plan de capacidad: estima número de claves, tamaño medio de valor, churn por TTL y overhead de memoria. Configura maxmemory explícitamente.
  10. Despliegue gradual: feature flag, activación por porcentaje y rollback sencillo.

Lista de verificación: ¿es seguro poner estos datos en Redis?

  • ¿Puedes recomponerlos desde MySQL u otros almacenes duraderos?
  • ¿Puedes tolerar lecturas obsoletas hasta TTL?
  • ¿Puedes tolerar perder los últimos segundos de escrituras en un failover?
  • ¿Hay un propietario claro para la invalidación?
  • ¿La cardinalidad de claves está acotada o puede explotar con entrada de usuario?

Lista de verificación: preparación para producción de Redis frente a MySQL

  • Los timeouts de Redis son estrictos y los reintentos están acotados.
  • Existe circuit breaker para errores/timeouts de Redis.
  • maxmemory y política de eviction están configurados explícitamente.
  • SLOWLOG monitorizado; evitar claves grandes; usar SCAN en lugar de KEYS.
  • Escenario de caché fría probado en carga similar a pico.
  • MySQL tiene margen para sobrevivir degradación de caché.

FAQ

1) ¿Puede Redis reemplazar a MySQL?

No para la mayoría de aplicaciones. Redis puede almacenar datos, pero MySQL proporciona modelado relacional, restricciones y semántica de durabilidad que Redis no iguala por defecto. Usa Redis para acelerar MySQL, no para suplantarlo.

2) ¿Cuál es el caso de uso más seguro para empezar con Redis?

Sesiones, limitación de tasa y caché de modelos de lectura derivados. Son naturalmente limitados en el tiempo y no pretenden ser el sistema de registro.

3) ¿Cómo estimar si puedo reducir la carga de MySQL en un 80%?

Mira los top query digests por tiempo total y conteo. Si un conjunto pequeño de consultas repetitivas domina y sus resultados son cacheables, reducciones grandes son plausibles. Si las escrituras dominan o las lecturas son muy únicas, no obtendrás 80%.

4) ¿Debo cachear filas individuales o objetos completos?

Prefiere objetos completos o vistas agregadas que coincidan con límites de servicio. Cachear a nivel de fila a menudo lleva a muchas llamadas a Redis por petición y lógica de invalidación compleja.

5) ¿Qué TTL debería usar?

Elige TTL según la obsolescencia aceptable y riesgo operativo. TTL corto reduce obsolescencia pero aumenta churn y riesgo de stampede. Añade jitter. Recuerda: TTL no es un modelo de consistencia; es un respaldo.

6) ¿Qué política de eviction debería usar?

Para caching típico: una variante LRU/LFU con conciencia de TTL es común. La clave es establecer maxmemory y planear las evictions en el comportamiento de la aplicación. “noeviction” puede ser adecuado para datos de coordinación si lo dimensionas correctamente, pero también puede convertir presión de memoria en outages.

7) ¿Cómo evito que Redis sea un punto único de fallo?

Ejecuta Redis con replicación y failover, pero más importante: haz la aplicación resiliente. Timeouts estrictos, reintentos acotados, circuit breakers y un modo degradado probado que no provoque stampede en MySQL.

8) ¿La persistencia de Redis (AOF/RDB) basta para datos críticos?

Usualmente no. La persistencia reduce ventanas de pérdida pero no ofrece las garantías relacionales y transaccionales que muchos datasets críticos requieren. Si los datos podrían aparecer en una auditoría legal, pertenecen a MySQL (u otro sistema duradero), no a una memoria “casi duradera”.

9) ¿Por qué Redis aumentó la carga de MySQL durante un outage?

Porque tu ruta de fallback probablemente provoca una estampida: cada miss de caché se convierte en una consulta a la DB y los reintentos multiplican el tráfico. Arregla con single-flight, reconstrucción con límite de tasa y circuit breakers.

10) ¿Y usar Redis para búsqueda o analítica?

Redis puede soportar índices secundarios y estructuras elegantes, pero operacionalmente no es un motor de analítica general. Si necesitas filtrado flexible y agregación a escala, mantén esa carga en sistemas diseñados para ello.

Siguientes pasos que puedes hacer esta semana

Si quieres que Redis reduzca dramáticamente la carga de MySQL, deja de pensar en slogans y empieza a pensar en contratos. MySQL es la verdad. Redis es la velocidad. Verdad sin velocidad es lento; velocidad sin verdad es un futuro informe de incidentes.

  1. Extrae los 5 top query digests de MySQL por tiempo total y elige un endpoint objetivo.
  2. Diseña una clave de caché que represente el objeto a nivel de servicio, no una fila de tabla.
  3. Implementa cache-aside con invalidación explícita en escrituras y jitter en TTL.
  4. Añade protección contra stampede y circuit breaking antes de habilitarlo globalmente.
  5. Mide: hit rate, evictions, latencia de Redis y QPS/CPU de MySQL antes y después.

Luego haz la prueba aburrida: reinicia Redis en staging bajo carga y confirma que tu aplicación no entra en pánico. Si entra en pánico en staging, entrará en pánico en producción—solo que con más testigos.

← Anterior
Windows ME: cómo lanzar un sistema operativo que la gente recuerda como castigo
Siguiente →
Plan híbrido ZFS: datos en HDD + metadatos en SSD + caché NVMe, bien hecho

Deja un comentario