Redis pub/sub
Un Dynamic Snapshot sirve todo el contenido desde la memoria del servidor y lo refresca en caliente cuando publicas.
Sin rebuild. Sin una llamada al API por lectura.
El patrón tiene dos piezas:
- De dónde se lee el snapshot → el
snapshotLoader. - Quién lanza el refresco →
refreshSnapshot().
En esta versión el JSON vive en Redis.
Un publisher exporta el snapshot con exportSnapshot(), hace SET + PUBLISH, y todas las instancias suscritas se refrescan a la vez con un único mensaje.
¿Cómo funciona?
- Origen: Redis. El
snapshotLoaderlee la key del snapshot. - Trigger: pub/sub en vez de un
POSTpor instancia.
Un único PUBLISH notifica a todas las réplicas suscritas, y cada una llama a refreshSnapshot().
¿Cuándo elegirla?
Elígela para escalado horizontal: refresca N instancias con un solo mensaje, sin webhooks por instancia ni N llamadas al API.
Necesitas un Redis accesible por todas las réplicas.
Paso a paso
Parte del proyecto de la versión por endpoint.
Cambiamos el snapshotLoader por Redis, añadimos la suscripción y un publisher.
-
Levanta Redis e instala dependencias:
Ventana de terminal docker run -d --name redis -p 6379:6379 redis:7npm i redisnpm i -D tsx # para ejecutar el publisher en TypeScript.env REDIS_URL=redis://localhost:6379 -
Crea la conexión a Redis:
src/server/redis.ts import 'dotenv/config';import { createClient as createRedis } from 'redis';export const redis = createRedis({ url: process.env.REDIS_URL });export const redisReady = redis.connect(); -
Cambia el
snapshotLoaderpara leer de Redis y suscríbete al canal. Al primer uso, cada instancia escucha y se refresca sola cuando el publisher avisa:src/server/content-island.ts const SNAPSHOT_KEY = 'content-island:snapshot';const CHANNEL = 'content-island:updated';// snapshotLoader del cliente: lee el JSON de RedissnapshotLoader: async () => {await redisReady;return (await redis.get(SNAPSHOT_KEY)) ?? '';},// Suscripción + carga iniciallet primed: Promise<unknown> | null = null;export function ensureSnapshot() {if (!primed) {primed = (async () => {await redisReady;const sub = redis.duplicate();await sub.connect();await sub.subscribe(CHANNEL, () => {contentIslandClient.refreshSnapshot().catch(console.error);});await contentIslandClient.refreshSnapshot(); // carga inicial})();}return primed;} -
Crea el publisher: exporta, hace
SETyPUBLISH:scripts/publish-redis.ts import 'dotenv/config';import { exportSnapshot } from '@content-island/api-client';import { createClient as createRedis } from 'redis';const redis = createRedis({ url: process.env.REDIS_URL });await redis.connect();const snapshot = await exportSnapshot({ accessToken: process.env.CONTENT_ISLAND_TOKEN! });await redis.set('content-island:snapshot', JSON.stringify(snapshot));await redis.publish('content-island:updated', '1');await redis.quit(); -
Pruébalo con dos instancias (que es la gracia). Haz una primera carga en Redis y arranca dos apps en puertos distintos:
Ventana de terminal set -a; source .env; set +anpx tsx scripts/publish-redis.ts # primera carga en RedisPORT=3000 npm run dev # en una terminalPORT=3001 npm run dev # en otraPublica contenido nuevo y lanza el publisher una sola vez. Recarga las dos páginas: ambas actualizan su
exportedAta la vez, con un únicoPUBLISH.
Ejemplo
Código completo (incluye el docker-compose.yml): content-island/examples-dynamic-snapshot → 03-redis-pub-sub.