← All posts

Nuestro descubrimiento de servicios detectó su propio fallo y se desactivó solo

Authagonal·July 1, 2026
authdistributed-systemsclusteringleader-electionazureaks

Teníamos un clúster de tres réplicas que no dejaba de estar en desacuerdo consigo mismo. Los trabajos en segundo plano se ejecutaban dos y tres veces seguidas. La respuesta no estaba en los logs; estaba en nuestro propio código. La rutina de descubrimiento de pares tenía un bloque catch, y el comentario dentro decía, más o menos: "multicast falló, descubrimiento desactivado". Nuestro descubrimiento de servicios detectaba su propio fallo y se apagaba en silencio, y había estado haciendo exactamente eso en producción desde el primer día.

Esta es la historia de por qué un protocolo de clustering que es correcto en un servidor en un rack es la herramienta equivocada en cuanto lo pones sobre una plataforma gestionada, y de por qué la solución fue eliminarlo en lugar de repararlo.

Para qué sirve realmente el clúster

Un servidor de autenticación se ejecuta en más de una réplica por disponibilidad. Las réplicas son en su mayoría independientes: cualquiera de ellas puede validar un token o comprobar una contraseña. Pero unos pocos trabajos deben ejecutarse una vez, no una vez por réplica. El barrido de retención de datos que borra los registros caducados. La pasada que dispara los webhooks de clientes. Cualquier cosa que actúe hacia afuera y tenga un efecto secundario. Para eso necesitas dos cosas en las que el clúster tiene que ponerse de acuerdo: quiénes son los miembros y cuál de ellos es el líder que ejecuta el trabajo singleton.

Nuestra respuesta original era la clásica: gossip. Cada nodo charla con sus pares, la pertenencia es una propiedad emergente de quién es alcanzable, y el grupo elige un líder del conjunto acordado. Es un diseño hermoso. También asume que los nodos pueden encontrarse entre sí, y la forma en que el código antiguo los encontraba era multicast: gritar en el segmento de red local y ver quién responde.

Por qué se rompió, en silencio

El Kubernetes gestionado no transporta multicast. Tampoco lo hace casi ninguna red en la nube que no construyas tú mismo. El grito salía y nada volvía, todas las veces. Así que el descubrimiento "fallaba", el bloque catch se disparaba, y cada nodo concluía que estaba solo en el mundo.

Con una réplica es inofensivo. Con tres es split-brain por construcción: tres nodos, cada uno seguro de ser el único miembro, cada uno eligiéndose a sí mismo como líder, cada uno ejecutando el trabajo singleton. El barrido de retención se ejecutaba en los tres. La pasada de webhooks disparaba el webhook de cada cliente tres veces. Nada de eso lanzaba un error, porque desde el punto de vista de cada nodo solitario todo iba bien. El bug no era una caída; eran tres programas comportándose cada uno de forma perfectamente correcta en un mundo que tenían equivocado.

La solución fue dejar de descubrir pares por completo

Aquí está el replanteamiento que hizo que todo se colapsara a unas pocas líneas: estábamos intentando reconstruir, con un protocolo parlanchín, un hecho que la plataforma ya almacena para nosotros con consistencia fuerte. Corremos sobre una nube que nos entrega un almacén fuertemente consistente. La elección de líder no necesita consenso entre pares si ya hay un único lugar en el que todos pueden ponerse de acuerdo.

Así que el liderazgo pasó a ser un blob lease. Un blob, un lease. Quien tiene el lease es el líder. El lease tiene un timeout, así que un líder que muere deja de renovarlo y el lease se libera para el siguiente que lo tome. No hay descubrimiento de pares, ni cálculo de quórum, ni multicast, ni bloque catch esperando para desactivarlo. La coordinación y los eventos viajan por una pequeña tabla que actúa como un bus entre los nodos.

La propiedad que no esperaba adorar: puedes abrir Storage Explorer y ver la mente del clúster. Quién tiene el lease ahora mismo. Qué hay en cola en el bus. La pertenencia dejó de ser algo que infieres del comportamiento de un protocolo para convertirse en una fila que puedes leer. Cuando lo que decide quién ejecuta tus trabajos destructivos es un valor que puedes mirar, depurar deja de ser arqueología.

La parte en la que eliminamos algo ingenioso a propósito

Ya que estábamos, eliminamos un limitador de tasa distribuido construido sobre un conflict-free replicated data type, un CRDT que fusionaba los contadores por nodo en un recuento global sin coordinación. Era genuinamente elegante. También resolvía un problema que habíamos movido. El límite de tasa global pertenece al borde, donde llegan las peticiones, no reconstruido dentro del clúster con una estructura de datos que necesita un párrafo para explicarse. Fuera.

Eliminar código que funciona se siente como una pérdida hasta que cuentas lo que dejas de mantener. Quitamos la capa de gossip, la suposición del multicast, el bloque catch que se desactivaba solo y el CRDT, y lo reemplazamos todo con un lease y una tabla. El número de líneas bajó, y el número de estados en los que el sistema puede estar bajó con él.

La lección, factorizada

La pertenencia por gossip y multicast son las herramientas correctas en bare metal o en una red plana que controlas. Son las herramientas equivocadas en una plataforma cuya red no transporta el mismísimo mecanismo del que dependen, y el modo de fallo no es una caída ruidosa; es un repliegue silencioso que parece estar funcionando. Antes de portar un patrón de sistemas distribuidos a infraestructura gestionada, pregúntate qué asume sobre la red, y pregúntate qué te da ya la plataforma gratis. La nuestra ya estaba corriendo un almacén fuertemente consistente. Un lease en ese almacén era una elección de líder más simple y más correcta que el protocolo que habíamos estado arrastrando, y es una que podemos observar.

Si operas autenticación, las partes que custodian tus claves y deciden quién ejecuta tus trabajos destructivos deberían ser las cosas más aburridas y más inspeccionables que posees. Hicimos las nuestras aburridas. Fue una mejora.