Cómo integrar Redis de Azure en una solución cloud nativa
Daniel J. Saldaña- 19 de junio de 2026
- Puntuación de feedback

Cómo integrar Redis de Azure en una solución cloud nativa
Redis suele aparecer en una arquitectura como una pieza sencilla: un sitio donde guardar datos temporalmente para no repetir trabajo.
Y en parte es eso.
Pero cuando lo llevamos a producción, Redis deja de ser simplemente una caché rápida y empieza a formar parte del modelo operativo de la solución. Afecta a la latencia, al coste, a la resiliencia, a la seguridad, a la experiencia de usuario y a la presión que ejercemos sobre bases de datos, APIs externas o servicios cloud.
Por eso integrar Redis de Azure no debería consistir únicamente en copiar una cadena de conexión y hacer un GET o un SET.
La integración debería responder a algunas preguntas importantes:
- Qué datos merece la pena cachear.
- Durante cuánto tiempo.
- Cómo se invalida la información.
- Qué ocurre si Redis no está disponible.
- Cómo se autentica la aplicación.
- Qué permisos necesita realmente.
- Cómo se observa el comportamiento en producción.
- Cómo evitamos convertir la caché en una nueva fuente de problemas.
En este artículo veremos cómo integrar Azure Managed Redis en una solución moderna, usando TypeScript como ejemplo, autenticación con Microsoft Entra ID y una arquitectura pensada para producción.
La idea no es construir una demo aislada, sino entender cómo Redis encaja dentro de una aplicación real.
Redis no arregla una mala arquitectura
Redis es muy rápido, pero no es magia.
Si una consulta a base de datos es lenta, si una API externa tarda demasiado o si un dashboard recalcula información pesada en cada petición, Redis puede ayudar mucho. Pero no debería usarse para esconder indefinidamente problemas de diseño.
Una buena integración con Redis empieza por identificar claramente el coste que queremos evitar.
Por ejemplo:
- Consultas repetidas a una base de datos.
- Llamadas frecuentes a APIs de terceros.
- Cálculos derivados que no cambian en cada petición.
- Sesiones de usuario.
- Resultados agregados para dashboards.
- Datos de configuración que se leen constantemente.
- Limitadores de tasa.
- Colas o coordinación ligera entre procesos.
Lo importante es que Redis tenga un papel claro.
Cuando se usa sin criterio, la caché termina llena de claves difíciles de entender, TTLs arbitrarios, datos obsoletos y dependencias implícitas que nadie quiere tocar.
Redis puede hacer una solución más rápida y estable, pero solo si se integra como una pieza de arquitectura, no como un parche.
Azure Managed Redis como servicio administrado
En Azure existen varias formas de trabajar con Redis. Si estás creando una solución nueva, la opción que conviene mirar primero es Azure Managed Redis, la oferta administrada más reciente de Microsoft para Redis sobre Azure.
Azure Managed Redis proporciona un almacén en memoria de baja latencia y alto rendimiento, operado como servicio administrado. Esto significa que no tenemos que desplegar ni mantener servidores Redis manualmente, ni ocuparnos de buena parte del trabajo operativo de infraestructura.
La aplicación se conecta a un endpoint Redis, pero el servicio se encarga de aspectos como disponibilidad, escalado, configuración de red, autenticación, replicación y mantenimiento de la plataforma.
Esto no elimina nuestras responsabilidades, pero cambia el tipo de trabajo que hacemos.
En lugar de administrar máquinas, nos centramos en decidir cómo usar bien Redis dentro del producto.
Azure Managed Redis o Azure Cache for Redis
Durante mucho tiempo, el servicio más habitual fue Azure Cache for Redis.
Si ya tienes una solución existente sobre Azure Cache for Redis, muchos de los patrones de este artículo siguen siendo válidos: conexión segura, TTLs, cache-aside, observabilidad, invalidación y resiliencia.
Sin embargo, para proyectos nuevos conviene revisar Azure Managed Redis porque Microsoft lo está posicionando como la experiencia más actual para Redis en Azure.
La diferencia práctica para una solución no está solo en el nombre del servicio.
Azure Managed Redis pone mucho foco en integración con Microsoft Entra ID, opciones de alta disponibilidad, datos persistentes, módulos Redis y escenarios modernos como caché semántica o búsqueda vectorial.
Mi recomendación sería esta:
- Si estás empezando una solución nueva, evalúa Azure Managed Redis primero.
- Si mantienes Azure Cache for Redis, revisa las guías de migración antes de hacer cambios grandes.
- Si solo necesitas un patrón de caché básico, diseña la aplicación de forma que la capa Redis pueda cambiar sin tocar todo el sistema.
La abstracción interna importa más de lo que parece.
Dónde encaja Redis en la arquitectura
Una forma limpia de integrar Redis es situarlo detrás de una capa de servicio propia.
No conviene que cada controlador, cada componente o cada tarea programada construya sus propias claves y hable directamente con Redis.
El patrón sería algo así:
Frontend / API consumer | vAPI / Controller | vServicio de dominio | vServicio de caché | vAzure Managed Redis | vBase de datos / API externa / cálculo pesadoLa capa de caché se encarga de los detalles de Redis: conexión, serialización, TTL, claves, errores e invalidación.
La capa de dominio decide qué dato necesita y qué nivel de frescura es aceptable.
Esta separación evita que Redis se convierta en una dependencia dispersa por toda la aplicación.
El patrón cache-aside
El patrón más habitual para empezar es cache-aside.
La idea es sencilla:
- La aplicación intenta leer el dato desde Redis.
- Si existe, lo devuelve.
- Si no existe, consulta la fuente real.
- Guarda el resultado en Redis con un TTL.
- Devuelve el dato al consumidor.
En código, el flujo mental sería este:
const cachedValue = await cache.get(key);
if (cachedValue) { return cachedValue;}
const freshValue = await database.findSomething(id);
await cache.set(key, freshValue, { ttlSeconds: 300,});
return freshValue;Este patrón tiene una ventaja importante: la fuente de verdad sigue siendo la base de datos, la API externa o el sistema principal.
Redis acelera la lectura, pero no se convierte automáticamente en el origen definitivo de los datos.
Para muchas aplicaciones web, dashboards internos y APIs de consulta, esto es exactamente lo que queremos.
Diseñar las claves antes de escribir código
Uno de los errores más comunes al usar Redis es improvisar las claves.
Al principio parece algo menor. Una clave por aquí, otra clave por allá, un user:${id} y algún data:${id}.
Pero cuando la solución crece, las claves se convierten en un contrato interno.
Conviene tratarlas con cuidado.
Una buena clave debería decir:
- A qué dominio pertenece.
- Qué versión de estructura usa.
- Qué entidad representa.
- Qué filtros o parámetros incluye.
- Si el dato es privado, compartido o global.
Por ejemplo:
dashboard:v1:tenant:acme:summarycustomer:v2:tenant:acme:id:12345settings:v1:globalrate-limit:v1:login:ip:203.0.113.10El prefijo con versión es especialmente útil.
Si mañana cambia la forma de serializar el dato o el contrato de respuesta, puedes pasar de v1 a v2 sin tener que borrar inmediatamente todas las claves antiguas.
El TTL terminará limpiando lo viejo.
Crear la instancia en Azure
Antes de escribir código, hay varias decisiones de infraestructura que merece la pena cerrar.
Primero, la región. Lo normal es desplegar Redis en la misma región que la aplicación principal para reducir latencia y evitar costes innecesarios de tráfico entre regiones.
Segundo, el tamaño. No elijas el SKU únicamente por memoria. Redis también necesita CPU, red y capacidad de conexiones concurrentes. Una caché pequeña puede quedarse corta no por almacenamiento, sino por ancho de banda o saturación de conexiones.
Tercero, la red. Para producción, lo recomendable es evitar exposición pública cuando sea posible y usar acceso privado desde la red donde vive la aplicación.
Cuarto, la autenticación. En soluciones modernas, la prioridad debería ser Microsoft Entra ID con identidades gestionadas o service principals, no claves compartidas pegadas en variables de entorno.
Quinto, la alta disponibilidad. Si Redis forma parte del camino crítico de la aplicación, hay que diseñar pensando en failover, reconexiones y degradación controlada.
Estas decisiones son arquitectura, no solo configuración.
Configuración mínima de la aplicación
Para el ejemplo vamos a suponer una aplicación Node.js con TypeScript.
Las variables mínimas podrían ser:
REDIS_ENDPOINT=my-cache.westeurope.redis.azure.net:10000En Azure Managed Redis, el endpoint incluye host y puerto. En muchos escenarios verás el puerto 10000 para conexiones TLS.
Para instalar las dependencias:
npm install redis @redis/client @redis/entraid @azure/identityUsaremos:
@azure/identitypara autenticarnos conDefaultAzureCredential.@redis/entraidpara conectar Redis con Microsoft Entra ID.@redis/clientcomo cliente Redis para Node.js.
En local, DefaultAzureCredential puede apoyarse en tu sesión de Azure CLI.
En producción, puede usar la identidad gestionada del recurso donde se ejecuta la aplicación, como App Service, Azure Functions, Container Apps, AKS o una máquina virtual.
Crear un cliente Redis reutilizable
La conexión a Redis no debería crearse en cada petición HTTP.
Lo habitual es crear un cliente reutilizable y mantenerlo durante la vida del proceso. Esto reduce latencia, evita abrir demasiadas conexiones y facilita gestionar reconexiones.
Un ejemplo de cliente para Azure Managed Redis con Microsoft Entra ID sería:
import { DefaultAzureCredential } from "@azure/identity";import { EntraIdCredentialsProviderFactory, REDIS_SCOPE_DEFAULT,} from "@redis/entraid";import { createCluster, type RedisClusterType } from "@redis/client";import { isIP } from "node:net";
let redisClient: RedisClusterType | null = null;
function getRequiredEnv(name: string): string { const value = process.env[name];
if (!value) { throw new Error(`Missing required environment variable: ${name}`); }
return value;}
export async function getRedisClient(): Promise<RedisClusterType> { if (redisClient?.isOpen) { return redisClient; }
const redisEndpoint = getRequiredEnv("REDIS_ENDPOINT"); const [redisHostName] = redisEndpoint.split(":");
const credential = new DefaultAzureCredential(); const credentialsProvider = EntraIdCredentialsProviderFactory.createForDefaultAzureCredential({ credential, scopes: REDIS_SCOPE_DEFAULT, options: {}, tokenManagerConfig: { expirationRefreshRatio: 0.8, }, });
const client = createCluster({ rootNodes: [ { url: `rediss://${redisEndpoint}`, }, ], defaults: { credentialsProvider, socket: { tls: true, connectTimeout: 15_000, keepAlive: 5_000, reconnectStrategy: (retries) => Math.min(retries * 200, 5_000), }, }, nodeAddressMap: (incomingAddress) => { const [hostNameOrIp, port] = incomingAddress.split(":");
return { host: isIP(hostNameOrIp) ? redisHostName : hostNameOrIp, port: Number(port), }; }, });
client.on("error", (error) => { console.error("Redis connection error", { message: error.message, }); });
await client.connect();
redisClient = client; return redisClient;}Hay varios detalles importantes aquí.
Primero, usamos rediss://, es decir, conexión cifrada.
Segundo, no guardamos una contraseña de Redis en el código.
Tercero, usamos DefaultAzureCredential, por lo que el mecanismo de autenticación cambia según el entorno.
Cuarto, el cliente se reutiliza.
Quinto, definimos una estrategia de reconexión sencilla para evitar que un fallo temporal tumbe toda la aplicación de forma innecesaria.
En una aplicación grande, este cliente viviría en un módulo de infraestructura, no dentro de un controlador HTTP.
Una capa de caché con JSON
Redis guarda strings, hashes, listas, sets y otras estructuras.
Para muchos casos de aplicación, una primera capa sencilla basada en JSON es suficiente.
Podemos crear un pequeño wrapper:
import { getRedisClient } from "./redisClient";
type CacheOptions = { ttlSeconds: number;};
export async function getJsonFromCache<T>(key: string): Promise<T | null> { const redis = await getRedisClient(); const value = await redis.get(key);
if (!value) { return null; }
return JSON.parse(value) as T;}
export async function setJsonInCache<T>( key: string, value: T, options: CacheOptions,): Promise<void> { const redis = await getRedisClient();
await redis.set(key, JSON.stringify(value), { EX: options.ttlSeconds, });}
export async function deleteFromCache(key: string): Promise<void> { const redis = await getRedisClient();
await redis.del(key);}Este wrapper es pequeño, pero ya nos da una frontera clara.
El resto de la aplicación no necesita saber si usamos GET, SET, EX, serialización JSON o un cliente concreto.
Más adelante podríamos añadir compresión, métricas, trazas, prefijos automáticos, validación de esquemas o manejo de errores sin cambiar todas las capas superiores.
Implementar un helper cache-aside
Para evitar repetir el mismo patrón en muchos servicios, podemos crear una función remember.
La idea es parecida a decir: “dame este dato desde caché y, si no existe, ejecuto esta función para obtenerlo”.
import { getJsonFromCache, setJsonInCache } from "./cache";
type RememberOptions<T> = { key: string; ttlSeconds: number; loader: () => Promise<T>;};
export async function remember<T>({ key, ttlSeconds, loader,}: RememberOptions<T>): Promise<T> { const cachedValue = await getJsonFromCache<T>(key);
if (cachedValue !== null) { return cachedValue; }
const freshValue = await loader();
await setJsonInCache(key, freshValue, { ttlSeconds, });
return freshValue;}Con esto, los servicios de dominio quedan mucho más limpios.
import { remember } from "../infrastructure/cache/remember";import { dashboardRepository } from "../repositories/dashboardRepository";
type DashboardSummary = { tenantId: string; activeUsers: number; monthlyRevenue: number; openIncidents: number; generatedAt: string;};
export async function getDashboardSummary( tenantId: string,): Promise<DashboardSummary> { return remember({ key: `dashboard:v1:tenant:${tenantId}:summary`, ttlSeconds: 120, loader: async () => dashboardRepository.getSummary(tenantId), });}El servicio no sabe casi nada de Redis.
Solo define una clave, un TTL y cómo obtener el dato fresco.
Eso es una buena señal.
Ejemplo en una API de Next.js
Si llevamos esto a una API, el controlador debería ser bastante simple.
import type { NextApiRequest, NextApiResponse } from "next";import { getDashboardSummary } from "@/server/dashboard/getDashboardSummary";
export default async function handler( req: NextApiRequest, res: NextApiResponse,) { if (req.method !== "GET") { res.setHeader("Allow", "GET"); return res.status(405).json({ error: "Method not allowed" }); }
const tenantId = String(req.query.tenantId ?? "");
if (!tenantId) { return res.status(400).json({ error: "tenantId is required" }); }
const summary = await getDashboardSummary(tenantId);
return res.status(200).json(summary);}La API no construye el cliente Redis.
Tampoco serializa JSON a mano.
Tampoco decide cómo reconectar.
Solo atiende la petición y delega en el servicio correspondiente.
Esta separación parece sencilla, pero en producción marca mucha diferencia.
Elegir bien los TTLs
El TTL es una de las decisiones más importantes al usar Redis.
Un TTL demasiado corto reduce el valor de la caché porque casi siempre acabamos yendo a la fuente original.
Un TTL demasiado largo puede devolver información obsoleta durante más tiempo del aceptable.
No hay un valor universal.
Depende del tipo de dato:
- Configuración global: varios minutos o incluso horas, si cambia poco.
- Dashboards operativos: entre segundos y pocos minutos.
- Resultados de búsquedas costosas: minutos, si el usuario acepta cierta latencia de actualización.
- Permisos de usuario: TTL corto y estrategia clara de invalidación.
- Datos financieros o críticos: mucha cautela, o directamente no cachear si se necesita consistencia estricta.
- Rate limiting: TTL ligado a la ventana de control.
El TTL debe responder a una pregunta de negocio:
¿Cuánto tiempo puede vivir este dato sin volver a validarse?
Cuando no hay una respuesta clara, es mejor empezar con TTLs conservadores y medir.
Invalidación de caché
La invalidación es la parte incómoda de cualquier sistema de caché.
El patrón más simple es dejar que el TTL expire.
Esto funciona bien para datos donde cierta obsolescencia es aceptable.
Pero hay casos donde necesitamos invalidar antes:
- Se actualiza el perfil de un usuario.
- Cambia la configuración de un tenant.
- Se modifica un permiso.
- Se completa una operación que afecta a un dashboard.
- Se borra o modifica una entidad crítica.
En esos casos podemos eliminar claves concretas:
import { deleteFromCache } from "../infrastructure/cache/cache";
export async function updateTenantSettings(tenantId: string, input: unknown) { await settingsRepository.update(tenantId, input);
await deleteFromCache(`settings:v1:tenant:${tenantId}`); await deleteFromCache(`dashboard:v1:tenant:${tenantId}:summary`);}La clave está en no repartir estas invalidaciones por cualquier sitio.
Si el cambio de datos vive en un servicio de dominio, la invalidación debería vivir cerca de ese cambio.
Otra opción es publicar eventos internos y que un consumidor se encargue de invalidar caché:
await eventBus.publish({ type: "tenant.settings.updated", tenantId, occurredAt: new Date().toISOString(),});Ese enfoque escala mejor cuando varios cambios afectan a muchas claves.
Evitar el cache stampede
Un problema típico aparece cuando muchas peticiones piden la misma clave justo después de expirar.
Todas fallan en caché.
Todas van a la base de datos.
Todas recalculan lo mismo.
Eso se conoce como cache stampede.
Una solución sencilla es usar TTLs con algo de variación:
export function ttlWithJitter(baseSeconds: number, jitterSeconds: number) { const jitter = Math.floor(Math.random() * jitterSeconds);
return baseSeconds + jitter;}Y aplicarlo al guardar:
await setJsonInCache(key, value, { ttlSeconds: ttlWithJitter(300, 60),});Así evitamos que miles de claves creadas al mismo tiempo expiren exactamente en el mismo segundo.
Para casos más críticos, podemos añadir bloqueo distribuido o refresco en segundo plano.
Pero conviene empezar por lo simple.
Bloqueo distribuido con cuidado
Redis también puede utilizarse para coordinación ligera entre procesos.
Por ejemplo, para evitar que varias instancias ejecuten el mismo trabajo pesado a la vez.
Una forma básica es usar SET con NX y expiración:
import { randomUUID } from "node:crypto";import { getRedisClient } from "./redisClient";
export async function withRedisLock<T>( lockKey: string, ttlSeconds: number, task: () => Promise<T>,): Promise<T | null> { const redis = await getRedisClient(); const lockValue = randomUUID();
const acquired = await redis.set(lockKey, lockValue, { NX: true, EX: ttlSeconds, });
if (acquired !== "OK") { return null; }
try { return await task(); } finally { const currentValue = await redis.get(lockKey);
if (currentValue === lockValue) { await redis.del(lockKey); } }}Esto puede servir para tareas sencillas.
Pero si el bloqueo protege operaciones críticas, pagos, inventario o consistencia fuerte, hay que diseñarlo con mucha más seriedad.
Redis puede coordinar, pero no deberíamos convertir un lock casero en la garantía principal de integridad de negocio.
Rate limiting con Redis
Otro caso de uso muy común es limitar peticiones.
Por ejemplo, controlar intentos de login por IP o por usuario.
import { getRedisClient } from "./redisClient";
type RateLimitResult = { allowed: boolean; remaining: number;};
export async function checkRateLimit( key: string, limit: number, windowSeconds: number,): Promise<RateLimitResult> { const redis = await getRedisClient(); const hits = await redis.incr(key);
if (hits === 1) { await redis.expire(key, windowSeconds); }
return { allowed: hits <= limit, remaining: Math.max(limit - hits, 0), };}El uso sería:
const result = await checkRateLimit( `rate-limit:v1:login:ip:${clientIp}`, 10, 60,);
if (!result.allowed) { return res.status(429).json({ error: "Too many requests" });}Este patrón es simple y efectivo, aunque en sistemas con mucho tráfico puede interesar usar scripts Lua o algoritmos más precisos como sliding window.
Lo importante es que Redis permite tomar decisiones rápidas sin golpear la base de datos en cada intento.
Seguridad: mejor identidad que claves
En una integración moderna con Azure, el enfoque recomendado es evitar secretos estáticos siempre que sea posible.
Con Microsoft Entra ID y DefaultAzureCredential, el código puede autenticarse de forma distinta según el entorno:
- En local, usando
az login. - En CI/CD, usando una identidad de workload o service principal.
- En producción, usando una identidad gestionada del recurso de Azure.
Este modelo tiene varias ventajas:
- No hay contraseñas Redis dentro del repositorio.
- No hay claves compartidas que rotar manualmente en la aplicación.
- Los permisos pueden asignarse a identidades concretas.
- Se puede aplicar mínimo privilegio.
- La auditoría es más clara.
Para que funcione, no basta con que la aplicación tenga identidad.
También hay que conceder acceso de datos a esa identidad en el recurso Redis, normalmente desde la configuración de autenticación del propio servicio.
La identidad de la aplicación debe tener solo los permisos que necesita.
No todas las aplicaciones necesitan permisos administrativos.
Red: no todo debe ser público
Una prueba de concepto puede usar un endpoint público.
Una solución de producción debería revisar la red con más cuidado.
Si la aplicación vive en Azure, lo habitual es conectar con Redis mediante acceso privado, restringir tráfico y evitar que el endpoint quede expuesto innecesariamente a internet.
Esto suele implicar:
- Private Endpoint o conectividad privada equivalente.
- Restricciones de red.
- DNS privado correctamente configurado.
- Aplicación y Redis en regiones cercanas.
- TLS en la conexión.
- Reglas claras para entornos de desarrollo, staging y producción.
La red no debería ser una ocurrencia tardía.
Si Redis forma parte del camino crítico, la conectividad con Redis forma parte del diseño de disponibilidad de la aplicación.
Resiliencia: Redis también falla
Aunque sea un servicio administrado, Redis puede tener failovers, mantenimiento, problemas de red, saturación o límites de conexión.
La aplicación debe estar preparada.
Algunas recomendaciones prácticas:
- Reutiliza conexiones.
- Configura reconexión con backoff.
- Añade timeouts razonables.
- Evita operaciones bloqueantes o claves gigantes.
- No hagas
KEYS *en producción. - Usa TTLs para evitar crecimiento infinito.
- Diseña degradación si Redis no está disponible.
- Mide latencia, errores, memoria, CPU y evictions.
Un punto importante: si Redis es solo una caché, la aplicación debería poder funcionar degradada cuando Redis falla.
Quizá más lenta.
Quizá con más presión sobre la base de datos.
Pero no necesariamente caída.
Esto depende del caso. Si Redis guarda sesiones, rate limits o colas críticas, entonces su caída tiene un impacto mayor y hay que diseñar alta disponibilidad y recuperación con más rigor.
Manejar errores sin romper la experiencia
En un patrón cache-aside, un error al leer Redis no siempre debería romper la petición.
Podemos decidir que, si Redis falla, consultamos la fuente principal y seguimos.
Por ejemplo:
export async function rememberWithFallback<T>({ key, ttlSeconds, loader,}: RememberOptions<T>): Promise<T> { try { const cachedValue = await getJsonFromCache<T>(key);
if (cachedValue !== null) { return cachedValue; } } catch (error) { console.warn("Cache read failed", { key, message: error instanceof Error ? error.message : "Unknown error", }); }
const freshValue = await loader();
try { await setJsonInCache(key, freshValue, { ttlSeconds, }); } catch (error) { console.warn("Cache write failed", { key, message: error instanceof Error ? error.message : "Unknown error", }); }
return freshValue;}Este enfoque tiene sentido para cachés de lectura.
No tiene sentido para todos los casos.
Si Redis se usa para rate limiting, sesiones o coordinación, ignorar el error puede abrir problemas de seguridad o consistencia.
La regla es sencilla: decide explícitamente qué pasa cuando Redis falla.
No lo dejes al azar.
Observabilidad
Una caché sin observabilidad puede engañar mucho.
Puede parecer que todo va bien porque la aplicación responde, pero por debajo quizá Redis no está aportando casi nada.
O peor: quizá Redis está generando latencia adicional, errores de conexión o evictions constantes.
Merece la pena medir:
- Porcentaje de aciertos y fallos de caché.
- Latencia de lectura y escritura.
- Número de conexiones.
- Uso de memoria.
- Evictions.
- Timeouts.
- Errores de autenticación.
- Tamaño medio de valores.
- Claves más usadas.
En la aplicación, podemos empezar con métricas simples:
const startedAt = Date.now();const cachedValue = await getJsonFromCache<T>(key);const durationMs = Date.now() - startedAt;
metrics.histogram("cache.read.duration_ms", durationMs, { cache: "azure-managed-redis",});
metrics.increment(cachedValue ? "cache.hit" : "cache.miss", { keyPrefix: key.split(":").slice(0, 3).join(":"),});No conviene meter claves completas en métricas si contienen IDs sensibles o alta cardinalidad.
Es mejor registrar prefijos controlados, dominios o nombres lógicos de operación.
Qué no deberíamos guardar en Redis
Redis es rápido, pero no todo debe ir a Redis.
Evitaría cachear:
- Datos personales sensibles sin una razón clara.
- Secretos.
- Tokens de larga duración.
- Respuestas enormes.
- Objetos sin TTL.
- Datos que requieren consistencia estricta en cada lectura.
- Información que no sabemos invalidar.
- Estructuras que nadie podrá entender dentro de tres meses.
También tendría cuidado con usar Redis como base de datos principal.
Puede ser una opción válida en ciertos diseños, especialmente con persistencia, replicación y estructuras Redis bien pensadas.
Pero no debería ocurrir por accidente.
Si Redis es la fuente de verdad, la arquitectura debe reconocerlo explícitamente.
Pruebas locales
Para desarrollo local, puedes usar una instancia Redis en Docker para validar lógica de claves, TTLs y serialización.
docker run --rm -p 6379:6379 redis:7Pero hay una diferencia importante: ese Redis local no prueba la autenticación con Microsoft Entra ID ni las condiciones reales de red de Azure Managed Redis.
Por eso separaría las pruebas en varios niveles:
- Unitarias para construcción de claves y serialización.
- Integración local contra Redis en Docker.
- Integración real contra Azure Managed Redis en un entorno de desarrollo o staging.
- Pruebas de resiliencia simulando timeouts o caídas.
La lógica de caché puede probarse bastante bien si está encapsulada.
Otro motivo más para no repartir llamadas Redis por todo el código.
Ejemplo de test para cache-aside
La función remember es fácil de probar si mockeamos la capa de caché.
Un test podría validar que el loader solo se ejecuta cuando no hay dato cacheado.
import { remember } from "./remember";import * as cache from "./cache";
jest.mock("./cache");
describe("remember", () => { it("returns cached value when it exists", async () => { jest.spyOn(cache, "getJsonFromCache").mockResolvedValue({ id: "123", name: "cached", });
const loader = jest.fn();
const result = await remember({ key: "customer:v1:id:123", ttlSeconds: 60, loader, });
expect(result).toEqual({ id: "123", name: "cached", }); expect(loader).not.toHaveBeenCalled(); });
it("loads and stores fresh value on cache miss", async () => { jest.spyOn(cache, "getJsonFromCache").mockResolvedValue(null); jest.spyOn(cache, "setJsonInCache").mockResolvedValue();
const loader = jest.fn().mockResolvedValue({ id: "123", name: "fresh", });
const result = await remember({ key: "customer:v1:id:123", ttlSeconds: 60, loader, });
expect(result).toEqual({ id: "123", name: "fresh", }); expect(cache.setJsonInCache).toHaveBeenCalledWith( "customer:v1:id:123", { id: "123", name: "fresh", }, { ttlSeconds: 60, }, ); });});Este tipo de test no valida Azure, pero sí valida nuestra lógica.
La integración real con Azure debe probarse aparte.
Un caso práctico completo
Imaginemos una aplicación que muestra el resumen operativo de un tenant.
El resumen combina:
- Usuarios activos.
- Facturación mensual.
- Incidencias abiertas.
- Última sincronización.
Calcularlo requiere varias consultas y una llamada a un servicio externo.
No queremos hacerlo en cada refresco del dashboard.
Podemos modelarlo así:
type TenantOverview = { tenantId: string; activeUsers: number; monthlyRevenue: number; openIncidents: number; lastSyncAt: string | null; generatedAt: string;};
function tenantOverviewCacheKey(tenantId: string) { return `tenant-overview:v1:tenant:${tenantId}`;}
export async function getTenantOverview( tenantId: string,): Promise<TenantOverview> { return rememberWithFallback({ key: tenantOverviewCacheKey(tenantId), ttlSeconds: ttlWithJitter(180, 30), loader: async () => { const [activeUsers, monthlyRevenue, openIncidents, lastSyncAt] = await Promise.all([ usersRepository.countActiveUsers(tenantId), billingService.getMonthlyRevenue(tenantId), incidentsRepository.countOpenIncidents(tenantId), syncRepository.getLastSyncAt(tenantId), ]);
return { tenantId, activeUsers, monthlyRevenue, openIncidents, lastSyncAt, generatedAt: new Date().toISOString(), }; }, });}Este ejemplo resume bastante bien el valor de Redis.
No estamos cacheando por cachear.
Estamos protegiendo una operación cara, reduciendo latencia y haciendo que el dashboard sea más estable.
Redis como parte del modelo operativo
Cuando Redis entra en producción, también entra en el modelo operativo.
Hay que saber responder:
- Qué ocurre si aumenta la latencia.
- Qué ocurre si se llena la memoria.
- Qué ocurre si hay muchas evictions.
- Qué ocurre durante un failover.
- Qué ocurre si expiran miles de claves a la vez.
- Qué ocurre si una versión nueva de la app cambia el formato de los valores.
Estas preguntas no son para complicar la solución.
Son para evitar que una mejora de rendimiento se convierta en una dependencia invisible.
Redis debería mejorar el sistema, no hacerlo más misterioso.
Buenas prácticas para integrar Redis de Azure
Estas son las reglas que intentaría seguir en una solución real.
Primero, usa una capa interna para Redis. No disperses el cliente por toda la aplicación.
Segundo, define convenciones de claves desde el inicio.
Tercero, usa TTLs explícitos. Una clave sin expiración debería ser una excepción consciente.
Cuarto, autentica con Microsoft Entra ID siempre que sea posible.
Quinto, usa identidades gestionadas en producción.
Sexto, evita endpoints públicos en entornos productivos si puedes usar conectividad privada.
Séptimo, reutiliza conexiones.
Octavo, diseña qué pasa cuando Redis falla.
Noveno, mide hits, misses, latencia, errores y evictions.
Décimo, no guardes datos sensibles sin necesidad.
Undécimo, versiona las claves cuando cambie el contrato del dato.
Duodécimo, prueba la integración real en Azure antes de asumir que Docker local representa producción.
Mi recomendación bajo mi experiencia
Integrar Redis de Azure en una solución cloud nativa no debería verse como un detalle menor de infraestructura.
Una caché bien diseñada puede reducir latencia, mejorar experiencia de usuario, proteger bases de datos, suavizar picos de tráfico y hacer que una aplicación sea más predecible.
Pero una caché mal integrada puede introducir datos obsoletos, errores difíciles de reproducir, dependencias ocultas y problemas de seguridad.
Por eso me gusta tratar Redis como una capacidad interna de la plataforma.
No como una llamada suelta.
Azure Managed Redis encaja bien en este enfoque porque permite apoyarse en un servicio administrado, autenticación con Microsoft Entra ID, conexiones cifradas, opciones de disponibilidad y una integración natural con aplicaciones desplegadas en Azure.
La clave está en rodearlo de buenas decisiones: patrón cache-aside, TTLs claros, invalidación controlada, observabilidad, resiliencia y una capa propia dentro del backend.
Si hacemos eso, Redis deja de ser solo una caché rápida.
Se convierte en una pieza útil para construir soluciones cloud más estables, más seguras y más agradables de operar.
Referencias útiles
- Azure Managed Redis - documentación oficial
- Qué es Azure Managed Redis
- Conectar Azure Managed Redis con Node.js y TypeScript
- Autenticación con Microsoft Entra ID en Azure Managed Redis
- Buenas prácticas de resiliencia de conexión
- Buenas prácticas de desarrollo con Azure Managed Redis


