Quórum Multi-Região: "Todos os Regiões Concordam" vs. "N de M"
Um mergulho profundo nas diferentes abordagens de quórum para monitoramento de tempo de atividade, comparando "todos devem concordar" com "N de M", com exemplos de código e análise de casos de uso. O artigo explora as vantagens e desvantagens de cada método, focando na precisão e na prevenção de falsos positivos.
MundiX News·19 de maio de 2026·10 min de leitura·👁 7 views
Quórum Multi-Região: "Todos os Regiões Concordam" vs. "N de M"
Olá, Habr!
No meu monitoramento de tempo de atividade Valpero, atualmente tenho sete monitores de produção e dez regiões de teste. Quando comecei, os alertas de falsos positivos eram frequentes – uma história típica com verificações de região única. Implementei a lógica de quórum. Descobriu-se que existem várias opções de quórum, e elas fornecem comportamentos diferentes em casos limite.
Abaixo, discutirei as duas principais abordagens – K-of-N (como em Pingdom, BetterStack) e all-must-agree (como eu uso) – com código real que estou usando atualmente na produção.
No final, analisarei os casos limite que quebram cada uma das abordagens e por que optei por all-must-agree com um threshold de falha consecutiva.
Duas Abordagens para Quórum
K-of-N:
Um incidente é aberto quando K de N regiões relatam inatividade. Por exemplo, 3 de 10. Isso permite tolerar até K-1 falhas simultâneas como "ruído".
All-must-agree:
Um incidente é aberto somente quando todas as regiões veem inatividade. Se pelo menos uma região vir atividade, o status se torna parcial (parte do mundo vê o problema, parte não), mas nenhum alerta é enviado.
À primeira vista, K-of-N parece mais forte – é mais sensível, detecta interrupções parciais mais rapidamente. Na prática, essa abordagem tem uma nuance: o que considerar "N"? Se você tiver um site em uma CDN com PoPs em diferentes regiões, e uma PoP cair, 3-4 de seus nós de teste, que estão mais próximos dessa PoP, verão o site inativo. Os outros 6-7 – ativos (eles atingiram outra PoP). O alerta K=3 funcionará, embora o site realmente esteja funcionando para a maior parte do mundo.
All-must-agree fornece menos falsos positivos, mas detecta interrupções parciais reais mais tarde. Este é um compromisso em favor da precisão em vez da recuperação.
O Que Tenho no Código
Mostrarei como isso funciona atualmente no Valpero. O código é simplificado, mas a lógica é a mesma.
Armazenamento – Redis (status por região + contador):
python
from redis import Redis
from app.config import get_settings
_TTL_SECONDS =86400# 24hdef_redis()-> Redis:return Redis.from_url(get_settings().REDIS_URL, decode_responses=True)def_key(site_id:int, region:str)->str:returnf"probe:region_status:{site_id}:{region}"def_fail_key(site_id:int, region:str)->str:returnf"probe:region_fails:{site_id}:{region}"
Quando o resultado da verificação de teste chega, atualizamos o estado por região:
python
defupdate_region_status( site_id:int, region:str, is_up:bool, threshold:int=1, has_open_incident:bool=False,)->tuple[str,int]: r = _redis() status_key = _key(site_id, region) fkey = _fail_key(site_id, region)if is_up:# UP é sempre confirmado instantaneamente, o contador de falhas é redefinido r.delete(fkey) r.set(status_key,"up", ex=_TTL_SECONDS)return"up",0# DOWN – requeremos threshold de falhas consecutivas fails =int(r.incr(fkey)or1) r.expire(fkey,3600)if has_open_incident or fails >=max(threshold,1): r.set(status_key,"down", ex=_TTL_SECONDS)return"down", fails
# Down mas não confirmado – deixamos o estado anterior prev = r.get(status_key) kept = prev if prev in("up","down")else"up" r.set(status_key, kept, ex=_TTL_SECONDS)return kept, fails
Lógica chave:
Uma única falha não muda a região para inativa. Requer threshold consecutivas (o padrão é 1, mas na produção eu uso 2). Isso suprime o ruído de problemas de rede transitórios.
Veredicto de Quórum – simples:
python
defget_quorum_verdict( site_id:int, all_regions:list[str], current_region:str, current_is_up:bool,)->tuple[str,list[str]]:"""Retorna um de: 'up', 'partial', 'down' + uma lista de regiões inativas."""ifnot all_regions:return("up"if current_is_up else"down",[]) statuses:dict[str,str]={} r = _redis() pipe = r.pipeline()for region in all_regions: pipe.get(_key(site_id, region)) results = pipe.execute()for region, val inzip(all_regions, results): statuses[region]= val if val in("up","down")else"up"# O resultado atual sempre vence (Redis pode ainda não ter atualizado) statuses[current_region]="up"if current_is_up else"down" down_regions =[r for r, s in statuses.items()if s =="down"]ifnot down_regions:return("up",[])iflen(down_regions)==len(all_regions):return("down", down_regions)return("partial", down_regions)
O alerta só vai para inativo.
Parcial é gravado no banco de dados para análise posterior, mas não acorda ninguém.
Por que o Threshold de Falha Consecutiva?
A coisa mais comum que eu pegava sem um threshold era um único tempo limite TCP em um RTT limite. O teste em Tóquio faz uma solicitação HTTP para um site na Alemanha. A viagem de ida e volta é de 240 ms, mas um pacote foi perdido no caminho – o handshake é atrasado, o tempo limite de 10 segundos é acionado, o teste escreve "falha". A próxima verificação em 30 segundos – tudo OK.
Se threshold = 1, um único tempo limite é suficiente para mudar a região para inativa. Se threshold = 2, são necessários dois seguidos – o que na prática acontece com uma ordem de magnitude menos frequência (porque problemas de rede transitórios geralmente duram <30 segundos).
Valpero atualmente usa threshold = 2 na produção. Em sete monitores na semana passada – zero incidentes de falsos positivos, enquanto duas quedas reais de curta duração (de um dos sites de teste por alguns minutos) foram capturadas corretamente.
Casos Limite
Falha transitória de um único teste – descrito acima. Threshold = 2 pega.
Correlação de AS – quando vários testes na mesma autonomous system caem simultaneamente devido a um upstream comum. Eu tenho 6 testes na mesma AS (escrevi sobre isso em um artigo anterior). All-must-agree os protege: mesmo que 6 nós na mesma AS vejam o site inativo, os 4 restantes em outras AS vejam ativo – o veredicto significativo será parcial, nenhum alerta será enviado. Este é o comportamento correto, porque a causa real não é o site, mas minha rede.
Falha de borda da CDN – o site de destino está no Cloudflare, uma PoP em Tóquio caiu. O teste em Tóquio vê o site inativo, os outros 9 testes atingem outras PoPs e veem o site ativo. All-must-agree relatará parcial. Isso é correto: os usuários em Tóquio estão tendo problemas, mas não é "o site inteiro está fora do ar", e os operadores não precisam de um alerta à noite.
Interrupção regional da internet – por exemplo, AWS US-East-1 cai. Um teste em Nova Jersey vê o site inativo (se estiver na AWS), os outros em outros provedores. All-must-agree: parcial, nenhum alerta será enviado.
Este já é um compromisso – os usuários reais em US-East-1 não podem acessar o site. Aqui, K-of-N seria mais sensível.
Um K-of-N simples nos mesmos dados daria um falso positivo nos casos de correlação de AS e borda da CDN. Ele tem menos precisão, mas mais recall. Para meus 7 monitores, a precisão é mais importante.
Quando Mudar para K-of-N
Eu mudaria se:
Houver clientes com um SLA com uma promessa de tempo de atividade para regiões específicas (por exemplo, "99,9% de disponibilidade da UE"). Então você precisa pegar interrupções parciais na UE como um incidente real.
A rede crescerá para mais de 30 regiões com AS garantidamente diferentes. Então K=5 de 30 já não é ruído – é uma indicação séria de que algo realmente não está certo.
Enquanto isso, 7 monitores e 10 regiões com 3 AS diferentes – all-must-agree traz mais benefícios.
O Que Eu Aprendi
Quórum não é um algoritmo, mas uma família. K-of-N é melhor para grandes redes com AS confiável. All-must-agree é melhor para pequenas redes onde falsos positivos são mais caros do que uma captura tardia de uma interrupção real. O threshold de falha consecutiva (pelo menos 2) é obrigatório em ambos os casos – uma única falha em um resultado de falha é quase sempre transitória.
Se você estiver construindo seu próprio monitoramento – comece com all-must-agree + threshold=2. Este é o sinal mais preciso e o menor número de falsos alarmes. Quando a sensibilidade é necessária – mude para K-of-N com K=floor(N/2).
Links
Site: valpero.com – meu monitoramento de tempo de atividade com o algoritmo de quórum descrito acima
Sem cartão para começar · Planos a partir de R$49/mês
Quórum Multi-Região: "Todos os Regiões Concordam" vs. "N de M"
Olá, Habr!
No meu monitoramento de tempo de atividade Valpero, atualmente tenho sete monitores de produção e dez regiões de teste. Quando comecei, os alertas de falsos positivos eram frequentes – uma história típica com verificações de região única. Implementei a lógica de quórum. Descobriu-se que existem várias opções de quórum, e elas fornecem comportamentos diferentes em casos limite.
Abaixo, discutirei as duas principais abordagens – K-of-N (como em Pingdom, BetterStack) e all-must-agree (como eu uso) – com código real que estou usando atualmente na produção.
No final, analisarei os casos limite que quebram cada uma das abordagens e por que optei por all-must-agree com um threshold de falha consecutiva.
Duas Abordagens para Quórum
K-of-N:
Um incidente é aberto quando K de N regiões relatam inatividade. Por exemplo, 3 de 10. Isso permite tolerar até K-1 falhas simultâneas como "ruído".
All-must-agree:
Um incidente é aberto somente quando todas as regiões veem inatividade. Se pelo menos uma região vir atividade, o status se torna parcial (parte do mundo vê o problema, parte não), mas nenhum alerta é enviado.
À primeira vista, K-of-N parece mais forte – é mais sensível, detecta interrupções parciais mais rapidamente. Na prática, essa abordagem tem uma nuance: o que considerar "N"? Se você tiver um site em uma CDN com PoPs em diferentes regiões, e uma PoP cair, 3-4 de seus nós de teste, que estão mais próximos dessa PoP, verão o site inativo. Os outros 6-7 – ativos (eles atingiram outra PoP). O alerta K=3 funcionará, embora o site realmente esteja funcionando para a maior parte do mundo.
All-must-agree fornece menos falsos positivos, mas detecta interrupções parciais reais mais tarde. Este é um compromisso em favor da precisão em vez da recuperação.
O Que Tenho no Código
Mostrarei como isso funciona atualmente no Valpero. O código é simplificado, mas a lógica é a mesma.
Armazenamento – Redis (status por região + contador):
Quando o resultado da verificação de teste chega, atualizamos o estado por região:
def update_region_status(
site_id: int,
region: str,
is_up: bool,
threshold: int = 1,
has_open_incident: bool = False,
) -> tuple[str, int]:
r = _redis()
status_key = _key(site_id, region)
fkey = _fail_key(site_id, region)
if is_up:
# UP é sempre confirmado instantaneamente, o contador de falhas é redefinido
r.delete(fkey)
r.set(status_key, "up", ex=_TTL_SECONDS)
return "up", 0
# DOWN – requeremos threshold de falhas consecutivas
fails = int(r.incr(fkey) or 1)
r.expire(fkey, 3600)
if has_open_incident or fails >= max(threshold, 1):
r.set(status_key, "down", ex=_TTL_SECONDS)
return "down", fails
# Down mas não confirmado – deixamos o estado anterior
prev = r.get(status_key)
kept = prev if prev in ("up", "down") else "up"
r.set(status_key, kept, ex=_TTL_SECONDS)
return kept, fails
Lógica chave:
Uma única falha não muda a região para inativa. Requer threshold consecutivas (o padrão é 1, mas na produção eu uso 2). Isso suprime o ruído de problemas de rede transitórios.
Veredicto de Quórum – simples:
def get_quorum_verdict(
site_id: int,
all_regions: list[str],
current_region: str,
current_is_up: bool,
) -> tuple[str, list[str]]:
"""Retorna um de: 'up', 'partial', 'down' + uma lista de regiões inativas."""
if not all_regions:
return ("up" if current_is_up else "down", [])
statuses: dict[str, str] = {}
r = _redis()
pipe = r.pipeline()
for region in all_regions:
pipe.get(_key(site_id, region))
results = pipe.execute()
for region, val in zip(all_regions, results):
statuses[region] = val if val in ("up", "down") else "up"
# O resultado atual sempre vence (Redis pode ainda não ter atualizado)
statuses[current_region] = "up" if current_is_up else "down"
down_regions = [r for r, s in statuses.items() if s == "down"]
if not down_regions:
return ("up", [])
if len(down_regions) == len(all_regions):
return ("down", down_regions)
return ("partial", down_regions)
O alerta só vai para inativo.
Parcial é gravado no banco de dados para análise posterior, mas não acorda ninguém.
Por que o Threshold de Falha Consecutiva?
A coisa mais comum que eu pegava sem um threshold era um único tempo limite TCP em um RTT limite. O teste em Tóquio faz uma solicitação HTTP para um site na Alemanha. A viagem de ida e volta é de 240 ms, mas um pacote foi perdido no caminho – o handshake é atrasado, o tempo limite de 10 segundos é acionado, o teste escreve "falha". A próxima verificação em 30 segundos – tudo OK.
Se threshold = 1, um único tempo limite é suficiente para mudar a região para inativa. Se threshold = 2, são necessários dois seguidos – o que na prática acontece com uma ordem de magnitude menos frequência (porque problemas de rede transitórios geralmente duram <30 segundos).
Valpero atualmente usa threshold = 2 na produção. Em sete monitores na semana passada – zero incidentes de falsos positivos, enquanto duas quedas reais de curta duração (de um dos sites de teste por alguns minutos) foram capturadas corretamente.
Casos Limite
Falha transitória de um único teste – descrito acima. Threshold = 2 pega.
Correlação de AS – quando vários testes na mesma autonomous system caem simultaneamente devido a um upstream comum. Eu tenho 6 testes na mesma AS (escrevi sobre isso em um artigo anterior). All-must-agree os protege: mesmo que 6 nós na mesma AS vejam o site inativo, os 4 restantes em outras AS vejam ativo – o veredicto significativo será parcial, nenhum alerta será enviado. Este é o comportamento correto, porque a causa real não é o site, mas minha rede.
Falha de borda da CDN – o site de destino está no Cloudflare, uma PoP em Tóquio caiu. O teste em Tóquio vê o site inativo, os outros 9 testes atingem outras PoPs e veem o site ativo. All-must-agree relatará parcial. Isso é correto: os usuários em Tóquio estão tendo problemas, mas não é "o site inteiro está fora do ar", e os operadores não precisam de um alerta à noite.
Interrupção regional da internet – por exemplo, AWS US-East-1 cai. Um teste em Nova Jersey vê o site inativo (se estiver na AWS), os outros em outros provedores. All-must-agree: parcial, nenhum alerta será enviado.
Este já é um compromisso – os usuários reais em US-East-1 não podem acessar o site. Aqui, K-of-N seria mais sensível.
Um K-of-N simples nos mesmos dados daria um falso positivo nos casos de correlação de AS e borda da CDN. Ele tem menos precisão, mas mais recall. Para meus 7 monitores, a precisão é mais importante.
Quando Mudar para K-of-N
Eu mudaria se:
Houver clientes com um SLA com uma promessa de tempo de atividade para regiões específicas (por exemplo, "99,9% de disponibilidade da UE"). Então você precisa pegar interrupções parciais na UE como um incidente real.
A rede crescerá para mais de 30 regiões com AS garantidamente diferentes. Então K=5 de 30 já não é ruído – é uma indicação séria de que algo realmente não está certo.
Enquanto isso, 7 monitores e 10 regiões com 3 AS diferentes – all-must-agree traz mais benefícios.
O Que Eu Aprendi
Quórum não é um algoritmo, mas uma família. K-of-N é melhor para grandes redes com AS confiável. All-must-agree é melhor para pequenas redes onde falsos positivos são mais caros do que uma captura tardia de uma interrupção real. O threshold de falha consecutiva (pelo menos 2) é obrigatório em ambos os casos – uma única falha em um resultado de falha é quase sempre transitória.
Se você estiver construindo seu próprio monitoramento – comece com all-must-agree + threshold=2. Este é o sinal mais preciso e o menor número de falsos alarmes. Quando a sensibilidade é necessária – mude para K-of-N com K=floor(N/2).
Links
Site: valpero.com – meu monitoramento de tempo de atividade com o algoritmo de quórum descrito acima