Saltar al contenido principal

Dos clústeres, un change feed: replicar a través de una WAN sin estirar Zaris

· 11 min de lectura
Clustron Team
Ingeniería de Sistemas Distribuidos

Dos clústeres independientes enlazados por un change feed reanudable a través de una WAN

Tarde o temprano alguien hace la pregunta: tenemos un clúster en una región y usuarios en otra —¿no podemos simplemente añadir unos cuantos nodos allá y dejar que un clúster abarque ambas? Es un instinto razonable. Un clúster, un namespace, una cadena de conexión, datos en todas partes. El instinto también es una trampa, y vale la pena ser preciso sobre por qué.

Un clúster de Zaris está diseñado para una red de datacenter. La membresía se rastrea con heartbeats en temporizadores cortos. La propiedad de una partición se mueve entre nodos mediante un handshake que asume que el otro lado responde rápido. El failover aísla a un propietario sospechoso de estar muerto y promueve una réplica en cuestión de segundos. Cada uno de esos mecanismos está calibrado para un enlace donde el viaje de ida y vuelta se mide en microsegundos y un nodo "lento" está genuinamente enfermo —no meramente a ochenta milisegundos y un océano de distancia. Pon la mitad de los nodos al otro lado de una WAN y no habrás construido un clúster geo-distribuido; habrás construido un clúster que ahora confunde la latencia normal de WAN con un fallo, flapea la propiedad y arriesga split-brain la primera vez que el enlace entre regiones tiene un hipo.

Así que la forma correcta no es un clúster estirado. Son dos clústeres independientes —cada uno autocontenido, cada uno con su propia propiedad y failover quedándose enteramente en su propia LAN— unidos por un change feed asíncrono y reanudable. Este artículo trata sobre ese patrón: por qué la independencia es la decisión portante, y cómo el log de operaciones de Zaris ya te entrega lo único que un replicador de WAN realmente necesita —un cursor desde el cual reanudar.

Por qué un clúster estirado es la herramienta equivocada​

Ayuda nombrar las suposiciones específicas que hace un único clúster, porque cada una se rompe de forma distinta sobre una WAN.

  • La membresía basada en heartbeats decide que un nodo se ha ido cuando pierde unos pocos latidos. Un enlace entre regiones que añade latencia y la partición ocasional hará saltar ese detector durante la operación saludable. El clúster declarará muertos a nodos vivos.
  • El protocolo de propiedad mueve particiones entre nodos con un handshake que espera una respuesta pronta. A través de una WAN, esas transiciones se estancan, reintentan y compiten —exactamente el churn que la maquinaria de propiedad y cutover está diseñada para resolver rápidamente en una LAN.
  • Los temporizadores de failover promueven una réplica cuando el propietario parece muerto. Sobre una WAN, "parece muerto" y "solo está lejos" se vuelven indistinguibles, así que obtienes promociones espurias —y si el enlace se sana con una réplica promovida en cada lado, tienes dos propietarios para una partición.

Nada de esto es un defecto. Es un almacén haciendo exactamente lo que un almacén de baja latencia en datacenter debería hacer, aplicado a una red que nunca fue pensado para abarcar. El arreglo no es aflojar cada temporizador hasta que el clúster tolere una WAN —eso también lo haría tolerar fallos reales demasiado lentamente. El arreglo es dejar de pedirle a un clúster que lo haga.

El bloque de construcción que ya tienes: un log reproducible​

Zaris mantiene, por segmento, un log de operaciones ordenado por revisión. Cada escritura aceptada avanza una revisión monótona y añade una entrada. Ese log es el sustrato que las réplicas y los nuevos propietarios de partición usan para ponerse al día sin congelar las escrituras: un consumidor que conoce la última revisión que vio puede reproducir hacia adelante desde ese punto y converger a la verdad actual.

Esa propiedad —reproducir hacia adelante desde una revisión conocida— es el fundamento entero de un replicador de WAN. Un change feed no es más que un lector de larga vida de ese log que transmite entradas en orden de revisión y recuerda dónde se detuvo. Lo que recuerda es un cursor: la revisión más alta que ha aplicado de forma durable, por segmento.

Como el log está ordenado por revisión y la reproducción comienza desde un punto que eliges, el feed tiene los dos rasgos que un enlace de WAN exige:

  1. Reanudable. El enlace se caerá —ese es el hecho definitorio de una WAN. Cuando vuelve, el seguidor reconecta y pide todo lo posterior a su cursor almacenado. Sin re-copia completa, sin coordinación, solo "reanudar desde la revisión N".
  2. Idempotente en la reproducción. Aplica una entrada solo cuando su revisión es mayor que la última aplicada para ese segmento. Si una reconexión re-entrega la cola, el seguidor omite lo que ya tiene. La entrega duplicada se convierte en un no-op, que es lo que permite que el feed sea al-menos-una-vez en lugar de tener que ser exactamente-una-vez.

El patrón, de principio a fin​

Dos clústeres. Llámalos A (la fuente) y B (el seguidor). Cada uno es un clúster de Zaris ordinario e independiente que no sabe nada de los internos del otro.

En el clúster B ejecutas un seguidor de replicación —un cliente que consume el change feed de A y escribe las operaciones en B localmente. Su estado es diminuto y durable: un cursor por segmento. El bucle es poco glamoroso, que es el punto:

  1. Conéctate a A, solicita el change feed para los segmentos (o prefijos de clave) que quieres replicar, empezando después del cursor almacenado de cada segmento.
  2. Recibe entradas en orden de revisión. Para cada una, aplica la operación a B —SET, DELETE, expiración— protegida por la comprobación de revisión para que la re-entrega sea inofensiva.
  3. Avanza y persiste el cursor a medida que las entradas se aplican de forma durable.
  4. Ante cualquier desconexión, vuelve al paso 1. El cursor es donde lo dejaste.

Todo lo que el seguidor necesita para sobrevivir a una interrupción vive en ese cursor. No hay transacción distribuida a través de la WAN, ni membresía compartida, ni quórum entre regiones. Un hipo en el enlace no te cuesta más que un poco de frescura.

Lo que estás negociando: el RPO no es cero, y ese es el trato​

Esta replicación es asíncrona. El seguidor siempre está a cierta distancia por detrás de la fuente —tantas revisiones como se hayan producido en A pero aún no se hayan aplicado en B. Esa distancia es tu Objetivo de Punto de Recuperación (Recovery Point Objective): si la región A desaparece en este instante, pierdes lo que aún no había llegado a B.

Eso no es un defecto por el que disculparse; es el trato que estás eligiendo explícitamente. La alternativa —hacer que las escrituras en A esperen un acuse de recibo de B antes de confirmar— pondría un viaje de ida y vuelta de WAN en tu ruta de escritura y rendiría la baja latencia que es la razón entera por la que ejecutas Zaris. La replicación síncrona entre regiones convierte cada escritura en una solicitud transcontinental. Casi nadie quiere eso en realidad una vez que ven la factura de latencia.

Así que el encuadre honesto es: tú eliges el RPO. Un seguidor bien alimentado sobre un enlace saludable se mantiene dentro de un puñado de revisiones —obsolescencia por debajo del segundo en la práctica. Un enlace saturado o que flapea lo deja derivar más. El cursor hace la deriva acotada y visible —puedes medir exactamente cuánto va B por detrás comparando su cursor con la revisión actual de A— pero no la hace cero. Dimensiona tu tolerancia en consecuencia, y alerta sobre el retraso del cursor igual que alertarías sobre el retraso de una réplica dentro de un clúster.

Cuando la fuente hace failover, al feed no le importa​

Aquí es donde construir sobre el log de operaciones rinde una segunda vez. Dentro del clúster A, la partición que tu feed está leyendo ocasionalmente cambiará de propietario —un nodo muere, una réplica es promovida, una partición migra. El log sobrevive a eso: está indexado por revisión, y un nuevo propietario continúa la misma secuencia de revisión. Para el seguidor, un failover del lado de la fuente es solo otra reconexión. Re-establece el feed contra la topología actual de A y reanuda desde su cursor. Las revisiones que ya aplicó siguen siendo las revisiones que ya aplicó; las que le faltan siguen estando después del cursor. El seguidor nunca tiene que entender quién en A posee la partición —solo qué revisión vio por última vez.

Esta es la razón silenciosa por la que el diseño de cursor-sobre-log vale más que un relé ingenuo de "sigue las escrituras y envíalas". Un relé que transmite escrituras en vivo sin una posición durable e indexada por revisión no tiene nada desde lo cual reanudar cuando la topología de la fuente cambia. El log le da al seguidor un sistema de coordenadas estable que sobrevive a cualquier propietario individual.

El alcance es una elección, y los conflictos son una frontera​

Dos cosas más sobre las que ser deliberado.

Alcance de los datos. No tienes que replicar todo. El feed es por segmento, y puedes seleccionar los segmentos o prefijos de clave que importan —un tenant específico, un dataset específico, la porción que una región de recuperación ante desastres realmente necesita. Replicar menos significa menos tráfico de WAN y un seguidor más pequeño; replicar todo te da un standby cálido completo. Decide a propósito en lugar de por defecto.

Dirección y conflictos. El patrón anterior es unidireccional: A es la fuente de verdad, B es un seguidor / standby mayormente de lectura. Ese es el caso limpio, y no tiene problema de conflictos —hay exactamente un escritor por clave. En el momento en que lo haces bidireccional —ambas regiones aceptando escrituras para las mismas claves— has introducido escritores concurrentes separados por una WAN, y ninguna cantidad de plomería de change feed lo resuelve por ti. Ahora debes una política de resolución de conflictos: last-writer-wins sobre un reloj en el que confíes, propiedad por clave para que las dos regiones nunca escriban la misma clave, o merge a nivel de aplicación. Activo-activo es un diseño real y a veces necesario, pero es una decisión de modelado de datos, no una característica de transporte. Recurre a unidireccional primero; adopta bidireccional solo cuando hayas decidido, explícitamente, cómo se reconcilian dos escritores.

La forma a recordar​

Una WAN no es una LAN lenta; es un medio distinto con las particiones como un evento normal en lugar de una emergencia. Un único clúster, correctamente, se niega a fingir lo contrario —sus temporizadores y su protocolo de propiedad están afinados para tratar un silencio largo como un fallo, porque dentro de un datacenter casi siempre lo es. Así que no estiras el clúster. Ejecutas dos de ellos, cada uno soberano en su propia red, y los unes con la única primitiva que está construida para tolerar un enlace que va y viene: un log ordenado por revisión que puedes reproducir, y un cursor que recuerda dónde estabas.

La independencia es la decisión que hace seguro todo lo demás. El change feed es solo la tubería. El log de operaciones ya te dio el cursor.