Vários sistemas que precisam de funcionar em conjunto, mesmo quando um deles falha.
Concebemos e operamos plataformas de vários serviços, com barramento de mensagens, novas tentativas limitadas, chaves de idempotência, circuit breakers e publicação com reversão automática. Cada padrão abaixo corre num sistema próprio, não num diagrama.
Já construídoOperăm patru platforme proprii cu arhitecturi diferite și le putem deschide pe toate. Cifrele sunt măsurate azi cu comenzi, nu preluate din documentație: aichat are 28 de directoare de serviciu, 235 de modele de date pe patru scheme și o magistrală de mesaje cu 297 de cozi și 17.525 de legături; Kallina are 472 de funcții edge, 559 de migrări și 28 de sarcini programate în bază; MEGA CRM are un backend de 9.516 linii cu 99 de rute și 179 de politici de acces pe rând; Taskin rulează 21 de sarcini programate fără niciun Docker. Rezerva pe care o spunem noi: în două locuri documentația proprie a rămas în urma codului — o afirmație despre numărul de linii era depășită cu 16%, iar o diagramă de infrastructură descria un server pe care nu mai rulăm nimic. Am folosit măsurătoarea, nu documentul.
«Arquitetura complexa» não significa muitas caixas num desenho. Significa que você sabe o que acontece quando uma delas não responde. Um sistema com três serviços que se chamam de forma síncrona, sem limite de novas tentativas e sem idempotência, é mais frágil do que um monólito — porque cada nova ligação é uma nova forma de falha, e as formas de falha multiplicam-se mais depressa do que as funcionalidades.
O nosso trabalho parte da separação de responsabilidades e termina no que se vê quando algo cede. Numa das plataformas próprias, as mensagens não passam diretamente entre serviços, mas por um barramento com 297 filas, 9 exchanges e 17.525 ligações, das quais 278 filas são geradas por cliente, não escritas à mão. Oito filas têm exchange de dead letter e oito têm tempo de vida da mensagem de cinco minutos — ou seja, uma mensagem que não pode ser processada chega a um sítio onde pode ser vista, não desaparece.
Os padrões que implementamos são poucos e repetem-se: novas tentativas com limite escrito em código, chaves de idempotência que tornam inofensiva a segunda entrega do mesmo evento, um interruptor que pára os pedidos a um serviço que falha repetidamente, um gate de consumo antes das ações que custam dinheiro, e alertas com limiar de repetição para que uma avaria intermitente não enterre o resto.
A última parte, a que se observa: a publicação. Uma das plataformas é publicada por sincronização de ficheiros, sem Docker, com a verificação de que a página servida contém exatamente o identificador do pacote acabado de publicar — não apenas que o servidor responde 200 — e com reversão automática para a versão anterior se a verificação falhar. A regra surgiu depois de um incidente real em que uma verificação superficial anulou uma publicação boa.
Qué incluye
El trabajo, por componentes
Separamos os serviços pelo que pode estragar-se de forma independente
Numa plataforma própria existem 28 diretórios de serviço, dos quais 16 têm ponto de entrada próprio, e em produção correm cinco processos sob um gestor de processos. Cada canal de comunicação tem o seu próprio serviço e a sua própria porta, precisamente para que uma falha num deles não pare o resto. Os dados são modelados em quatro esquemas separados, com 235 modelos no total — a separação não é apenas ao nível do processo, é também ao nível do esquema.
Colocamos um barramento de mensagens, não chamadas síncronas em cadeia
297 filas, 9 exchanges, 17.525 ligações. A nomeação das filas é por cliente e, em alguns casos, por tópico de conversa — ou seja, a topologia é gerada, não escrita à mão. Existe também uma exchange com entrega atrasada, para coisas que precisam de acontecer mais tarde, não agora. Oito filas têm exchange de dead letter configurada e oito têm tempo de vida da mensagem de cinco minutos.
Novas tentativas com limite, não até ao infinito
Três limites diferentes para três situações diferentes, todos escritos em código: cinco novas tentativas para mensagens agendadas, com o contador persistido, não mantido em memória; três novas tentativas ao nível da fila, contadas num cabeçalho da mensagem; e três novas tentativas com aumento exponencial da pausa para o envio de e-mail, em dois caminhos de fornecedor. Uma nova tentativa sem limite não é resiliência, é um ciclo.
Chaves de idempotência, para que a segunda entrega não estrague nada
Para eventos de terceiros usamos uma inserção que falha se o evento já tiver sido visto: a violação de unicidade da base de dados é tratada como «duplicado», não como erro, juntamente com uma verificação de frescura do momento. Para pagamentos, a chave é uma marca na transação de crédito, por isso o reenvio do mesmo webhook não credita duas vezes. Para mensagens, a deduplicação é feita por identificador, com tempo de vida e limpeza.
Um interruptor de circuito na integração frágil
A integração que depende da sessão de um site de terceiros tem um interruptor real, não apenas uma nota na documentação: após dez falhas consecutivas, os pedidos param durante dois minutos. Sem ele, um site que não responde transforma um canal em falha numa plataforma lenta, porque toda a gente espera no pedido.
Gate de consumo antes das ações que custam
Numa das plataformas, qualquer ação paga passa por uma função de verificação em quatro etapas, cada uma com o seu próprio motivo devolvido em claro: conta suspensa, limite mensal de conversas atingido, teto diário de despesa ultrapassado, saldo insuficiente. Em caso de exceção, devolve «não permitido», não «permitido» — a gate fecha-se, não se abre, quando algo corre mal. Por trás está um registo de eventos de consumo com custo por evento, discriminado por fornecedor, modelo e modalidade.
Limitação de taxa que sobrevive ao reinício
Janela deslizante mantida na base de dados, com um mapa em memória apenas como atalho dentro de uma invocação. O motivo está escrito logo no início do ficheiro: os processos que servem os pedidos são de curta duração, portanto a sua memória não pode ser a fonte de verdade. Quinze funções usam-na, e a quota de um fornecedor externo tem o seu próprio limitador, separado.
Observabilidade que distingue «respondeu» de «funcionou»
Uma resposta 200 não significa que a tarefa tenha tido sucesso. A embalagem com que executamos as tarefas agendadas percorre recursivamente a resposta à procura de listas de falhas ou erros e trata «200 com falhas» como um resultado distinto. Os alertas têm limiar de repetição — dez minutos num sistema, duas horas noutro — e a marca é apagada no primeiro sucesso, para que uma avaria recorrente possa voltar a alertar de imediato.
Publicação com verificação e reversão automática
A publicação verifica não só que o endereço responde 200, mas que a página servida contém exatamente o identificador do pacote acabado de ser publicado; caso contrário, repõe a versão anterior. Para o serviço de fundo, a publicação compara a árvore por somas de controlo e salta o reinício se nada tiver mudado — um reinício inútil mata um loop em execução.
Qué aspecto tiene
El recorrido, paso a paso.
01
Desenhamos o mapa dos modos de falha, não das caixas
Para cada ligação entre dois sistemas: o que acontece se o outro estiver lento, se estiver em baixo, se responder duas vezes, se responder de forma errada. Entregamos: a lista das ligações com o comportamento esperado em cada uma das quatro situações, mais a decisão síncrona/assíncrona para cada uma.
02
Colocamos o barramento e os contratos de mensagem
Filas, exchanges, cartas mortas, tempo de vida por mensagem, e a chave de idempotência para cada tipo de evento. Entregamos: a topologia, os contratos de mensagem e o comportamento documentado na reentrega.
03
Adicionamos as gates: taxa, consumo, interruptor
A limitação de taxa numa janela deslizante na base, a gate de consumo antes das ações que têm custo e o interruptor nas integrações fora do nosso controlo. Entregamos: os limiares definidos, os motivos devolvidos em claro e os testes para cada etapa de recusa.
04
Construímos a publicação e a reversão
Publicação com verificação de conteúdo, não apenas de código de resposta, versão anterior mantida ao lado e reversão automática. Entregamos: o procedimento de publicação, o procedimento de reversão exercitado pelo menos uma vez, e as sondas de saúde — uma superficial para «vive», uma profunda para «está mesmo a funcionar».
05
Entregamos com a documentação verificada face ao código
O documento é verificado face à medição antes da entrega, porque a documentação que fica para trás é mais perigosa do que a sua ausência. Entregamos: o diagrama, os procedimentos e a lista explícita dos pontos onde o documento e o código foram encontrados em desacordo, com o que corrigimos.
Sistemele nu se apelează în lanț: între ele stă o magistrală cu cozi per client, schimburi separate pe tip de trafic și scrisori moarte pentru ce nu se poate procesa. În jurul ei, porțile — limitare de rată, poartă de consum, întrerupător de circuit — și sondele de sănătate, una superficială și una adâncă.
Os dados
Qué tocamos, dónde están y cuánto se quedan
Las preguntas que hace cualquiera que tenga un delegado de protección de datos — hechas aquí antes de que las haga él.
Onde estão os dados, por plataforma
Não existe uma resposta única e isso é bom: uma plataforma usa MySQL no seu próprio servidor, duas usam PostgreSQL através de Supabase — uma alojada, uma instalada na nossa infraestrutura —, e uma guarda o conteúdo como ficheiros estáticos, sem base de dados nenhuma. A escolha é feita com base nos requisitos, não no hábito.
O acesso aplica-se na base, não só na aplicação
Num dos nossos sistemas internos, a segurança ao nível da linha está ativa em 43 tabelas, através de 90 instruções, com 179 políticas escritas. A regra que seguimos: se uma chamada contorna a aplicação e atinge a base diretamente, ainda assim não deve ver as linhas de outra pessoa.
Registo de consumo
Uma linha por evento com tipo, fornecedor, quantidades separadas por modalidade — segundos de chamada, tokens de entrada, tokens de saída, caracteres —, mais custo em moeda com seis casas decimais e os créditos subtraídos. Daqui saem o teto diário de despesa, o custo por chamada e a projeção de consumo.
As migrações são o histórico, não a documentação
559 migrações numa plataforma, 80 noutra. O esquema muda através de migrações versionadas, por isso o estado da base pode ser reconstruído e lido cronologicamente. Quando o documento e a migração não concordam, a migração tem razão.
As tarefas agendadas ficam na base ou no cron, não no processo
28 tarefas agendadas correm no interior da base de dados numa plataforma; noutra, 21 tarefas correm através do cron do sistema. O motivo está escrito no ficheiro: os cronómetros dentro de um serviço reiniciam a cada reinício, por isso um ciclo de três horas num serviço que é republicado nunca se ativa.
Un caso
Uma topologia de mensagens que se gera sozinha para cada cliente
A situação
Uma plataforma com vários clientes, cada um com os seus próprios canais de comunicação, os seus próprios tópicos de conversa e as suas próprias notificações. A variante ingénua — uma fila comum e um filtro pelo identificador do cliente — faz com que um cliente com grande volume bloqueie os restantes, e um erro num tópico pare a fila para todos.
Qué construimos
A topologia é gerada por cliente, não escrita à mão: de 297 filas, 278 são do tipo «notificações para uma determinada conta», e algumas descem até ao nível de um único tópico de conversa. Por cima delas, nove exchanges separam os tipos de tráfego — notificações, tokens, webhooks, canais — e existe um exchange com entrega atrasada para o que tem de acontecer mais tarde. Oito filas têm exchange de mensagens mortas, oito têm tempo de vida por mensagem de cinco minutos. As regras de orquestração podem ser recarregadas a quente, através de um canal de publicação, sem reiniciar os consumidores.
Qué salió
Um cliente com grande volume não atrasa os outros clientes, e uma mensagem que não pode ser processada vai para um lugar onde pode ser vista e relançada, em vez de desaparecer ou bloquear a fila. O número de ligações no barramento — 17.525 — mostra exatamente porque a topologia tem de ser gerada: ninguém mantém isto manualmente.
Qué no dice el caso
O preço é operacional: um barramento desta dimensão precisa da sua própria monitorização e de um plano para as filas abandonadas, caso contrário cresce indefinidamente. E a geração por cliente pressupõe que a eliminação de um cliente elimina também a sua topologia — se essa etapa faltar, acumulam-se filas mortas.
Perguntas
Lo que nos pregunta la gente antes de llamar
Como sei que não me estão a vender complexidade de que não preciso?
Porque a primeira recomendação que damos muitas vezes é não separar. Cada novo serviço é um novo modo de falha, e os modos de falha multiplicam-se mais depressa do que as funcionalidades. Um exemplo dos nossos próprios sistemas: o backend de um deles é um único ficheiro de 9.516 linhas com 99 rotas. Não é elegante e dizemo-lo, mas é publicado numa só etapa e depurado num só lugar. A separação faz-se quando existe um motivo mensurável — um serviço que tem de escalar separadamente, uma equipa separada, um ritmo de entrega separado.
O que acontece quando um sistema na cadeia não responde?
Depende do que decidimos em conjunto na fase de mapeamento, e é essa a ideia. Nos nossos sistemas: a mensagem entra na fila e é reenviada um número limitado de vezes, depois chega à troca de cartas mortas, onde pode ser vista; a integração instável tem um interruptor que, após dez falhas consecutivas, pára durante dois minutos em vez de manter os pedidos em espera; e as ações que custam dinheiro são bloqueadas por uma gate que, em exceção, recusa, não permite.
Como evitam que o mesmo evento seja processado duas vezes?
Com uma chave de idempotência, não com uma verificação “já vi isto?” que tem race condition. Concretamente: inserimos uma linha com o identificador do evento e, se a base de dados recusar por violação de unicidade, tratamos esse código de erro como sinal de duplicado, não como falha. Para pagamentos, o marcador fica na transação de crédito, por isso o reenvio do mesmo webhook não credita duas vezes.
Usam Docker ou não?
Ambos, e a escolha é justificada sempre. Um sistema corre em contentor, mas sem volumes montados, o que significa que a publicação é uma cópia para o contentor mais reinício — um `docker rm` ali perde o estado, e isso está escrito no documento de operação exatamente assim. Outra plataforma não tem qualquer Docker: a publicação é sincronização de ficheiros, e os scripts de publicação são instalados manualmente com privilégios de administrador e expostos ao fluxo automático por exatamente dois caminhos permitidos. O motivo está escrito no código: um script que o fluxo pode reescrever é um script pelo qual o fluxo pode escalar.
Como sabe que uma tarefa agendada foi mesmo executada?
Por experiência negativa. Um relatório diário nosso esteve morto durante onze dias, entre 7 e 17 de agosto, enquanto a tarefa agendada era acionada todos os dias — o comando usado saía em silêncio em caso de erro e não escrevia nada. Desde então, cada tarefa corre num invólucro que percorre a resposta por listas de falhas, trata “200 com falhas” como resultado distinto, tem tempo máximo de execução e escreve uma linha estruturada por execução. Os alertas têm limiar de repetição, e o marcador é removido na primeira reússita.
Quanto importa a abstração do fornecedor?
Muito, se o fornecedor estiver numa área que se move depressa. Para voz temos três pontes separadas — uma para cada fornecedor de fala em tempo real — que implementam a mesma interface. A ponte faz a conversão de áudio entre a central telefónica e o fornecedor, numa porta dedicada, com o servidor de saúde ligado apenas à interface local, não exposta. A mudança de fornecedor torna-se uma decisão, não uma reescrita.
A vossa documentação está atualizada?
Não em todo o lado, e preferimos dizer onde. Na preparação desta página medimos dois sistemas e encontrámos o documento próprio em atraso: uma afirmação sobre o tamanho de um ficheiro estava cerca de 16% abaixo da realidade, e um diagrama de infraestrutura descrevia um servidor de onde já tínhamos mudado. A regra que aplicamos e que pedimos nos projetos: quando o documento e a medição não concordam, a medição tem razão, e o documento é corrigido no mesmo passo.
O que não fazem?
Não prometemos objetivos de disponibilidade sem medição — uma percentagem do tipo “99,9%” exige dados de terreno ao longo de um período, e onde não os temos não os afirmamos. Não projetamos sistemas que não possamos operar ou entregar: se o resultado é uma arquitetura que a vossa equipa não consegue manter, é uma arquitetura errada. E não fazemos certificações de conformidade — podemos construir os controlos, não podemos emitir o certificado.
Em que se baseiam as afirmações acima (23 fontes)
23 delas são código e ficheiros dos nossos repositórios. Não publicamos o nome nem a linha: juntos, numa única página, descreveriam com demasiada precisão como estão construídos sistemas que não são só nossos. Percorremo-los consigo, no repositório, a pedido — a verificação continua possível, só que se faz numa conversa.