Notificaciones de keyspace que sobreviven a la conmutación por error
Las notificaciones de keyspace son una de las funciones más útiles de Redis y una de las más silenciosamente frágiles. La idea es preciosa: cada vez que se escribe, borra o expira una clave, el servidor publica un evento en un canal pub/sub bien conocido, y cualquiera a quien le interese puede hacer PSUBSCRIBE y reaccionar — invalidar una caché, despertar a un worker, refrescar una proyección. Sin sondeo, sin un GET en bucle preguntando "¿ya ha cambiado?".
La fragilidad está en la letra pequeña. Las notificaciones de keyspace de Redis son pub/sub de disparar-y-olvidar, emitidas por el nodo que procesó el comando, a los suscriptores conectados a ese mismo nodo. En un despliegue en clúster eso significa que un suscriptor solo se entera de las escrituras que aterrizaron en el nodo al que está conectado, y si ese nodo sufre una conmutación por error, la suscripción — y el flujo de eventos que transportaba — se va con él. La función funciona de maravilla en una sola máquina y se vuelve sutil rápidamente en cuanto hay más de una.
Zaris habla exactamente el mismo protocolo — CONFIG SET notify-keyspace-events, los canales __keyspace@0__ y __keyevent@0__, un PSUBSCRIBE corriente desde cualquier cliente de Redis en cualquier lenguaje. Pero la fontanería de debajo no es una reimplementación del notificador de Redis. Es un puente fino hacia una función que Zaris ya tenía: un watch nativo, a nivel de todo el clúster, replicado. Eso cambia lo que valen las notificaciones.
Activarlo — exactamente al estilo de Redis
No hay nada específico de Zaris en cómo activas o consumes la función. Apunta cualquier cliente de Redis al front-end RESP y usa los comandos que ya conoces:
# Enable notifications for keyspace (K) and keyevent (E) channels
redis-cli -p 6380 CONFIG SET notify-keyspace-events KEA
# Subscribe to every key-event, cluster-wide
redis-cli -p 6380 PSUBSCRIBE '__keyevent@0__:*'
1) "pmessage"
2) "__keyevent@0__:*"
3) "__keyevent@0__:set"
4) "user:42"
Desde un cliente tipado — aquí StackExchange.Redis — tiene la misma forma, sin ningún SDK de Zaris de por medio:
var mux = await ConnectionMultiplexer.ConnectAsync("localhost:6380");
var sub = mux.GetSubscriber();
await sub.SubscribeAsync(
new RedisChannel("__keyevent@0__:expired", RedisChannel.PatternMode.Literal),
(channel, key) => Console.WriteLine($"key expired: {key}"));
El punto que vale la pena subrayar: ningún cambio de aplicación. El código escrito contra las notificaciones de keyspace de Redis corre contra Zaris sin modificar. Esa es toda la promesa del front-end RESP — tus clientes no necesitan saber qué hay detrás del socket.
Las dos familias de canales
Zaris publica las dos familias de canales estándar de Redis, con la convención de carga útil (payload) estándar — a cuál te suscribas depende de si te orientas por la clave o por el evento:
| Canal | Publicado cuando la bandera… | Carga útil | Úsalo cuando te importa… |
|---|---|---|---|
__keyspace@0__:<key> | K está activada | el nombre del evento (set / del / expired) | una clave específica — "dime todo lo que le pasa a session:abc" |
__keyevent@0__:<event> | E está activada | la clave que cambió | una clase de eventos — "dime cada clave que acaba de expirar" |
El @0 es el índice de base de datos, y en Zaris es siempre 0 — Zaris no divide el keyspace en bases de datos numeradas, así que hay un único espacio de nombres de notificaciones, plano, en lugar de dieciséis. Los tres nombres de evento se corresponden con las tres cosas que le pueden pasar a una clave:
set— la clave fue escrita (unSET, unHSET, unLPUSH, cualquier mutación)del— la clave fue eliminadaexpired— la clave fue desalojada porque su TTL transcurrió
Qué ocurre realmente por debajo
Cuando envías CONFIG SET notify-keyspace-events KEA, Zaris no enciende un emisor de eventos de Redis a medida cableado en la ruta de comandos. Registra un watch de prefijo sobre todo el keyspace en el propio cliente de enrutamiento del nodo — la misma primitiva Watch que el cliente nativo de .NET expone para lecturas reactivas sin sondeo. A partir de ese momento, la única lógica que vive en la capa RESP es un mapa de nombres: llega un evento de watch nativo, y el puente lo convierte en el nombre de canal de Redis y la carga útil correctos.
// The entire RESP-side translation, essentially verbatim:
var eventName = e.EventType switch
{
WatchEventType.Put => "set",
WatchEventType.Delete => "del",
WatchEventType.Evicted => "expired",
_ => null, // snapshots / heartbeats carry no keyspace meaning
};
if (keyspace) PubSub.Publish($"__keyspace@0__:{e.Key}", eventName);
if (keyevent) PubSub.Publish($"__keyevent@0__:{eventName}", e.Key);
Esa llamada a Publish es el pub/sub nativo — la misma maquinaria de suscribir/entregar, con su propio fan-out a nivel de todo el clúster, que el almacén ya ejecuta. El front-end RESP posee el vocabulario (nombres de canal, convención de carga útil); el clúster posee la entrega. Este es un patrón que mantenemos deliberadamente a lo largo de todo el producto: cada función es primero una capacidad nativa, y RESP es una piel fina sobre ella — nunca una implementación paralela con sus propios errores.
Por qué "el watch es nativo" es todo el asunto
Aquí es donde divergen los dos sistemas, y no es cosmético.
Una notificación de keyspace de Redis se produce en el nodo que ejecutó el comando y se entrega a los suscriptores conectados a ese nodo. Nada de la suscripción se comparte con el resto del clúster, y nada de ella es duradero. Es pub/sub en sentido estricto — si no estabas escuchando en el nodo correcto en el instante correcto, el evento simplemente no existió para ti.
Una notificación de keyspace de Zaris viaja sobre un watch nativo, y un watch nativo es un objeto a nivel de todo el clúster:
- La suscripción se replica entre los miembros.
WatchSyncServicemantiene las suscripciones de watch sincronizadas entre nodos — el líder empuja el conjunto actual a un nodo que se une, y un nodo que arranca pide al líder lo que está activo. La suscripción no es un hecho conocido solo por un proceso; es estado del clúster. - Se reubica a través de los cambios (churn). Cuando las particiones se mueven — conmutación por error, migración, un nodo que se va — el watch se mueve con los datos que vigila, igual que la propiedad de las claves. La escritura sigue produciendo un evento una vez que se asienta el polvo, porque el vigilante siguió a la partición.
- La entrega se distribuye a nivel de todo el clúster. Como la publicación va a través del pub/sub nativo, un cliente conectado a cualquier nodo recibe eventos de escrituras que ocurrieron en cualquier nodo. No estás suscrito silenciosamente a la actividad de un solo shard.
La consecuencia práctica: un suscriptor que vigila __keyevent@0__:expired para impulsar la invalidación de caché sigue funcionando a través de un fallo de nodo. En Redis Cluster ese mismo suscriptor está conectado a un nodo y ve las expiraciones de un nodo; la mitigación honesta allí es abrir una suscripción a cada nodo y desduplicar. En Zaris es una suscripción, completa a nivel de clúster, y sobrevive a la conmutación por error que la habría cortado en silencio.
Los bordes honestos
Esto es un puente sobre pub/sub de mejor esfuerzo, no una actualización mágica de fiabilidad. Tres cosas que conviene tener claras:
La entrega sigue siendo de mejor esfuerzo, no un registro garantizado. El puente publica cada evento de disparar-y-olvidar: un fallo al entregar a un suscriptor nunca debe atascar la tubería del watch para todos los demás, así que una notificación a un cliente momentáneamente desconectado se descarta, no se almacena en búfer. Esto coincide con el propio contrato de Redis — las notificaciones de keyspace nunca fueron un registro de eventos exactamente-una-vez, y no deberías tratar las de Zaris como tal. Si un evento perdido corrompiera tu estado, las notificaciones son el aviso, no la fuente de verdad: ante el evento, ve y lee el valor autoritativo.
Sin relleno retroactivo (backfill). El watch se arma sin instantánea inicial, así que obtienes los cambios desde el momento en que se activan las notificaciones en adelante — nunca una reproducción de lo que el keyspace ya contenía. Activa las notificaciones y luego empieza a razonar sobre los deltas.
La granularidad de las banderas es gruesa. Redis te permite seleccionar clases de eventos con una cadena de banderas densa (g, $, l, s, h, z, x, e, …). Zaris lee las dos banderas que deciden dónde publicar — K para los canales de keyspace, E para los canales de keyevent (y A como el atajo habitual de "todas las clases") — y puentea los tres tipos de evento nativos siempre que cualquiera de las dos esté activada. No filtra hasta suscripciones por clase. En la práctica te suscribes a los canales y patrones que te importan y dejas que la coincidencia de patrones haga el estrechamiento, que es como se escribe de todos modos la mayoría del código de notificaciones de keyspace.
Si necesitas notificaciones acotadas a una parte del keyspace en lugar de a todo él, configura ZARIS_RESP_KEYSPACE_PREFIX en el nodo y el watch subyacente se arma sobre ese prefijo en lugar de sobre todo el almacén — una forma barata de evitar que un keyspace ocupado publique eventos a los que nadie se suscribe.
Cuando quieres un registro en lugar de un timbre
Las notificaciones de keyspace son un timbre: te dicen que pasó algo, ahora mismo, a quien esté escuchando. Cuando lo que realmente necesitas es un registro reproducible y duradero — cada evento, en orden, legible por un consumidor que se unió tarde o se cayó y volvió — eso no es una notificación, es un stream. Zaris tiene Redis Streams nativos exactamente para eso: XADD para añadir, XREADGROUP con grupos de consumidores para procesamiento al-menos-una-vez con confirmaciones, lecturas bloqueantes para baja latencia. Echa mano de las notificaciones cuando un evento perdido solo signifique "vuelve a comprobar la próxima vez"; echa mano de Streams cuando un evento perdido sea un error.
Usada por lo que es — un aviso de baja latencia y sin sondeo que, en Zaris, resulta ser completo a nivel de clúster y duradero ante conmutaciones por error — la notificación de keyspace es una de las cosas más agradables que puedes activar con un solo CONFIG SET. La diferencia solo es visible el día en que un nodo desaparece, que es precisamente el día en que habrías descubierto los límites de la versión de un solo nodo por las malas.