Blogue

Alertas que Disparam Uma Vez, Não Uma Vez por Servidor

Um dos nossos alertas operacionais ("créditos baixos" para a app publicada de um construtor) deve chegar-nos no máximo uma vez por dia. Numa tarde chegou cinco vezes. A causa: cada cópia da nossa API lembrava-se, na sua própria memória, dos alertas que já tinha enviado, por isso dois servidores enviavam o mesmo alerta duas vezes, e cada deploy apagava essa memória e permitia que enviassem de novo. O mesmo padrão fazia com que uma tarefa de fundo fizesse o seu trabalho em duplicado. Ambos coordenam agora através de um armazenamento partilhado, e ambos falham do lado seguro se esse armazenamento estiver indisponível.

O sintoma

A Mythex envia alertas à equipa quando algo precisa de atenção: um serviço está em baixo, uma tarefa parou, ou o saldo de créditos de um construtor está a esgotar-se enquanto a sua app está publicada. Cada alerta tem uma janela de deduplicação, um período durante o qual o mesmo alerta só é enviado uma vez. Para o alerta de créditos baixos, essa janela é de 24 horas.

Cinco cópias do mesmo alerta numa tarde significavam que a janela não estava a funcionar. Pior, chegavam em pares, com cerca de 11 minutos de intervalo.

Porque aconteceu

O pacote de alertas guardava a deduplicação em memória: uma tabela de "chave do alerta → quando foi enviado pela última vez", dentro do processo em execução. Isso funciona quando existe um único processo que nunca reinicia. A produção não tem nenhuma das duas coisas:

  • Duas réplicas da API. A produção corre duas cópias da API. Cada uma tinha a sua própria tabela, por isso cada uma enviou o alerta uma vez. Esses são os pares.
  • Deploys. Cada deploy substitui as duas réplicas por processos novos e com tabelas vazias. Depois de um deploy, o alerta saía outra vez, como se nunca tivesse sido enviado.

Os cerca de 11 minutos entre os dois alertas de cada par vinham da tarefa que dispara este alerta. Corre nas duas réplicas e obtém um bloqueio, por isso as réplicas ganham-no à vez, e cada uma disparou o alerta quando chegou a sua vez.

Havia um segundo erro, mais silencioso, no mesmo código. Para evitar que a tabela crescesse para sempre, limpava-se a si própria quando chegava a 500 chaves, apagando todas as entradas com mais de 15 minutos, independentemente da janela de cada uma. Sob carga, uma deduplicação de 24 horas podia durar um quarto de hora.

A correção

Um registo partilhado por alerta

Antes de a API enviar um alerta, reserva agora a chave de deduplicação do alerta no Redis, um armazenamento partilhado em memória com o qual as duas réplicas comunicam e que não reinicia quando a API reinicia. A reserva é uma única operação do tipo "define esta chave apenas se não existir, e expira-a ao fim da janela". A réplica que a reservar primeiro envia o alerta; a outra encontra a chave ocupada e fica em silêncio. Um processo novo depois de um deploy encontra a chave ainda lá.

A chave e a janela vêm da mesma regra que o pacote de alertas usa, agora partilhada entre as duas camadas, para que não possam discordar sobre o que conta como "o mesmo alerta".

Falhar aberto, não fechado

A decisão mais importante foi o que fazer quando não é possível contactar o Redis. Escolhemos enviar o alerta de qualquer forma. Se o Redis estiver em baixo, ou não responder em 1,5 segundos, o alerta sai. Um alerta duplicado é ruído. Um alerta perdido é alguém que não ficou a saber.

Os alertas mais graves, P0, nunca são retidos pela deduplicação.

Cada entrada mantém a sua própria janela

Na deduplicação em memória do próprio pacote de alertas, cada entrada guarda agora quando termina a sua janela, e a limpeza remove apenas as entradas cuja janela já terminou. Uma chave de 24 horas fica 24 horas, independentemente de quantos outros alertas chegarem.

O mesmo erro, noutro sítio

O mesmo padrão tinha duplicado também um pedaço de trabalho de fundo: um job de reconciliação que mantém o nosso registo do estado de cada espaço de trabalho alinhado com o do fornecedor de sandboxes. Cada passagem lista todos os sandboxes da conta (milhares em produção, cerca de cinco segundos) e todos os projetos em execução ou em pausa. Ambas as réplicas o executavam a cada dois minutos.

As suas escritas são idempotentes (executá-las duas vezes dá o mesmo resultado que executá-las uma vez), por isso nada se estragava. Era apenas trabalho a dobrar. Agora, cada janela de dois minutos é reservada no Redis da mesma forma que os nossos medidores de utilização já reservavam as suas, e só a réplica que a reserva executa a passagem. Se o Redis não estiver acessível, a passagem corre mesmo assim: fazer o trabalho duas vezes é inofensivo; não o fazer não é.

Como testámos

Os testes reproduzem a configuração real. Carregam o módulo de alertas duas vezes, como duas "réplicas" separadas, apontam ambas para um armazenamento partilhado, disparam o mesmo alerta em cada uma e depois carregam uma terceira cópia nova para simular o "depois de um deploy", disparando-o outra vez. Um único alerta é enviado. Outros testes verificam que o alerta de outro construtor continua a sair, que um P0 é enviado a partir de ambas as réplicas e que o alerta continua a ser enviado quando o armazenamento está em baixo ou excede o tempo limite. A correção da limpeza tem o seu próprio teste: uma chave de 24 horas sobrevive a 600 outros alertas que chegam 20 minutos depois.

Para o job de reconciliação, duas réplicas a disparar ao mesmo tempo, mais uma terceira execução na mesma janela, produzem uma única listagem.

O que retirámos disto

  • O estado em memória é por processo. Tudo o que tem de ser verdade entre servidores ("enviado uma vez", "executado uma vez") precisa de viver num sítio que todos partilham.
  • Os deploys são reinícios. Um estado que só sobrevive até ao próximo reinício não sobrevive a um dia de trabalho normal.
  • Decide para que lado falhar. Nos alertas, um armazenamento partilhado em baixo deve significar mais alertas, não menos.
  • Procura o padrão, não apenas a instância. O alerta foi o caso ruidoso; o job de reconciliação duplicado era o mesmo erro, a custar trabalho em silêncio.

Nada disto é visível do lado de dentro da Mythex. Para saber mais sobre como mantemos a plataforma estável, vê como chegámos a zero erros 502 nos deploys e o ficheiro de swap que faltava. Se estás a começar a perceber a ideia de correr mais do que uma cópia de um serviço, o nosso guia sobre o que é um servidor explica o básico.

Mythex · Pricing · Crie qualquer coisa com IA · Modelos de sites e apps · Mythex comparado · Glossário de criação de apps com IA · Ferramentas grátis para criar apps com IA · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)