O problema não é o bug. É o estado do sistema.

O problema não é o bug. É o estado do sistema.

O problema não é o bug. É o estado do sistema.

Japa Tela Preta #4

Teve uma situação que, olhando hoje, explica melhor do que qualquer teoria por que sistemas não quebram do jeito que a gente imagina.

O sistema estava rodando bem. Código revisado, testes passando, deploy tranquilo. Nada fora do padrão. Até que começaram a aparecer alguns casos estranhos: pedidos marcados como pagos, mas que nunca tinham sido faturados. Não era um volume absurdo, mas era o suficiente pra gerar desconforto. E o pior, acontecia de forma intermitente, sem um padrão claro.

A primeira reação foi a mais óbvia: tem um bug aí.

Alguém deve ter deixado passar alguma condição, algum fluxo não tratado, alguma validação mal posicionada. Começamos a olhar o código com esse viés. Revisamos o fluxo de pagamento, depois o de faturamento, depois as integrações. Tudo parecia correto. Isoladamente, cada parte fazia exatamente o que deveria fazer.

E é aí que começa a parte que muda o jogo.

O problema não estava em nenhuma dessas peças individualmente. Ele só aparecia quando tudo acontecia junto. Um usuário finalizava o pagamento ao mesmo tempo em que uma integração externa confirmava o status com um pequeno delay. Em paralelo, um worker processava uma fila que também reagia a esse evento. Em alguns cenários, a ordem dessas coisas mudava alguns milissegundos. E isso já era suficiente.

Nada estava errado no código.

Mas o estado final dos dados ficava inconsistente.

Esse tipo de problema é traiçoeiro porque quebra o modelo mental mais comum sobre erro em software. A gente aprende a procurar bugs como se fossem falhas localizadas, quase sempre ligadas a uma linha específica de código. Só que, na prática, sistemas reais falham muito mais por causa da interação entre partes corretas do que por causa de uma parte isoladamente errada.

Quando você tem múltiplos processos acontecendo ao mesmo tempo, usuários interagindo, filas sendo processadas, integrações externas respondendo fora de ordem, retries acontecendo automaticamente, o sistema deixa de ser previsível de forma linear. Ele passa a depender da combinação desses eventos. E nem sempre essa combinação foi pensada quando o código foi escrito.

É assim que surgem estados que ninguém antecipou. Um pedido pago que não dispara faturamento. Um registro criado sem sua dependência. Um processo executado duas vezes porque recebeu o mesmo evento em momentos diferentes. E o mais perigoso, tudo isso acontecendo sem nenhum erro explícito sendo lançado.

Com o tempo, você percebe que o trabalho muda de natureza. Já não é mais só sobre escrever código que funciona em condições ideais. É sobre garantir que, mesmo sob concorrência, latência, retries e eventos fora de ordem, o sistema continue consistente.

E é por isso que conceitos que antes pareciam exagero começam a fazer sentido de forma muito prática. Transações deixam de ser detalhe e viram proteção real de consistência. Idempotência vira defesa contra duplicidade. Controle de concorrência evita disputa silenciosa de dados. Eventos precisam ser bem definidos. Reprocessamento precisa ser seguro.

Não é paranoia de engenheiro.

É adaptação à realidade.

Porque, em algum momento, todo sistema que cresce deixa de ser apenas um conjunto de funções bem escritas e passa a ser um organismo cheio de estados possíveis, reagindo a estímulos que nem sempre chegam na ordem esperada.

E quando você entende isso, você para de caçar bug como se fosse o único problema.

E começa a projetar comportamento.

É aí que a engenharia muda de nível.

E, sendo bem direto, é aí que o jogo começa a ficar sério.

Picture of Willian Sanada
Willian Sanada