Projetei, construí e opero sozinho um ecossistema SaaS multi-tenant em produção, com clientes reais pagantes. Esta página mostra os números medidos, como uma publicação acontece, e — a parte que importa — os incidentes que derrubaram o sistema e o que mudou depois de cada um.
Esta tabela existe para você não precisar ler a página inteira. A coluna que mais importa é a última: medir é o que separa operar de supor. E onde eu só tenho formação, e não prática, está dito — quem marca tudo não informa nada.
Não é laboratório. Há clientes pagando, e quando isto cai alguém deixa de vender. Todo o resto desta página existe por causa desta frase.
ARM64 em nuvem pública, 8 vCPU e 30 GB cada, atrás de túnel Cloudflare. Os dois servem tráfego: não existe nó ocioso esperando desastre.
Row-Level Security no PostgreSQL, fail-closed: sem o escopo do cliente na
sessão, a consulta devolve zero linhas. O papel de runtime não tem BYPASSRLS.
Cobertura mínima que falha o build: 95% em dois serviços, 90,3% em outro. Não é meta de parede — é condição para publicar.
Número sem procedência é opinião com cara de dado. Cada linha abaixo diz o que foi contado, como, quando — e o que a medição NÃO prova. A última coluna é a que me interessa: medição honesta declara o próprio limite.
docker ps nos dois nós: 112 cada. Dos 112, 59 são processos de aplicação (web, trabalhador, publicador, consumidor, agendador); os outros 53 são barramento, dados, observabilidade, borda e cofre
não é medida de carga nem de utilização — é contagem de processos“Mostre-me o diagrama e eu desconfio. Mostre-me o que acontece quando ele falha e eu acredito.”
o que esta página tenta responderO desenho completo: por onde o tráfego entra, onde cada peça roda, como se chega ao dado — e o que acontece quando cada parte falha.
Duas vistas que vêm antes do desenho de implantação. A primeira responde “quem usa isto e com que mundo externo ele fala”. A segunda responde “quais aplicações existem e como elas conversam” — sem nenhuma infraestrutura, de propósito: misturar as duas coisas num diagrama só é o erro que torna a maioria deles ilegível.
Os 224 containers não são 224 cópias da mesma coisa. Contados por papel, eles desenham a plataforma — e cada camada existe porque alguma dor pediu.
Nenhum dos dois nós expõe porta pública. O tráfego entra por túnel Cloudflare (conector de saída) e cai no Traefik, que faz roteamento por host, TLS automático e serve de ponto de balanceamento. Drenar um nó do túnel é o que torna a publicação canária possível: é assim que se tira um nó do balanceamento sem derrubar nada.
PostgreSQL em host dedicado, compartilhado pelas verticais, com
HAProxy como ponto único de acesso e pgbouncer
provisionado mas ainda fora do caminho (medido: zero cliente ativo; as
aplicações vão direto ao HAProxy) — entra quando o número de conexões pedir. São duas instâncias: primário e réplica quente. Isolamento de cliente é
Row-Level Security, não WHERE tenant_id espalhado pelo
código: o papel de runtime não tem BYPASSRLS, então esquecer o escopo
devolve zero linhas em vez de vazar.
Honestidade sobre o alvo: não há failover automático de banco. A promoção é manual e documentada — e eu prefiro dizer isso a desenhar um Patroni que não existe.
Serviços não se chamam em cadeia: publicam fato. O caminho é outbox transacional (o fato commita junto com o dado, ou nenhum dos dois) → relay → Redis Streams → inbox com dedup por grupo de consumo → DLQ para o que falhou. São 14 instâncias de Redis: o barramento é por vertical e por nó, e é justamente por isso que a convergência se resolve no Postgres, não num lock distribuído que não atravessaria os nós.
.envCredenciais vivem em cofre (OpenBao) com token de serviço por consumidor. Há alerta dedicado para token do cofre expirando — porque segredo que vence silenciosamente derruba tudo de uma vez, no pior horário.
A regra é publicar fato, não chamar serviço. O caminho padrão é assíncrono: outbox transacional (o fato só existe se o dado existir), relay, fila, inbox com deduplicação por grupo de consumo e DLQ para o que falhou. Isso significa que um serviço fora do ar atrasa o outro — não o derruba junto.
Quando a resposta é necessária agora — “este cliente tem direito a esta funcionalidade?”, “esta empresa é filha daquela?” — a chamada é síncrona, mas nunca é HTTP solto espalhado pelo código: passa por um cliente tipado do SDK único, com operações nomeadas e autenticação máquina-a-máquina por chave própria de cada serviço. A autoridade sobre plano, hierarquia e direito de uso tem um dono só; os outros perguntam, não deduzem.
Quando uma venda acontece, o serviço de vendas não avisa ninguém. Ele publica o fato — e doze grupos de consumo independentes leem aquilo no ritmo de cada um. Estoque baixa a peça, financeiro gera o recebível, fiscal prepara o documento, contabilidade lança. Nenhum deles sabe que os outros existem, e um deles fora do ar atrasa a própria fila, não a venda.
Por nó: 13 trabalhadores, 11 publicadores, 10 consumidores e 10 agendadores. Cada um roda com concorrência 1, declarada no arquivo de composição. O padrão da ferramenta é 8 — e foi assim que a frota caiu uma vez. Limite que não está escrito não existe.
Trabalho de IA, de análise e de rotina têm filas próprias. Uma chamada de modelo que demora trinta segundos não pode sentar na frente da fila que confirma uma venda. Separar é o que impede que o lento atrase o urgente.
O barramento grava em disco. Se um nó cai com trabalho enfileirado, aquele trabalho não evapora — ele espera o nó voltar e retoma. A diferença entre “o cliente esperou” e “o pedido sumiu” é esta linha de configuração.
Prometheus raspando 9 alvos (os dois nós, cAdvisor, exportador do PostgreSQL, Traefik, sondas HTTP), Grafana, Alertmanager, cAdvisor, node e postgres exporter, blackbox sondando rotas de fora para dentro. 12 regras de alerta no ar.
O que eu considero a parte madura não é a quantidade — é o que elas vigiam. Três delas não perguntam “algo quebrou?”, perguntam “alguma coisa deixou de acontecer?”: backup off-site vazio, backup atrasado, backup sem o banco dentro. Um cofre de backup que existe mas está vazio passa por saudável em qualquer verificação de presença — e só se descobre no dia do restore. As outras cobrem queda do PostgreSQL, disco enchendo em cada nó, nó fora do ar, slot de WAL acumulando e container em ciclo de reinício.
“A melhor ferramenta é o planejamento.”
por que cada peça está onde estáToda escolha aqui tem um ganho, um custo que eu pago e uma consequência comercial. Quem só lista o ganho está vendendo, não projetando.
O orçamento de computação inteiro são dois nós de 8 vCPU e 30 GB, e eles já rodam 224 containers. Um plano de controle de Kubernetes — servidor de API, etcd, agendador, controladores, rede sobreposta, controlador de entrada — consome CPU e memória do mesmo orçamento, de forma sustentada. E carga sustentada de CPU é exatamente o que já me custou duas suspensões de infraestrutura.
Como decisão de negócio: Kubernetes resolve um problema de escala que eu ainda não tenho, cobrando hoje em complexidade operacional e em consumo do recurso mais escasso que eu tenho. Enquanto a carga couber em dois nós, adotá-lo seria pagar adiantado por um seguro contra um risco que não existe — e criar um risco novo e real: uma superfície que eu não consigo sustentar sozinho em plantão. Isso não é rejeitar a ferramenta; é não trocar de ferramenta por moda. No dia em que a conta virar — mais de um operador, ou carga que não cabe em dois nós — a migração está desenhada, e é barata justamente porque tudo já é imagem de container e configuração declarativa.
Como decisão de negócio: um balanceador gerenciado custa por hora e por volume, e ainda me devolve um IP público para defender. O túnel custa zero no plano em uso, elimina a superfície exposta e entrega filtro de aplicação junto. Troquei um risco que eu teria de operar (exposição) por um risco que eu monitoro (disponibilidade de terceiro) — e este segundo tem sonda externa e alerta.
Como decisão de negócio: failover automático sem camada de consenso e isolamento do nó defeituoso troca uma parada rara e recuperável por uma corrupção silenciosa e permanente. Para quem guarda dado financeiro de terceiros, essa troca é ruim em qualquer cenário. A mitigação não é automatizar a promoção: é a réplica estar quente e em dia, o procedimento estar escrito, e a recuperação a ponto no tempo existir — tudo medido.
Como decisão de negócio: reserva ociosa é custo que só paga no pior dia — e que costuma falhar justamente lá, porque ninguém a exercita. Com os dois ativos, o mesmo dinheiro compra capacidade e resiliência, e a prova de que funciona é o tráfego de ontem. Honestidade sobre o limite: hoje os dois estão no mesmo domínio de disponibilidade, então a falha correlacionada de domínio ainda me derruba. Está marcado no diagrama porque é o próximo passo, não porque eu não enxergo.
Como decisão de negócio: o valor do portão não está em onde ele executa, está em o que ele reprova. Um executor hospedado rodando a suíte contra um banco que não tem isolamento de cliente dá a sensação de cobertura e deixa passar a classe de defeito que mais machuca num SaaS multi-tenant: vazamento entre clientes. Compensação para o custo que eu assumi: um teste que vigia o próprio portão e falha alto.
As doze verticais não têm endereço público próprio. Todas são montadas por caminho sob uma origem só, e o servidor de identidade — OAuth2 e publicação de chaves — vive nessa mesma origem. A autorização não fica só num portão na frente: ela viaja com o serviço, por um SDK único que todas as verticais carregam e que nega a chamada quando o escopo não está presente.
Como decisão de negócio: o produto é multi-tenant e vende para empresas que precisam herdar hierarquia de cliente, plano contratado e segregação de funções. Identidade terceirizada cobra por usuário ativo — o custo cresce junto com a base, no exato momento em que a margem deveria melhorar — e empurra a regra de negócio para fora do sistema que é dono dela. Mantendo em casa, o custo é de engenharia (uma vez) em vez de assinatura (sempre), e a regra de quem-vê-o-quê fica onde ela é verdade. A mitigação do custo é operacional: há alerta dedicado para chave e token expirando, e o portão de publicação valida que o emissor de chaves responde antes de devolver o nó ao tráfego.
Java com Spring Boot na identidade, na contabilidade e no motor de fluxo. Go nos serviços novos — binário único, sem runtime, sobe rápido e ocupa pouca memória, que é o recurso escasso aqui. Rust em dois lugares precisos: o agente que observa o núcleo do sistema operacional e o motor de cálculo fiscal. Python com Django nas verticais de negócio, onde velocidade de mudança vale mais que microssegundos.
Como decisão de negócio: reescrever o que funciona é a forma mais cara de não entregar nada. O legado produtivo segue rendendo em Python enquanto o que nasce já nasce em Go, e o contrato estável entre eles é o que permite trocar peça por peça sem parar a operação — sem o risco da reescrita total, que historicamente derruba empresa.
Adquirente de cartão, bureau de crédito, APIs de governo, open banking, assinatura eletrônica, marketplace, mensageria — tudo entra e sai por um serviço só. Ele traduz cada fornecedor para uma capacidade normalizada e devolve o resultado ao ecossistema como fato no barramento. Nenhuma vertical de negócio conhece o nome do fornecedor; a regra de negócio fica em quem consome, não no adaptador.
Como decisão de negócio: integração com terceiro é onde mora o risco contratual — credencial vazada, cobrança indevida, dado de cliente saindo para fora. Concentrar significa que auditar “quem fala com o mundo lá fora, e com qual credencial?” é olhar um serviço, não doze. E isso é verificável, não promessa: medi as seis verticais de negócio e nenhuma tem SDK de terceiro instalado — eles só existem no serviço de integração. Regra que depende de disciplina humana falha calada; esta dá para medir.
Operar sozinho com clientes reais impõe uma restrição que empresa grande não tem: cada serviço gerenciado que eu contrato é dinheiro todo mês e uma peça que eu não controlo. Por isso o desenho evita as três fontes clássicas de conta que cresce sem aviso: tráfego de saída cobrado, balanceador por hora e identidade por usuário ativo.
Como decisão de negócio: num SaaS, o custo de infraestrutura é o que separa margem de prejuízo quando a base cresce. A pergunta que eu faço antes de adotar qualquer serviço é “este custo cresce com o número de clientes?” — se cresce junto com a receita de forma linear, ele come o ganho de escala que o modelo deveria dar. Dimensionamento, limites de CPU e concorrência declarados e o livro-caixa de publicação existem tanto por confiabilidade quanto por custo: recurso que ninguém mede é recurso que ninguém corta.
Plano de recuperação que nunca foi cronometrado é intenção, não plano. Estes três números foram medidos de verdade, não estimados.
“Nada é publicado à mão — e nada compila no servidor de produção.”
uma regra que nasceu de duas suspensõesPublicar é a hora em que um sistema estável fica perigoso. O caminho abaixo existe para que mudar deixe de ser um ato de coragem.
Nada é publicado à mão e nada compila no servidor de produção. A restrição nasceu de dor real: a infraestrutura anterior foi suspensa duas vezes por carga sustentada de CPU — compilar no host era um dos gatilhos. A arquitetura mudou por causa disso.
A imagem é construída na estação/CI e publicada no registry com a tag do
commit. O servidor de produção só executa pull — ele nunca compila.
O conector do túnel é parado no nó A. 100% do tráfego vai para o nó B. A publicação acontece num nó que nenhum usuário está acessando.
Sem crashloop, emissor de chaves respondendo, e zero migração pendente. Esta última nasceu de um incidente: processo de pé não significa banco em dia.
Volta para o digest anterior no próprio nó e aborta — o nó estável nunca foi
tocado. E o conector do túnel é restaurado com verificação: se falhar, grita em
stderr com o comando do conserto manual.
O nó validado retorna ao túnel e só então o segundo nó recebe a mesma imagem, um serviço por vez. Uma trava global serializa a frota inteira: dois deploys simultâneos já derrubaram a infraestrutura uma vez.
Cada serviço recriado entra num livro-caixa no nó com data, digest da imagem e o commit. É assim que se responde “o que exatamente está rodando?” sem adivinhar.
A esteira é própria: não há runner hospedado checando o pull request. O GitHub guarda código e serve de registry das imagens; o portão de qualidade roda antes, na estação. Isso tem uma consequência desconfortável que eu prefiro dizer em voz alta: não existe rede de segurança remota — se eu não rodar o portão, ninguém roda.
Formatação e lint com catraca (o piso sobe, nunca desce), verificação de integridade do projeto e a suíte rodando em PostgreSQL com RLS armado — não em banco de brinquedo. Cobertura abaixo do piso reprova. No serviço em Java, o mesmo portão soma testes de arquitetura, integração com containers reais e análise estática.
Trabalho em ramo, publicação por pull request, e a linha principal é protegida.
push --force, reset --hard e apagar a principal são operações
bloqueadas por política. Antes de refatoração grande, uma marca de retorno.
O alvo do portão chamava o interpretador do sistema e morria logo depois de o lint imprimir “tudo certo”. Quem lesse a tela via verde num portão que não havia executado um único teste. Hoje um teste vigia o próprio portão — e ele falha alto.
“Sistema que nunca quebrou é sistema que ninguém usa.”
três incidentes reais, e a cicatriz de cada umA parte que normalmente não se publica. Causa, o que falhou na detecção, o que eu piorei tentando consertar, e a guarda que impede a reincidência.
Sistema que nunca quebrou é sistema que ninguém usa. O que diferencia não é a ausência de incidente — é o que sobra depois dele. Estes três são reais, do mesmo dia, e cada um deixou uma guarda no código.
A guarda nasceu com o mesmo vício que foi caçar: contava só a pendência, e
grep -c em saída vazia imprime zero — igual a “nenhuma pendente”. Hoje ela conta
o total também: lista vazia não é schema em dia, é falta de evidência.
except largo que envolvia o cliente inteiro: toda divergência era
detectada e descartada. Transação fantasma no adquirente, ou pagamento sem
confirmação — dinheiro — virava uma linha de log.Ausência de medição não é ausência do fato. E alerta que grita sempre cega o guardião — teria sido pior que o defeito original.
Ao medir uma integração, não pare em “o evento é publicado”. Pergunte quem escuta — e se o que ele carrega significa alguma coisa do outro lado.
Todo incidente acima virou um teste que reprova o build se a classe do defeito voltar. Convenção escrita em documento envelhece; guarda executável, não. E toda guarda é testada com um mutante: se eu reintroduzo o defeito e ela continua verde, ela não serve.
“Este depósito conta como saldo vendável?” estava escrita, literal, em cinco lugares. Com um segundo tipo de depósito, cada cópia esquecida vira um buraco silencioso. A regra passou a ter dono único — e um teste reprova quem a escrever à mão.
Varredura por AST: todo tipo de evento publicado no código tem de estar declarado. Achou dois casos vivos — um deles engolia divergência de cartão.
Guarda estrutural, não comportamental: em teste os dois papéis de banco colapsam, então o defeito só existe em produção. Teste de comportamento ficaria verde com o sistema no chão.
Dois links absolutos apontando para a máquina de quem os criou foram parar dentro da imagem de produção — e eram autorreferentes: recursão infinita para qualquer varredura que os siga.
SRE, aqui, não é um título: é um conjunto de regras que já me custaram caro para aprender e hoje estão escritas em script, em teste e em política. Seis delas:
O relato do primeiro incidente desta página tem um campo chamado “Eu piorei”: minha tentativa de conserto manual empilhou três sessões travadas antes de eu entender o problema. Post-mortem que omite o erro do autor não serve para ninguém aprender — serve para proteger o autor.
Diante de produção no chão, o caminho é voltar ao digest anterior e só então investigar. O rollback é automático e por digest — não por “reconstruir a imagem de antes”, que não é a mesma coisa e já me enganou.
Um alerta disparado por total acumulado fica aceso para sempre e passa a ser ignorado — então a morte nova chega sem ninguém ver. Alerta tem de medir a mudança, e silenciar quando a mudança para.
A infraestrutura anterior foi suspensa duas vezes por carga sustentada de CPU, e compilar no servidor era um dos gatilhos; a terceira suspensão seria definitiva. Hoje “produção nunca compila” não é preferência de estilo — é regra com revisão que bloqueia o pull request.
Dois deploys simultâneos já derrubaram a frota. Existe uma trava global: um serviço por vez, um nó por vez, e a trava órfã é reclamada em vez de travar a frota para sempre.
Cada recriação de serviço entra num livro-caixa no nó: data, digest e commit. A pergunta “o que exatamente está rodando?” tem resposta por consulta, não por memória de quem publicou. Foi daí que saiu a média medida de 87 recriações de serviço por dia (mediana 79, variando de 2 a 172).
Já relatei “é configuração do operador” sobre um campo que era impossível de configurar — o tipo não cabia o identificador do outro serviço. Zero de doze registros preenchidos na frota inteira não era negligência: era um defeito. Hoje eu meço antes de concluir.
Uma validação que não conseguiu medir não pode reportar “tudo certo”. Essa confusão já apareceu três vezes: no deploy, na conciliação de cartão e numa guarda minha — sempre com cara de verde.
Suíte com milhares de testes verdes convivia com a tela principal quebrada. Teste com dublê não mede a regra que só existe na borda real — e verificação visual pega o que teste nenhum pega.
“O que eu ainda não testei vale tanto quanto o que eu testei — desde que eu diga qual é qual.”
o estado honesto do que faltaO que está feito, o que está parcial e o que ainda não existe. Separar os três é o que torna as duas primeiras colunas confiáveis.
Posso demonstrar uma publicação canária de ponta a ponta — incluindo o rollback automático quando a validação reprova.