Quando uma Zona Cai, Não Deixe seu Cluster se Rebalancear até a Morte
Uma zona de disponibilidade cai e o cluster sobrevive à indisponibilidade — e então se machuca tentando se curar. Estas são minhas anotações da leitura do artigo da Uber sobre OpenSearch resiliente a zonas confrontado com a documentação de allocation awareness: por que o reflexo de re-replicação é o verdadeiro perigo, e como declarar todos os domínios de falha antecipadamente faz um cluster degradar para um amarelo estável em vez de uma tempestade de rebalanceamento.
Uma zona de disponibilidade apaga e leva um terço do cluster com ela. Meu instinto, na primeira vez que pensei nesse cenário até o fim, foi que o perigo é a capacidade perdida. Não é. Um cluster bem posicionado continua servindo leituras e escritas atravessando a perda de uma zona. O perigo é o que o datastore faz em seguida: ele nota milhares de cópias de shard faltando e começa a reconstruir cada uma delas nos nós que sobraram — os mesmos nós que agora carregam o tráfego sobrevivente.
O artigo do InfoQ de julho de 2026 sobre como a Uber mantém clusters OpenSearch servindo durante quedas de zona cristalizou isso para mim, porque a correção é o oposto do que a cultura de monitoramento nos treina a querer. A Uber configura seus clusters para recusar a cura. A perda de uma zona deixa shards não alocados, o cluster fica amarelo, e permanece amarelo — deliberadamente — até a zona voltar. O que encontrei ao cavar a documentação de allocation awareness que embasa essa decisão é um padrão que generaliza muito além do OpenSearch: declare todos os domínios de falha antecipadamente, para que o cluster consiga distinguir entre um nó que morreu e um domínio que está ausente.
A cura que te mata
Para ver por que o comportamento padrão é perigoso, acompanhe o que Elasticsearch e OpenSearch fazem quando um nó deixa o cluster. A sequência tem três passos: o manager promove réplicas para substituir os primários perdidos, aloca novas réplicas nos nós restantes para restaurar a contagem de cópias configurada, e rebalanceia os shards uniformemente pelo que sobrou. O passo um é barato — uma mudança de metadados. Os passos dois e três são movimentação de dados em massa.
Para um único nó morto, essa movimentação é a decisão certa. O padrão index.unassigned.node_left.delayed_timeout até espera um minuto antes de começar, precisamente porque a documentação reconhece que copiar shards inteiros pela rede para substituir um nó que reinicia e volta dois minutos depois é trabalho desperdiçado — a documentação chama isso de embaralhamento de shards que o cluster nunca precisou.
Agora escale a falha de um nó para uma zona. Em um cluster de três zonas, uma queda de zona remove um terço dos nós de uma vez. Todo shard que tinha uma cópia ali está agora sub-replicado, e a lógica de alocação não vê razão para não corrigir isso. Então o cluster tenta recriar um terço do seu conjunto total de dados — lendo das cópias sobreviventes, transmitindo pela rede, escrevendo em discos dos sobreviventes. Os nós fazendo esse trabalho extra são os mesmos que estão absorvendo a carga de consulta e indexação da zona que caiu. O artigo de engenharia da Uber nomeia o custo diretamente: movimentação de dados em larga escala entre os nós sobreviventes consome I/O de disco, CPU e banda de rede, e pode empurrar as zonas remanescentes para instabilidade, picos de latência ou falha em cascata.
Há uma armadilha de segunda ordem escondida no uso de disco. A re-replicação concentra dados em menos máquinas, e as marcas d'água de disco que governam a alocação ficam em 85% (parar de alocar novos shards), 90% (começar a realocar shards para fora) e 95% (estágio de inundação — escritas de índice são bloqueadas). Um cluster que estava confortavelmente a 60% de disco por nó antes da queda pode caminhar sozinho até o estágio de inundação durante a cura. Nesse ponto a falha escalou de "uma zona offline" para "cluster recusa escritas", e nenhuma falha de zona foi necessária para o segundo passo — a recuperação o causou. A AWS documentou o mesmo formato de falha no OpenSearch Service gerenciado: shards redistribuídos se acumulam desigualmente nos nós sobreviventes, shards desiguais significam tráfego desigual, e um domínio sem folga de armazenamento acaba com escritas bloqueadas no meio da recuperação.
A conclusão incômoda à qual eu sempre volto: depois da perda de uma zona, o reflexo de cura do cluster é um ataque de negação de serviço que ele lança contra si mesmo.
O que o "amarelo estável" realmente preserva
A alternativa é manter o estado degradado. Se cada índice mantém pelo menos três cópias — uma por zona — então perder uma zona deixa cada shard com duas cópias vivas. Leituras funcionam. Escritas funcionam. Replicação por quórum ainda tem maioria de cópias. Nada na queda, por si só, ameaça a disponibilidade.
A saúde do cluster fica amarela, e é aqui que o enquadramento importa. Amarelo é uma afirmação sobre alocação de réplicas, não um veredito sobre se o cluster funciona. Em uma queda de zona, uma réplica não alocada não é uma falha a ser corrigida — é uma descrição precisa da realidade. A zona se foi; as réplicas que viviam ali não têm onde estar validamente. Um cluster que reporta amarelo e serve tráfego com latência normal está em um estado melhor do que um que reporta verde porque enfiou três cópias de tudo em duas zonas enquanto empurra seus discos para o estágio de inundação.
O insight que tirei do design da Uber é que "amarelo" precisa se tornar um estado estável, não transitório. A configuração padrão trata amarelo como uma fila de reparos pendentes. A configuração resiliente a zonas trata amarelo como a resposta projetada para uma pergunta específica: o que o cluster deve fazer quando um domínio de falha inteiro está ausente? Resposta: nada. Esperar.
Zonas desiguais quebram a awareness silenciosamente
Chegar a essa resposta exige dois mecanismos, e o primeiro é o pré-requisito de aparência banal que a topologia física quebra silenciosamente.
Allocation awareness é o recurso padrão: marque cada nó com um atributo (zone, rack_id), liste esse atributo em cluster.routing.allocation.awareness.attributes, e o alocador distribui cópias de cada shard entre os valores do atributo, de modo que a falha de um único domínio não possa eliminar todas as cópias de nada.
O detalhe é aritmético. A awareness só consegue distribuir cópias uniformemente se os domínios tiverem espaço para elas de forma uniforme. Zonas físicas raramente cooperam — gerações de hardware diferem, capacidade chega em uma zona antes de outra, um lote de nós é substituído. A Uber descobriu que, com contagens desiguais de nós por zona, a lógica de awareness falha em encontrar posicionamentos válidos para cada shard, e o cluster fica em um amarelo ruim: shards não alocados não porque uma zona morreu, mas porque a topologia nunca teve um lar legal para eles, mais desbalanceamento de disco e nós quentes do lado sobrecarregado.
A correção deles é uma camada de indireção que chamam de grupos de isolamento: domínios de falha lógicos com contagem de nós garantidamente igual, cada um mapeado para uma zona física, com uma composição que sobrevive à substituição de hardware. A Uber roda três grupos para a maioria dos serviços, de modo que uma zona física mapeia para exatamente um grupo e a falha de uma zona remove no máximo um terço da capacidade — e o padrão é o default para todo cluster OpenSearch e Elasticsearch de Tier 3 ou superior que eles operam. A abstração importa menos do que o invariante que ela impõe — capacidade igual por domínio de falha declarado. Qualquer plataforma pode manter o mesmo invariante com labels de nó e disciplina de provisionamento. Sem ele, tudo a jusante da awareness degrada de garantia para melhor esforço. A Uber roda o mesmo padrão para o Apache Pinot, mapeando IDs de grupos de isolamento em pools de grupos de réplica para que as réplicas de segmento sobrevivam à perda completa de uma zona — o que me diz que o invariante é portável, não uma peculiaridade do OpenSearch.
Forced awareness é o cluster se recusando a curar
A awareness simples tem um padrão que surpreende as pessoas: ela governa onde as cópias vão, não se elas vão. A referência do Elasticsearch é explícita: se uma localização falha, o alocador distribui as cópias perdidas entre as localizações restantes. A awareness sozinha não previne a tempestade de rebalanceamento — ela alegremente reconstrói um terço do conjunto de dados dentro dos dois terços sobreviventes, mantendo as cópias de cada shard separadas onde consegue.
A forced awareness fecha exatamente essa lacuna. Você declara o conjunto completo de valores de domínio antecipadamente:
cluster.routing.allocation.awareness.attributes: zone
cluster.routing.allocation.awareness.force.zone.values: zone-a,zone-b,zone-cA declaração muda o que uma zona ausente significa. Sem ela, o alocador só sabe das zonas que consegue ver no momento — quando a zone-c desaparece, o mundo simplesmente tem duas zonas, e duas zonas podem abrigar três cópias. Com a lista completa declarada, o alocador sabe que um domínio está ausente em vez de inexistente. Cópias que pertencem à zone-c permanecem não alocadas até que os nós da zone-c voltem. A documentação declara o trade claramente: o cluster vai "preferir deixar algumas réplicas não alocadas" em vez de sobrecarregar as localizações restantes.
Esse estado não alocado tem exatamente duas saídas, e vale conhecer as duas antes de depender dele. Os nós da zona reingressam e as cópias são reconstruídas em seu lar declarado. Ou um operador reescreve a lista forçada, removendo o valor morto, e o cluster retoma a reconstrução que estava segurando. Não há temporizador, nem relaxamento gradual, nem limiar no qual o cluster muda de ideia por conta própria. A configuração é a política inteira.
Isso merece uma imagem: o mesmo cluster de três zonas, a mesma perda de zona, com e sem a declaração — um lado derretendo numa tempestade de setas de cópia, o outro lado mantendo duas cópias por shard e uma fileira de espaços deliberadamente vazios.
Os dois mecanismos se compõem em um único comportamento. Domínios de capacidade igual fazem a operação normal ser verde — cada shard encontra um lar legal, sem desbalanceamento de disco. A forced awareness faz a falha de zona ser um amarelo estável — sem tempestade, sem espiral de marcas d'água, e com uma recuperação que começa apenas quando a zona realmente volta, momento no qual as cópias faltantes são reconstruídas em seu lar declarado em vez de se embaralharem duas vezes.
Uma consequência que vale nomear: a forced awareness também restringe o scale-down. Reduza a contagem de nós de uma zona abaixo das outras e as cópias fixadas àquele domínio não terão para onde ir. A declaração é um contrato, e o lado do provisionamento tem que continuar honrando-o. Esse é o preço operacional de fazer de "domínio ausente" um estado de primeira classe.
Os managers têm que sobreviver à mesma falha
Shards de dados são só metade da história. O quórum de cluster managers vive nas mesmas zonas, e um design que mantém os dados servindo mas perde a capacidade de eleger um manager falhou de todo modo.
A Uber roda cinco nós elegíveis a manager pelos três domínios — uma divisão 2-2-1 — em vez dos três de manual. Com cluster.auto_shrink_voting_configuration habilitado (o padrão é true), a configuração de votação acompanha o conjunto de nós vivos. Perder a zona com dois managers deixa três votantes, que reformam um quórum em dois de três; a configuração então encolhe, de modo que o cluster tolera mais uma perda de manager. Zona-mais-um, sequencialmente. Três managers em três zonas também sobrevivem à perda da zona, mas a próxima falha individual depois disso encerra as eleições.
Não vou rederivar o dimensionamento estático de quórum aqui — a aritmética 2f + 1 é um tópico próprio. O ponto específico à falha de zona é dinâmico: o auto-shrink compra tolerância para falhas sequenciais, não simultâneas, e a documentação de referência é cuidadosa em dizer que a configuração de votação só descarta nós que partiram enquanto ainda mantém pelo menos três. Cinco managers não é "mais é melhor"; é a menor contagem em que um domínio inteiro mais um atrasado podem morrer em sequência e o cluster ainda realizar eleições.
Quando a auto-cura é a resposta certa
O contra-argumento honesto a tudo isso: amarelo é uma janela de redundância reduzida, e a janela fica aberta pela duração da queda. Com três cópias e uma zona fora, cada shard está a uma falha de nó de ter uma única cópia sobrevivente, e a forced awareness não vai reconstruir essa cópia perdida nas zonas sobreviventes. O design troca uma sobrecarga certa e autoinfligida por uma segunda falha incerta e de baixa probabilidade. O artigo da Uber enquadra isso como trocar replicação total imediata por estabilidade, e acho que esse trade é certo para clusters grandes — mas é um trade, e ele te obriga a monitorar o estado amarelo e a ter um ponto de decisão para uma queda longa (redeclarar a lista forçada para duas zonas e aceitar a reconstrução, no seu cronograma, na vazão que você escolher).
A escolha também depende do que falhou e de qual folga existe, e é por isso que eu não aplicaria forced awareness reflexivamente:
- Oscilações transitórias de um único nó são o que a alocação atrasada já resolve. Aumentar o
index.unassigned.node_left.delayed_timeouta partir do padrão de um minuto é o botão barato, e não exige disciplina de topologia. - Clusters pequenos com folga real — aqueles em que os sobreviventes conseguem absorver uma re-replicação completa abaixo da marca d'água baixa de 85% e o tráfego de recuperação cabe na rede — perdem pouco ao deixar a cura padrão rodar. A tempestade é um fenômeno de escala; com três nós, é um chuvisco.
- Qualquer coisa sem domínios de capacidade igual não pode usar forced awareness com segurança. Corrija a aritmética primeiro ou a configuração produz o amarelo ruim — shards permanentemente não alocados em um cluster saudável.
- Plataformas gerenciadas estão convergindo para isso de qualquer forma. O AWS OpenSearch Service aplica zone awareness e alocação de shards ciente de carga durante falhas zonais por conta própria: o alocador pondera capacidade provisionada, capacidade real e o total de cópias de shard contra a média esperada de shards por nó, e se recusa a entregar a um nó sobrevivente mais do que sua parcela dos shards da zona morta. Leio isso como o sinal mais forte disponível de que os fornecedores que lidam com o maior número de quedas de zona consideram o "recusar-se a rebalancear" o padrão correto em escala.
O que fica comigo deste estudo é que o padrão inverte um hábito. Passei anos tratando a auto-cura do cluster como o recurso e a intervenção manual como o modo de falha. Na escala de zona isso se inverte: a configuração madura é a que codifica, antecipadamente, quais falhas o cluster não deve tentar corrigir.
Pontos principais
- A reação padrão à perda de uma zona é reconstruir um terço do conjunto de dados nos dois terços sobreviventes — tráfego de recuperação e marcas d'água de disco podem transformar em cascata uma queda de zona em um cluster com escritas bloqueadas.
- Mantenha ao menos três cópias de cada shard, uma por domínio de falha; a perda de uma zona então custa redundância, não disponibilidade.
- Declare todos os domínios de falha antecipadamente (forced awareness) para que o cluster distinga "domínio ausente" de "domínio nunca existiu" e mantenha um amarelo estável em vez de se curar.
- A matemática da awareness só funciona sobre domínios de capacidade igual; imponha contagens iguais de nós por zona (os grupos de isolamento da Uber) antes de qualquer outra coisa.
- Rode cinco nós manager em uma divisão 2-2-1 com auto-shrink de votação para sobreviver a uma zona mais uma falha sequencial.
- Trate o amarelo estável como uma obrigação: alerte sobre ele, limite-o no tempo e decida antecipadamente quando e como você força a reconstrução durante uma queda prolongada.
Recorra a isso quando o cluster for grande o bastante para que re-replicar os dados de uma zona saturaria os discos ou a rede dos sobreviventes, quando os domínios tiverem (ou puderem receber) capacidade igual, e quando você puder sustentar o runbook que um amarelo deliberado exige. Evite para clusters pequenos com folga ampla, para topologias com zonas desiguais, e como substituto do ajuste de alocação atrasada em rotatividade normal de nós — a tempestade contra a qual você está se defendendo precisa ser real antes de valer a pena ensinar seu cluster a ficar parado.
Curtindo? Talvez goste disso aqui.
Nada parecido — quer tentar outro ângulo?
Posts Relacionados
As Três Fases de Movimentação de Dados Online, Lidas Como Três Invariantes
Todo diagrama de migração online que vi se parece com o mesmo — três setas entre duas caixas — e pula a parte que importa: para que cada fase realmente serve. Estas são minhas anotações depois de reler os escritos da Stripe sobre DocDB junto com a documentação de cutover do gh-ost e do Vitess, destilados em três invariantes que posso plotar em gráficos e usar como portões de decisão.
Descarte Primeiro as Requisições Certas: Load Shedding Baseado em Prioridade sob Sobrecarga
Limites estáticos de RPS descartam o tráfego errado. Concorrência é o que satura um serviço, não a taxa de requisições. A partir das minhas anotações após ler o artigo do InfoQ sobre proteção contra sobrecarga, o post de janeiro da Uber sobre o Cinnamon e a palestra da Netflix no QCon SF sobre load shedding priorizado em nível de serviço, eis por que latência é o sinal de controle correto — e como uma pequena taxonomia de prioridades aliada a um limite adaptativo de concorrência mantém o tráfego mais barato sendo descartado primeiro.
Arquitetura Baseada em Células Não É de Graça: O Que Slack, DoorDash e Roblox Realmente Pagaram Por Ela
Arquitetura baseada em células contém o raio de impacto, mas não é gratuita. Um olhar sobre o que Slack, DoorDash e Roblox realmente pagaram por células em produção — e um checklist para os padrões mais baratos de isolamento de falhas que a maioria dos times deveria considerar primeiro.