Algunos sitios no «escalan». Simplemente acumulan latencia hasta que el primer pico de tráfico significativo convierte tu base de datos en un cráter humeante. Añades índices. Añades réplicas. Añades «un caché». Los gráficos mejoran—hasta que dejan de hacerlo, y ahora estás depurando carritos desaparecidos, precios obsoletos y usuarios desconectados aleatoriamente.
La verdad incómoda: el caché es fácil de añadir, difícil de hacer correcto y muy sencillo de hacer rápido en la dirección equivocada. MariaDB y Redis pueden usarse para caché, pero fallan de formas distintas. Si entiendes esos modos de fallo, puedes construir caches que aceleren tu sitio sin perder datos.
Lo que realmente estás decidiendo
«MariaDB vs Redis» rara vez es una elección binaria. La decisión real es cómo repartes responsabilidades entre:
- Sistema de registro: la base de datos que debe permanecer correcta y recuperable tras fallos (típicamente MariaDB).
- Cómputo y coordinación: contadores, locks, límites de tasa, tablas de clasificación, claves de desduplicación (aquí sobresale Redis).
- Datos derivados: resultados de consultas en caché, páginas renderizadas, agregados precomputados (ambos pueden hacerlo, pero no por igual).
- Riesgo operacional: ¿puedes tolerar perder el contenido del caché? Si no, hablas de persistencia, backups, replicación y simulacros de restauración—no solo «añadir Redis».
Si quieres velocidad sin pérdida de datos, debes ser honesto sobre lo que significa «datos». Perder una página de producto en caché está bien. Perder el estado de pago confirmado no lo está. Redis puede ser algo duradero. MariaDB puede usarse como un caché-ish. Pero ninguno debe ser forzado a hacerse pasar por el otro sin un plan.
Idea parafraseada de Werner Vogels: «Todo falla, todo el tiempo», así que diseñas asumiendo que los componentes se romperán y haces que la recuperación sea rutinaria.
MariaDB como caché: cuándo tiene sentido y cuándo es una trampa
Usar MariaDB como caché suele significar una de estas cosas:
- Tablas materializadas-ish: agregados precomputados almacenados en tablas y refrescados.
- Modelos de lectura desnormalizados: «tabla índice de búsqueda», «product_summary», «user_profile_compact».
- Caché de resultados por la aplicación: almacenar blobs serializados en una tabla con campos tipo TTL.
- Réplicas de lectura: no es caché per se, pero descargar lecturas puede sentirse como tal.
Por qué MariaDB puede ser un buen caché
Porque es aburrido. Lo aburrido es una ventaja cuando la corrección importa.
- Durabilidad nativa: InnoDB redo logs, doublewrite buffer, recuperación ante fallos. Está diseñado para conservar tus bits.
- SQL te da palanca: puedes refrescar una tabla de caché incrementalmente, hacer joins, filtrarla y rellenarla hacia atrás.
- Operaciones ya lo conocen: backups, replicación, monitorización, controles de acceso—a menudo ya están en su sitio.
Por qué MariaDB como caché puede hacerte daño
El caché busca reducir trabajo costoso. Si haces que el «caché» sea tan costoso como el trabajo original, no cacheaste—duplicaste carga.
- Filas calientes se vuelven cuellos de botella: contadores, límites de tasa, «última vez visto», «inventario restante» pueden provocar oleadas de actualizaciones y contención de locks.
- Thrash en el buffer pool: cachear muchos blobs efímeros puede desalojar páginas realmente importantes.
- La limpieza por TTL duele: borrar filas expiradas puede causar picos de I/O, retrasos en la purga y retardos en la replicación.
Regla práctica: usa MariaDB para cachear datos derivados estructurados y consultables que estés dispuesto a gestionar como un conjunto de datos real. No la uses para «millones de claves pequeñas con TTL» a menos que disfrutes explicarle a finanzas por qué tu primaria está al 95% de CPU un martes por la mañana.
Redis como caché: rápido, con filos afilados
Redis es un servidor de estructuras de datos en memoria. Esa frase hace mucho trabajo. El valor no es solo la velocidad; son las primitivas: incrementos atómicos, sets, sorted sets, streams, pub/sub, scripting con Lua y expiración rápida.
En qué Redis es excelente
- Búsquedas de baja latencia: cache-aside para obtención de objetos, fragmentos HTML, permisos, feature flags.
- Estado efímero de alta rotación: sesiones, tokens CSRF, enlaces de un solo uso, claves de idempotencia.
- Coordinación: locks distribuidos (con precaución), límites de tasa, colas (con caveats), sets de desduplicación.
- Luchar contra stampedes: con TTLs, jitter y patrones de «single flight».
En qué Redis es malo (y cómo puedes empeorarlo)
- Hacerse pasar por base de datos sin presupuestar persistencia: si guardas hechos empresariales primarios en Redis sin un plan de durabilidad, estás apostando tu compañía a la RAM y a los valores por defecto de configuración.
- Crecimiento de memoria sin límites: sin políticas de maxmemory y buena higiene de claves, aceptará tus datos hasta que el OOM killer del kernel aparezca como un auditor no invitado.
- Claves grandes y valores voluminosos: claves únicas con payloads enormes causan picos de latencia por operaciones bloqueantes y sobrecarga de evicción.
Broma #1: Redis es como un espresso—asombroso cuando se mide, catastrófico cuando sigues rellenando la taza porque «todavía cabe».
Hechos interesantes y pequeña historia relevante
- Redis nació (2009) como forma de manejar estadísticas en tiempo real sin golpear una base relacional—su ADN es «contadores rápidos y sets», no «durabilidad perfecta».
- MariaDB se creó (2009–2010) como fork de MySQL tras la compra de Sun por Oracle; muchos equipos la adoptaron para mantener un camino de desarrollo abierto.
- El Query Cache histórico de MySQL (relevante para la ascendencia de MariaDB) fue notorio por la contención de mutex bajo carga de escritura; finalmente se eliminó en MySQL 8.0 porque perjudicaba más de lo que ayudaba a escala.
- La expiración de Redis es lazy + activa: las claves expiran cuando se accede a ellas, además de muestreo en background. Esto afecta la planificación de memoria y el debugging de «¿por qué lo obsoleto sigue ahí?».
- La persistencia de Redis tiene dos modos principales: RDB snapshots y AOF journaling; ambos tienen compensaciones en amplificación de escritura y tiempo de recuperación.
- El buffer pool de InnoDB es un caché: MariaDB ya cachea páginas de datos en memoria. A veces tu «capa de caché» duplica lo que el motor ya hace bien.
- El retraso de réplicas es un problema antiguo: usar réplicas de lectura como «caché» introduce problemas de consistencia que pueden parecer escrituras faltantes o «bugs aleatorios».
- La ejecución de comandos de Redis en un solo hilo (para el loop principal) es una característica para atomicidad, pero significa que comandos lentos y payloads grandes perjudican a todos.
- La invalidación de caché ha sido un problema famoso en sistemas distribuidos durante décadas—no porque los ingenieros sean malos, sino porque el tiempo, la concurrencia y las fallas parciales son molestos.
Patrones de caché que no devoran tus datos
1) Cache-aside (carga perezosa): la opción por defecto
Flujo: leer del caché → fallo → leer de MariaDB → poner en caché con TTL → devolver.
Por qué funciona: MariaDB sigue siendo la fuente de verdad. El caché se puede perder en cualquier momento. La recuperación es «esperar calentamiento».
Modos de fallo:
- Stampede: muchas solicitudes fallan y todas golpean MariaDB a la vez.
- Datos obsoletos: caché no invalidado tras escrituras, o TTL demasiado largo.
- Clave caliente: una sola clave popular causa churn de caché y contención de locks en tu app.
Haz esto:
- Añade TTL con jitter (segundos aleatorios adicionales) para prevenir expiraciones sincronizadas.
- Usa «single flight» en la app (una recomputación por clave) o locks en Redis con TTL corto.
- Cachea resultados negativos brevemente (p. ej., «usuario no encontrado» por 30s) para prevenir misses por fuerza bruta.
2) Read-through cache: externalizar el manejo de misses
Algunas librerías cliente o sidecars pueden obtener del DB en caso de miss. Operacionalmente seductor. Usualmente mala idea salvo que esté muy probado en tu entorno, porque oculta la carga a la aplicación y hace el tráfico DB más difícil de razonar.
Casos de uso: plataformas internas con patrones de acceso estandarizados y observabilidad sólida.
3) Write-through: corrección primero, latencia después
Flujo: escribir en caché y DB en una sola ruta; las lecturas van al caché.
Beneficio: el caché se mantiene fresco; sin complejidad de invalidación para muchos casos de uso.
Costo: mayor latencia en la escritura, más partes móviles en la ruta de escritura. Si Redis está abajo, las escrituras pueden fallar a menos que degrades explícitamente a solo DB.
Haz esto: trata la actualización del caché como best-effort a menos que el valor cacheado sea necesario para la corrección. Para la mayoría de sitios, no lo es.
4) Write-behind (también conocido como write-back): la forma más rápida de inventar pérdida de datos
Flujo: escribir en caché, devolver éxito al usuario, escribir asíncronamente en DB después.
Cuándo es aceptable: raramente. Quizá para contadores analíticos donde perder algunas actualizaciones es tolerable y tienes idempotencia y replay.
Cuándo es un desastre: pedidos, saldos, permisos, derechos, inventario, contenido generado por usuarios.
Write-behind es cómo conviertes una caída de caché en un postmortem titulado «por qué evaporaron el 2% de los carritos». También despierta el interés de compliance por tus planes de fin de semana.
5) Invalidación solo por TTL: barato, alegre y a veces incorrecto
Si pones un TTL y nunca invalidas explícitamente, confías en el tiempo para arreglar la corrección. Eso está bien para contenido que puede estar algo obsoleto (páginas de inicio, listas de tendencias), y no está bien para nada transaccional.
Mejor: combina TTL con invalidación impulsada por eventos para claves críticas.
6) Invalidación impulsada por eventos: más difícil, pero escala la corrección
Flujo: las escrituras van a MariaDB → publicar evento «entidad cambiada» → consumidores invalidan o refrescan claves en Redis.
Beneficio: baja obsolescencia, comportamiento consistente bajo carga.
Riesgo: entrega de mensajes, lag de consumidores y ordenamiento. Necesitas idempotencia y debes asumir que los eventos pueden duplicarse.
7) Claves versionadas: el patrón anti-obsolescencia que realmente funciona
En lugar de borrar claves, cambias el espacio de nombres de la clave incrementando un número de versión.
- Clave:
product:123:v17 - Otra clave guarda la versión actual:
product:123:ver
Al actualizar, incrementa la versión. Las nuevas lecturas van a la nueva clave. Las claves antiguas expiran naturalmente. Esto es aburridamente bueno para páginas de alto tráfico donde las tormentas de borrado aplastarían Redis.
8) Patrones de protección contra stampedes
- Recomputación temprana probabilística: refrescar antes de que expire el TTL con una probabilidad basada en el TTL restante y el costo de cómputo.
- Servir obsoleto mientras se revalida: mantener un soft TTL (obsoleto permitido) y un hard TTL (debe recomputar). Lo obsoleto te compra tiempo cuando la DB está caliente.
- Single-flight: solo un trabajador recomputa; los demás esperan o sirven obsoleto.
9) Redis para sesiones: rápido y por lo general correcto
Las sesiones son un buen caso de uso para Redis porque son efímeras y naturalmente TTLadas. Pero aún tienes que decidir qué significa «sin pérdida de datos». Perder sesiones molesta a los usuarios; normalmente no corrompe dinero.
No almacenes estado de autorización de larga duración en sesiones a menos que puedas revocarlo correctamente. Cachea permisos, sí; almacena «este usuario es admin para siempre», no.
10) Tablas resumen en MariaDB: «caché» que sigue siendo consultable
Si lo costoso es una agregación multi-join, Redis puede cachear el resultado, pero pierdes capacidad de consulta. Las tablas resumen en MariaDB te permiten indexar y filtrar los datos derivados. Pagas con complejidad de refresco y almacenamiento, pero ganas SQL explicable y durabilidad.
Broma #2: La invalidación de caché es la versión adulta de «¿probaste apagarlo y encenderlo?», excepto que el caché recuerda que lo intentaste.
Durabilidad: “sin pérdida de datos” en el mundo real
Cuando alguien dice «sin pérdida de datos», pregunta: ¿qué datos?, y ¿cuál es tu ventana aceptable de pérdida?
- Datos derivados cacheables: perderlos está bien; se recomputan desde MariaDB.
- Estado efímero de usuario: perderlo es tolerable pero debería ser raro; las sesiones pueden reautenticarse.
- Hechos transaccionales: deben sobrevivir a caídas de proceso, fallos de nodo y errores de operador. Almacénalos en MariaDB (u otro verdadero sistema de registro), no solo en Redis.
Lista de verificación de durabilidad para MariaDB (baseline)
- InnoDB con configuraciones de flush adecuadas para tu apetito de riesgo.
- Binary logs habilitados si necesitas recuperación punto en el tiempo.
- Backups probados mediante restauración, no por esperanza.
- Replicación monitorizada por lag y errores.
Opciones de durabilidad de Redis (si insistes en conservar estado importante)
Redis puede persistir datos, pero la persistencia no es magia. Es un conjunto de compensaciones que debes elegir deliberadamente.
- RDB snapshots: snapshots periódicos. Escrituras más rápidas, pero puedes perder datos entre snapshots.
- AOF (append-only file): registra cada escritura. Mejor durabilidad, más I/O de escritura y comportamiento de rewrite para gestionar el tamaño del archivo.
- Replicación: ayuda disponibilidad, no garantiza cero pérdida salvo que lo diseñes y aceptes latencia.
Si realmente no puedes perder datos en Redis, ya no estás «usando un caché». Estás ejecutando otra base de datos. Trátala como tal: persistencia, replicación, backups, simulacros de restauración y planificación de capacidad.
Tareas prácticas: comandos, salidas y decisiones
Estos son los tipos de comprobaciones que ejecutas durante un incidente o una revisión de rendimiento. Cada tarea incluye: comando, salida de ejemplo, qué significa y la decisión que tomas.
Task 1: Confirmar que MariaDB es el cuello de botella (consultas principales)
cr0x@server:~$ sudo mariadb -e "SHOW FULL PROCESSLIST\G" | sed -n '1,60p'
*************************** 1. row ***************************
Id: 8421
User: app
Host: 10.0.3.24:41372
db: prod
Command: Query
Time: 12
State: Sending data
Info: SELECT ... FROM orders JOIN order_items ...
*************************** 2. row ***************************
Id: 8422
User: app
Host: 10.0.3.25:41810
db: prod
Command: Query
Time: 11
State: Sending data
Info: SELECT ... FROM orders JOIN order_items ...
Significado: consultas de lectura de larga duración dominan, y muchas son idénticas. Eso es terreno ideal para caché.
Decisión: implementar cache-aside para la ruta de lectura costosa, o crear una tabla resumen si la consulta es compleja y necesita filtrado.
Task 2: Verificar salud del buffer pool de MariaDB
cr0x@server:~$ sudo mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
+---------------------------------------+-----------+
| Variable_name | Value |
+---------------------------------------+-----------+
| Innodb_buffer_pool_read_requests | 984332111 |
| Innodb_buffer_pool_reads | 12099122 |
+---------------------------------------+-----------+
Significado: lecturas físicas vs lecturas lógicas. Una alta proporción de reads respecto a read_requests indica un buffer pool frío o subdimensionado.
Decisión: si la tasa de misses del buffer pool es alta, ajustar MariaDB puede superar añadir Redis para algunas cargas de trabajo.
Task 3: Identificar consultas lentas que estás a punto de «cachear»
cr0x@server:~$ sudo mariadb -e "SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time';"
+---------------------+-------+
| Variable_name | Value |
+---------------------+-------+
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/slow.log |
+---------------------+-------+
+-----------------+-------+
| Variable_name | Value |
+-----------------+-------+
| long_query_time | 1.000 |
+-----------------+-------+
Significado: tienes logging de queries lentas habilitado con umbral de 1s.
Decisión: antes de cachear, arregla índices faltantes obvios y patrones N+1. El caché debe reducir carga, no ocultar accesos descuidados.
Task 4: Medir lag de replicación (cuando las réplicas son tu «caché»)
cr0x@server:~$ sudo mariadb -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 37
Significado: la réplica está 37 segundos atrás. Las lecturas desde la réplica estarán obsoletas.
Decisión: no uses la réplica para rutas read-after-write (login, checkout). Usa primaria o construye una estrategia de consistencia (lecturas sticky, lecturas basadas en GTID o invalidación explícita del caché).
Task 5: Comprobar memoria de Redis y riesgo de evicción
cr0x@server:~$ redis-cli INFO memory | egrep "used_memory_human|maxmemory_human|mem_fragmentation_ratio"
used_memory_human:7.83G
maxmemory_human:8.00G
mem_fragmentation_ratio:1.62
Significado: estás cerca de maxmemory, la fragmentación es alta, la presión de evicción es probable.
Decisión: aumenta memoria, reduce tamaños de valor, shardea o cambia la política de evicción. También arregla la fragmentación revisando comportamiento del allocator y churn de claves.
Task 6: Verificar política de evicción de Redis (podrías estar evictando lo incorrecto)
cr0x@server:~$ redis-cli CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
Significado: Redis rechazará escrituras cuando la memoria esté llena. Las aplicaciones suelen interpretar esto como «errores aleatorios».
Decisión: para caché, prefiere allkeys-lfu o volatile-lfu según si cada clave tiene TTL. Para estado crítico, sé explícito: quizá quieras noeviction para que el fallo sea ruidoso.
Task 7: Buscar claves grandes (picos de latencia y desperdicio de memoria)
cr0x@server:~$ redis-cli --bigkeys
# Scanning the entire keyspace to find biggest keys as well as biggest overall keys per data type
Biggest string found 'pagecache:/product/123' has 5242880 bytes
Biggest hash found 'session:hash' has 183201 fields
0 keys sampled
Significado: tienes valores multi-megabyte y hashes enormes; ambos pueden crear pausas y evicción desigual.
Decisión: divide objetos grandes, comprime con cuidado (compensación de CPU) y evita «un hash gigante con todo». Prefiere claves por sesión con TTL.
Task 8: Comprobar configuración de persistencia de Redis (ventana de pérdida de datos)
cr0x@server:~$ redis-cli CONFIG GET save appendonly appendfsync
1) "save"
2) "900 1 300 10 60 10000"
3) "appendonly"
4) "no"
5) "appendfsync"
6) "everysec"
Significado: RDB snapshots habilitados; AOF deshabilitado. En el peor caso, se pierde hasta el intervalo de snapshot si el nodo muere.
Decisión: si Redis guarda algo más importante que caché desechable, habilita AOF y decide la política de fsync; si no, acepta pérdida de caché y diseña en consecuencia.
Task 9: Detectar clientes bloqueados en Redis (comandos lentos)
cr0x@server:~$ redis-cli INFO clients | egrep "blocked_clients|connected_clients"
connected_clients:812
blocked_clients:17
Significado: clientes están bloqueados; algo es lento (claves grandes, scripts Lua, disco lento para AOF o stalls de red).
Decisión: inspecciona slowlog, identifica el comando y arregla la carga. «En memoria» no significa inmune a I/O o CPU.
Task 10: Inspeccionar slowlog de Redis para detectar problemas autoinfligidos
cr0x@server:~$ redis-cli SLOWLOG GET 3
1) 1) (integer) 19042
2) (integer) 1735250401
3) (integer) 15423
4) 1) "KEYS"
2) "*"
5) "10.0.2.9:51244"
6) ""
2) 1) (integer) 19041
2) (integer) 1735250399
3) (integer) 8120
4) 1) "HGETALL"
2) "session:hash"
5) "10.0.2.10:42118"
6) ""
Significado: alguien ejecutó KEYS * (bloquea Redis en datasets grandes) y estás usando HGETALL en un hash masivo.
Decisión: prohibir KEYS en producción (usar SCAN), rediseñar almacenamiento de sesiones para evitar hashes enormes y añadir guardrails en tooling.
Task 11: Confirmar indexación de MariaDB en la ruta caliente
cr0x@server:~$ sudo mariadb -e "EXPLAIN SELECT * FROM orders WHERE user_id=123 ORDER BY created_at DESC LIMIT 20\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: orders
type: ref
possible_keys: idx_user_created
key: idx_user_created
key_len: 4
ref: const
rows: 20
Extra: Using where
Significado: la consulta usa el índice compuesto previsto y escanea solo un pequeño número de filas.
Decisión: cachear esta consulta puede aún ayudar, pero ya no estás enmascarando un índice faltante. Bien. Ahora puedes dimensionar TTL en función de la frescura de negocio.
Task 12: Buscar contención de locks en MariaDB (contadores mal implementados)
cr0x@server:~$ sudo mariadb -e "SHOW ENGINE INNODB STATUS\G" | sed -n '/LATEST DETECTED DEADLOCK/,+40p'
------------------------
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 928331, ACTIVE 0 sec updating or deleting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 221 page no 9 n bits 80 index PRIMARY of table `prod`.`rate_limits`
Significado: deadlocks en una tabla de rate limit. Clásico «usar la base de datos como servicio de contadores/locks».
Decisión: mover rate limiting y contadores a Redis donde las operaciones atómicas son baratas, y mantener MariaDB para registros durables.
Task 13: Validar higiene de TTL en claves Redis (¿estás fugando memoria?)
cr0x@server:~$ redis-cli INFO keyspace
db0:keys=1823492,expires=24112,avg_ttl=0
Significado: casi ninguna clave tiene expiración; avg_ttl=0 sugiere claves indefinidas.
Decisión: para un caché, la mayoría de claves debería tener TTL. Si no lo hacen, estás construyendo un segundo datastore sin plan de migración.
Task 14: Detectar un stampede en la capa de aplicación (hit rate de Redis)
cr0x@server:~$ redis-cli INFO stats | egrep "keyspace_hits|keyspace_misses"
keyspace_hits:12099331
keyspace_misses:8429932
Significado: la tasa de misses es alta; Redis no está sirviendo como un caché efectivo, o los TTL son demasiado bajos, o los nombres de clave son inconsistentes.
Decisión: estandarizar la construcción de claves, aumentar TTL donde sea seguro, añadir jitter e implementar single-flight para evitar estampidas.
Task 15: Medir presión a nivel OS en el nodo Redis (swap es muerte)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 16Gi 15Gi 120Mi 1.1Gi 900Mi 300Mi
Swap: 2Gi 1.8Gi 200Mi
Significado: la caja Redis está intercambiando. La latencia se volverá no lineal, y luego el incidente será «misterioso».
Decisión: detener el swapping (tunear, añadir RAM, reducir dataset). Si Redis debe ser fiable, deshabilita swap o establece márgenes de memoria estrictos y alertas.
Task 16: Confirmar en qué gasta tiempo MariaDB (CPU vs I/O)
cr0x@server:~$ sudo iostat -x 1 3
Linux 6.1.0 (db1) 12/30/2025 _x86_64_ (8 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
22.10 0.00 5.33 41.20 0.00 31.37
Device r/s w/s rKB/s wKB/s await svctm %util
nvme0n1 820.0 310.0 52160.0 20120.0 18.2 0.9 92.5
Significado: alto iowait y alta utilización de disco; la BD está limitada por I/O.
Decisión: cachear lecturas calientes en Redis puede ayudar, pero también considera dimensionar buffer pool, tunear consultas/índices y mejorar almacenamiento. No soluciones de caché si tu working set debería caber en memoria.
Guía rápida de diagnóstico
No tienes tiempo para filosofía cuando la gráfica de p95 latencia parece una pista de esquí. Aquí está el orden que encuentra cuellos de botella rápido, con mínima autoengaño.
Primero: ¿es la base de datos, el caché o la app?
- Verifica hits/misses y latencia de Redis: si los misses son altos, Redis puede ser irrelevante; si la latencia aumenta, Redis podría estar sobrecargado o swappeando.
- Verifica consultas activas y esperas de locks en MariaDB: «Sending data» largo apunta a lecturas costosas; esperas de lock/deadlocks apuntan a contención de escrituras.
- Verifica errores/timeouts en la app: errores de caché pueden cascadar en sobrecarga DB (clásico). La sobrecarga DB puede causar stampedes en caché (también clásico).
Segundo: busca dinámicas de stampede
- ¿Caducó una clave popular en toda la flota al mismo momento (sin jitter)?
- ¿Hubo un deploy que cambió la nomenclatura de claves o los valores por defecto de TTL?
- ¿El patrón de tráfico es distinto (campaña, crawler, bot)?
Tercero: confirma presión de recursos
- Memoria de Redis: ¿cerca de maxmemory? ¿thrash de evicción? ¿fragmentación? ¿swap?
- I/O de MariaDB: ¿alto iowait? ¿misses en buffer pool? ¿explosión del slow query log?
- Red: mayor RTT entre app y Redis/DB puede disfrazarse de «caché lento».
Cuarto: elige la mitigación de menor riesgo
- Servir obsoleto mientras revalida para contenido seguro.
- Aumentar temporalmente TTL y añadir jitter.
- Limitar recomputaciones (single-flight) para proteger MariaDB.
- Para sobrecarga de Redis: reducir tamaño de valores, deshabilitar comandos caros, escalar o degradar abierto (solo DB) si es seguro.
Errores comunes: síntomas → causa raíz → solución
1) «Añadimos caché, pero la carga DB no bajó»
Síntomas: CPU de Redis baja, misses altos, QPS de MariaDB sin cambios.
Causa raíz: cachear la capa equivocada (p. ej., cachear después de personalización), claves inconsistentes, TTL demasiado bajo o la mayoría de solicitudes son únicas.
Solución: cachear a un nivel más compartido (fragmentos), estandarizar construcción de claves y medir hit rate por endpoint. Considera tablas resumen si la consulta es inherentemente no cacheable.
2) «Redis es rápido hasta que de repente no lo es»
Síntomas: picos intermitentes, clientes bloqueados, timeouts.
Causa raíz: claves grandes, comandos lentos, swapping, stalls de fsync de AOF o hotspot en single-thread.
Solución: ejecutar --bigkeys, inspeccionar slowlog, eliminar comandos bloqueantes, mantener margen de memoria y evitar valores gigantes.
3) «Usuarios ven datos obsoletos tras actualizaciones»
Síntomas: actualizaciones de perfil no aparecen, precios revierten, cambios de admin retrasados.
Causa raíz: invalidación solo por TTL o eventos de invalidación faltantes; uso de réplicas con lag para lecturas; claves no versionadas.
Solución: invalidación por eventos para entidades críticas, claves versionadas para objetos calientes y consistencia read-after-write (lecturas sticky a la primaria cuando sea necesario).
4) «Perdimos datos cuando Redis se reinició»
Síntomas: tablas de clasificación vacías, sesiones faltantes, contadores desaparecidos; la app entra en pánico.
Causa raíz: Redis tratado como sistema de registro; persistencia apagada o insuficiente; sin plan de restauración.
Solución: mover hechos durables a MariaDB; habilitar AOF si Redis debe persistir; probar comportamiento en reinicios y tiempo de recuperación.
5) «MariaDB se volvió más lenta después de añadir ‘tablas de caché'»
Síntomas: tasa de aciertos del buffer pool cae, I/O aumenta, purge lag, retraso en replicación.
Causa raíz: datos efímeros de caché inundando InnoDB, borrados por TTL causando churn.
Solución: mover claves con mucho TTL a Redis; si mantienes tablas de caché, particiónalas, limpia en batch y separa cargas por instancia si es necesario.
6) «500s aleatorios durante picos de tráfico»
Síntomas: errores correlacionados con carga; Redis “OOM command not allowed” o timeouts.
Causa raíz: maxmemory alcanzado con noeviction, o política de evicción que pelea con la carga; stampede de caché fuerza recomputación.
Solución: configurar maxmemory + política de evicción adecuada para caches; añadir jitter y single-flight; proteger MariaDB con circuit breakers.
7) «El caché empeora la corrección más que si no existiera»
Síntomas: lecturas inconsistentes, estados imposibles, bugs difíciles de reproducir.
Causa raíz: cachear objetos con consistencia mixta (parcialmente desde DB, parcialmente desde otros servicios), actualizaciones multi-clave sin atomicidad o usar Redis como cola sin semántica de ack.
Solución: cachear snapshots inmutables; versionar claves; evitar ilusiones transaccionales multi-clave; si necesitas mensajería durable, usa colas/streams con garantías de entrega claras.
Tres mini-historias corporativas (anonimizadas)
Historia 1: Incidente causado por una suposición incorrecta (las réplicas son «un caché»)
El equipo operaba un gran sitio de contenido con MariaDB primaria + réplicas. Alguien propuso «rendimiento gratis» enviando todas las lecturas a las réplicas. Funcionó en staging, porque staging no tenía volumen de escrituras significativo. En producción sí.
Enrutaron la página de «cuenta» (compras recientes, libreta de direcciones, estado de suscripción) a réplicas. Llegaron tickets de soporte: «Cambié mi dirección y no se guardó.» Los ingenieros comprobaron la primaria—los datos eran correctos. La UI seguía mostrando datos viejos porque la réplica tenía lag bajo carga.
La suposición errónea fue sutil: trataron réplicas como un caché donde la obsolescencia es aceptable. Pero la página de cuenta es una superficie read-after-write. Los humanos notan cuando su dirección vuelve atrás.
La solución fue aburrida y efectiva: lecturas sticky. Tras una escritura exitosa, las siguientes lecturas del usuario durante N segundos fueron a la primaria. También instrumentaron el lag de réplicas y hicieron que el router rechace lecturas desde réplicas cuando el lag excediera un umbral.
La lección: las réplicas pueden reducir carga de lectura, pero introducen un comportamiento de consistencia que debes diseñar. Un miss de caché solo cuesta tiempo. Una lectura obsoleta cuesta confianza.
Historia 2: Optimización que salió mal (write-behind «por velocidad»)
Una empresa de e-commerce tenía una ruta lenta de «añadir al carrito». Alguien notó que la tabla de carritos en MariaDB era un hotspot—muchas pequeñas actualizaciones, mucha contención. Movieron los carritos a Redis y lo hicieron write-behind a MariaDB en un worker background.
La latencia mejoró dramáticamente. Todos celebraron. Luego un nodo Redis se reinició durante un parche rutinario del kernel. La cola del worker background se retrasó, luego se quedó atrás, y empezó a reintentar. Algunas actualizaciones se aplicaron fuera de orden. Un subconjunto de carritos revirtió o duplicó artículos, dependiendo del patrón de reintento.
El outage no fue un colapso total; fue peor: corrupción parcial. El sistema de checkout tuvo que añadir verificaciones defensivas, y soporte trató con clientes frustrados que juraban que «añadieron el artículo tres veces». Tenían razón.
Revirtieron a MariaDB como sistema de registro para carritos, y usaron Redis como cache-aside para renderizar carritos, más una pequeña estructura en Redis para «flags de carrito sucio» que redujera recomputaciones. También introdujeron tokens de idempotencia en peticiones de escritura para que reintentos no duplicaran operaciones.
La lección: write-behind es un impuesto a la corrección que pagas más tarde con intereses. Si no puedes articular tus semánticas de orden y reintento, no lo hagas.
Historia 3: Práctica aburrida pero correcta que salvó el día (servir obsoleto + claves versionadas)
Un producto SaaS tenía un panel que ejecutaba una consulta de agregación pesada en MariaDB. Construyeron una capa Redis cache-aside. Ayudó, pero una ráfaga de tráfico ocasionalmente coincidía con expiración masiva de caché y aplastaba la base de datos.
En lugar de perseguir ingenierías exóticas, implementaron dos patrones aburridos: claves versionadas y «servir obsoleto mientras revalida.» Cada tarjeta del dashboard tenía un soft TTL (servir cacheado) y un hard TTL (debe recomputar). Un mecanismo single-flight aseguró que solo una recomputación por clave ocurriera a la vez.
También usaron claves versionadas para que la invalidación fuera un incremento atómico del número de versión, en lugar de tormentas de borrado. Las claves antiguas expiraban naturalmente.
Semanas después, MariaDB tuvo una breve oscilación de rendimiento de I/O durante mantenimiento de almacenamiento. El dashboard se mantuvo responsivo sirviendo datos ligeramente obsoletos por unos minutos. Soporte nunca se enteró. El equipo lo notó solo porque la monitorización era lo bastante buena como para ser levemente molesta.
La lección: la mejor característica del caché es degradación controlada. Cuando las cosas fallan, tus usuarios no deberían tener que saberlo.
Listas de verificación / plan paso a paso
Paso a paso: elegir MariaDB, Redis o ambos
- Clasifica los datos:
- Hechos transaccionales → MariaDB.
- Estado efímero → Redis (con TTL).
- Modelos de lectura derivados → tablas MariaDB o Redis según necesidad de consulta.
- Elige el patrón de caché:
- Por defecto: cache-aside con TTL + jitter.
- Alta corrección y muchas lecturas: invalidación impulsada por eventos o claves versionadas.
- Evita write-behind a menos que las pérdidas sean aceptables y las semánticas estén probadas.
- Decide el presupuesto de obsolescencia: por endpoint, explícitamente. «Unos segundos» no es un plan; escríbelo.
- Diseña protección contra stampede: single-flight, stale-while-revalidate y/o refresh temprano.
- Planifica capacidad de Redis: maxmemory, política de evicción, headroom, cobertura de TTL, dimensionamiento de valores.
- Mantén MariaDB saludable: revisión de índices, revisión de consultas, dimensionado de buffer pool, monitorización de I/O, chequeos de replicación.
- Define comportamiento ante fallos:
- Si Redis está abajo, ¿fallas abierto (solo DB) o cerrado?
- Si DB está lenta, ¿sirves obsoleto o devuelves errores?
- Instrumenta lo básico: hit rate del caché, p95 de latencia para Redis y DB, lag de réplicas, memoria de Redis, consultas lentas en DB.
- Haz un game day: reinicia Redis, simula evicción, inyecta lag de réplica. Asegúrate de que el sitio degrade como planeaste.
Checklist: configuración segura de Redis para caché
- Configura maxmemory y una maxmemory-policy deliberada.
- Asegura que la mayoría de claves de caché tengan TTL.
- Alerta sobre used_memory/maxmemory, blocked_clients y evicted_keys.
- Evita comandos bloqueantes en producción (
KEYS,SORTgrande,HGETALLen hashes enormes). - Mantén headroom de memoria para evitar fragmentación/tormentas de evicción.
Checklist: uso seguro de MariaDB junto a Redis
- Haz explícitos los endpoints read-after-write; no los sirvas desde réplicas con lag.
- Usa tablas resumen para agregaciones pesadas que requieran filtrado e indexación.
- No implementes límites de tasa o contadores calientes como actualizaciones de fila en la primaria.
- Monitorea deadlocks y esperas de locks; te indican dónde mover cargas de coordinación a Redis.
Preguntas frecuentes
1) ¿Redis debería ser alguna vez el sistema de registro?
Sólo si estás dispuesto a ejecutarlo como un datastore primario: persistencia, estrategia de replicación, backups, pruebas de restauración y gestión estricta de capacidad. Para la mayoría de sitios: no. Mantén la verdad transaccional en MariaDB.
2) Si MariaDB ya cachea en el buffer pool, ¿por qué usar Redis?
MariaDB cachea páginas, no los resultados computados por tu aplicación. Redis ayuda cuando lo costoso son joins/agregaciones, serialización, renderizado de plantillas, comprobaciones de permisos o primitivas de coordinación.
3) ¿Cuál es el patrón de caché por defecto más seguro?
Cache-aside con TTL + jitter, más single-flight o stale-while-revalidate para claves calientes. Mantiene a MariaDB como autoritativa y hace que la pérdida del caché sea tolerable.
4) ¿Cómo prevengo datos obsoletos sin borrar un millón de claves?
Usa claves versionadas. Incrementa un número de versión en las escrituras, lee desde el nuevo namespace y deja que las claves antiguas expiren. Escala mejor que tormentas de borrado.
5) ¿Write-through es mejor que cache-aside?
Write-through puede reducir obsolescencia pero aumenta la complejidad en la ruta de escritura y el acoplamiento con disponibilidad de Redis. Úsalo cuando necesites lecturas cacheadas frescas y puedas degradar si Redis cae.
6) ¿Por qué write-behind es tan arriesgado?
Porque reconoces éxito antes de que los datos sean durables en MariaDB. Caídas, reintentos y reordenamientos se vuelven bugs de corrección. Úsalo solo para datos no críticos, agregables y con actualizaciones idempotentes.
7) ¿Qué política de evicción debería usar en Redis para caché?
Si todo es caché: allkeys-lfu es un buen valor por defecto. Si solo claves con TTL deberían ser evictadas: volatile-lfu. Evita noeviction salvo que quieras fallos duros al llenarse.
8) ¿Cómo sé si Redis está ayudando?
Mide hit rate por endpoint y compara QPS/latencia de MariaDB antes y después. Si los misses siguen altos o la carga DB no baja, estás cacheando lo equivocado o clavez mal las claves.
9) ¿Puedo almacenar sesiones en MariaDB en vez de Redis?
Puedes, pero a menudo genera contención de escrituras y churn de limpieza. Redis con TTL suele ser una mejor opción. Si las sesiones son realmente críticas, diseña expectativas de re-autenticación y persistencia explícitamente.
10) ¿Qué hay de cachear páginas HTML renderizadas?
Genial cuando la personalización es limitada. Cachea páginas completas o fragmentos en Redis e invalida con cambios de contenido vía eventos o versionado. No caches páginas por usuario a menos que hayas calculado la cardinalidad de claves.
Próximos pasos que puedes hacer esta semana
- Elige tres endpoints con mayor coste en DB. Mide sus patrones de consulta y decide qué cachear: objetos, fragmentos o tablas resumen.
- Añade cache-aside con TTL + jitter para un endpoint e instrumenta hit rate, p95 de latencia e impacto en QPS de DB.
- Implementa protección contra stampede (single-flight o stale-while-revalidate) para una ruta de clave caliente.
- Configura maxmemory y política de evicción en Redis deliberadamente, y alerta cuando el headroom de memoria disminuya.
- Audita «sin pérdida de datos»: asegura que los hechos transaccionales estén en MariaDB con backups y pruebas de restauración; asegura que Redis solo tenga lo que puedes perder o persistir correctamente.
- Haz un drill de reinicio: reinicia Redis en horario comercial de forma controlada, observa el comportamiento y arregla lo que falle antes de que falle de verdad.
Si no haces nada más: deja de tratar «caché» como una pegatina mágica de rendimiento. Trátalo como un segundo sistema con sus propios modos de fallo—y diseña para las fallas primero.