← All posts

Nossa descoberta de serviços detectou a própria falha e se desligou sozinha

Authagonal·July 1, 2026
authdistributed-systemsclusteringleader-electionazureaks

Tínhamos um cluster de três réplicas que vivia em desacordo consigo mesmo. Tarefas em segundo plano rodavam duas e três vezes seguidas. A resposta não estava nos logs; estava no nosso próprio código. A rotina de descoberta de pares tinha um bloco catch, e o comentário dentro dele dizia, mais ou menos: "multicast falhou, descoberta desativada". Nossa descoberta de serviços estava detectando a própria falha e se desligando silenciosamente, e fazia exatamente isso em produção desde o primeiro dia.

Esta é a história de por que um protocolo de clustering que é correto num servidor montado em rack é a ferramenta errada no momento em que você o coloca numa plataforma gerenciada, e de por que a solução foi apagá-lo em vez de consertá-lo.

Para que serve, de fato, o cluster

Um servidor de autenticação roda em mais de uma réplica para garantir disponibilidade. As réplicas são em grande parte independentes: qualquer uma delas pode validar um token ou conferir uma senha. Mas algumas tarefas precisam rodar uma vez, não uma vez por réplica. A varredura de retenção de dados que apaga registros expirados. A passagem que dispara os webhooks dos clientes. Qualquer coisa que alcance o mundo externo e tenha um efeito colateral. Para isso você precisa de duas coisas com que o cluster precisa concordar: quem são os membros, e qual deles é o líder que executa o trabalho singleton.

Nossa resposta original era a clássica: gossip. Cada nó conversa com seus pares, a associação é uma propriedade emergente de quem está alcançável, e o grupo elege um líder a partir do conjunto acordado. É um design lindo. Ele também presume que os nós conseguem se encontrar, e a maneira como o código antigo os encontrava era multicast: gritar no segmento de rede local e ver quem responde.

Por que quebrou, silenciosamente

Kubernetes gerenciado não transporta multicast. Quase nenhuma rede de nuvem que você não constrói por conta própria transporta. O grito saía e nada voltava, toda vez. Então a descoberta "falhava", o bloco catch disparava, e cada nó concluía que estava sozinho no mundo.

Com uma réplica isso é inofensivo. Com três é split-brain por construção: três nós, cada um certo de ser o único membro, cada um se elegendo líder, cada um executando o trabalho singleton. A varredura de retenção rodava nos três. A passagem de webhooks disparava o webhook de cada cliente três vezes. Nada disso lançava um erro, porque do ponto de vista de cada nó solitário estava tudo bem. O bug não era uma queda; eram três programas se comportando cada um de forma perfeitamente correta num mundo que entendiam errado.

A solução foi parar de descobrir pares por completo

Eis a reformulação que fez tudo desabar para algumas poucas linhas: estávamos tentando reconstruir, com um protocolo tagarela, um fato que a plataforma já armazena para nós com consistência forte. Rodamos numa nuvem que nos entrega um armazenamento fortemente consistente. A eleição de líder não precisa de consenso entre pares se já existe um único lugar com que todos podem concordar.

Então a liderança virou um blob lease. Um blob, um lease. Quem detém o lease é o líder. O lease tem um timeout, então um líder que morre para de renová-lo e o lease se libera para o próximo a tomá-lo. Não há descoberta de pares, nem cálculo de quórum, nem multicast, nem bloco catch à espera para desativá-lo. A coordenação e os eventos trafegam por uma pequena tabela que funciona como um barramento entre os nós.

A propriedade que eu não esperava amar: você pode abrir o Storage Explorer e ver a mente do cluster. Quem detém o lease neste exato momento. O que está na fila do barramento. A associação deixou de ser algo que você infere a partir do comportamento de um protocolo e passou a ser uma linha que você pode ler. Quando aquilo que decide quem executa suas tarefas destrutivas é um valor que você pode olhar, depurar deixa de ser arqueologia.

A parte em que apagamos algo engenhoso de propósito

Já que estávamos ali, apagamos um limitador de taxa distribuído construído sobre um conflict-free replicated data type, um CRDT que mesclava os contadores de cada nó em uma contagem global sem coordenação. Era genuinamente elegante. Também resolvia um problema que tínhamos movido. O limite de taxa global pertence à borda, onde as requisições chegam, e não reconstruído dentro do cluster com uma estrutura de dados que precisa de um parágrafo para se explicar. Fora.

Apagar código que funciona parece uma perda até você contabilizar o que deixa de manter. Removemos a camada de gossip, a suposição do multicast, o bloco catch que se desativava sozinho, e o CRDT, e substituímos tudo por um lease e uma tabela. A contagem de linhas caiu, e o número de estados em que o sistema pode estar caiu junto.

A lição, fatorada

Associação por gossip e multicast são as ferramentas certas em bare metal ou numa rede plana que você controla. São as ferramentas erradas numa plataforma cuja rede não transporta justamente o mecanismo do qual elas dependem, e o modo de falha não é uma queda barulhenta; é um recuo silencioso que parece estar funcionando. Antes de portar um padrão de sistemas distribuídos para uma infraestrutura gerenciada, pergunte o que ele presume sobre a rede, e pergunte o que a plataforma já te dá de graça. A nossa já rodava um armazenamento fortemente consistente. Um lease nesse armazenamento era uma eleição de líder mais simples e mais correta do que o protocolo que vínhamos carregando, e é uma que conseguimos observar.

Se você opera autenticação, as partes que guardam suas chaves e decidem quem executa suas tarefas destrutivas deveriam ser as coisas mais entediantes e mais inspecionáveis que você possui. Tornamos as nossas entediantes. Foi um upgrade.