paint-brush
A falha é necessária, então aceite-a: entendendo estratégias à prova de falhas e rápidas em softwarepor@shai.almog
461 leituras
461 leituras

A falha é necessária, então aceite-a: entendendo estratégias à prova de falhas e rápidas em software

por Shai Almog7m2024/05/07
Read on Terminal Reader

Muito longo; Para ler

Saiba como aceitar falhas pode melhorar a qualidade do seu aplicativo, levando à detecção precoce de erros, ao tratamento robusto de erros e a uma melhor estabilidade geral.
featured image - A falha é necessária, então aceite-a: entendendo estratégias à prova de falhas e rápidas em software
Shai Almog HackerNoon profile picture
0-item

Falhas em sistemas de software são inevitáveis. A forma como essas falhas são tratadas pode impactar significativamente o desempenho do sistema, a confiabilidade e os resultados financeiros do negócio. Neste post, quero discutir o lado positivo do fracasso. Por que você deve buscar o fracasso, por que o fracasso é bom e por que evitar o fracasso pode reduzir a confiabilidade do seu aplicativo. Começaremos com a discussão entre fail-fast e fail-safe; isso nos levará à segunda discussão sobre falhas em geral.

Como observação lateral, se você gosta do conteúdo desta e de outras postagens desta série, confira meu Livro de depuração , que cobre este assunto. Se você tem amigos que estão aprendendo a programar, eu apreciaria uma referência ao meuLivro básico de Java. Se você quiser voltar ao Java depois de um tempo, dê uma olhada no meu Livro Java 8 a 21 .

Falha rápida

Os sistemas fail-fast são projetados para parar de funcionar imediatamente ao encontrar uma condição inesperada. Essa falha imediata ajuda a detectar erros antecipadamente, tornando a depuração mais simples.


A abordagem fail-fast garante que os erros sejam detectados imediatamente. Por exemplo, no mundo das linguagens de programação, Java incorpora essa abordagem produzindo um NullPointerException instantaneamente ao encontrar um valor null , parando o sistema e tornando o erro claro. Esta resposta imediata ajuda os desenvolvedores a identificar e resolver problemas rapidamente, evitando que se tornem mais sérios.


Ao detectar e interromper erros antecipadamente, os sistemas fail-fast reduzem o risco de falhas em cascata, onde um erro leva a outros. Isto torna mais fácil conter e resolver problemas antes que eles se espalhem pelo sistema, preservando a estabilidade geral.


É fácil escrever testes unitários e de integração para sistemas com falhas rápidas. Essa vantagem é ainda mais pronunciada quando precisamos entender a falha do teste. Sistemas fail-fast geralmente apontam diretamente para o problema no rastreamento da pilha de erros.


No entanto, os sistemas à prova de falhas acarretam os seus próprios riscos, especialmente em ambientes de produção:


  • Interrupções de produção: se um bug atingir a produção, poderá causar interrupções imediatas e significativas, impactando potencialmente o desempenho do sistema e as operações da empresa.
  • Apetite pelo risco: Sistemas à prova de falhas exigem um nível de tolerância ao risco por parte dos engenheiros e executivos. Eles precisam estar preparados para lidar e resolver falhas rapidamente, muitas vezes equilibrando isso com possíveis impactos nos negócios.

À prova de falhas

Os sistemas à prova de falhas adotam uma abordagem diferente, visando recuperar e continuar mesmo diante de condições inesperadas. Isto os torna particularmente adequados para ambientes incertos ou voláteis.

Os microsserviços são um excelente exemplo de sistemas à prova de falhas, adotando a resiliência por meio de sua arquitetura. Os disjuntores, tanto físicos quanto baseados em software, desconectam funcionalidades com falha para evitar falhas em cascata, ajudando o sistema a continuar operando.


Os sistemas à prova de falhas garantem que os sistemas possam sobreviver até mesmo em ambientes de produção adversos, reduzindo o risco de falhas catastróficas. Isto os torna particularmente adequados para aplicações de missão crítica, como em dispositivos de hardware ou sistemas aeroespaciais, onde a recuperação tranquila de erros é crucial.


No entanto, os sistemas à prova de falhas têm desvantagens:


  • Erros ocultos: ao tentarem se recuperar de erros, os sistemas à prova de falhas podem atrasar a detecção de problemas, dificultando seu rastreamento e potencialmente levando a falhas em cascata mais graves.
  • Desafios de depuração: essa natureza atrasada dos erros pode complicar a depuração, exigindo mais tempo e esforço para encontrar e resolver problemas.

Escolhendo entre Fail-Fast e Fail-Safe

É um desafio determinar qual abordagem é melhor, pois ambas têm seus méritos. Os sistemas fail-fast oferecem depuração imediata, menor risco de falhas em cascata e detecção e resolução de bugs mais rápidas. Isso ajuda a detectar e corrigir problemas antecipadamente, evitando que se espalhem.

Os sistemas à prova de falhas lidam com erros com elegância, tornando-os mais adequados para sistemas de missão crítica e ambientes voláteis, onde falhas catastróficas podem ser devastadoras.

Equilibrando ambos

Para aproveitar os pontos fortes de cada abordagem, uma estratégia equilibrada pode ser eficaz:


  • Fail-Fast para serviços locais: ao invocar serviços locais, como bancos de dados, o fail-fast pode detectar erros antecipadamente, evitando falhas em cascata.
  • À prova de falhas para recursos remotos: Ao depender de recursos remotos, como serviços da Web externos, a proteção contra falhas pode evitar interrupções causadas por falhas externas.

Uma abordagem equilibrada também requer uma implementação clara e consistente em todos os processos de codificação, revisões, ferramentas e testes, garantindo que seja integrada perfeitamente. Fail-fast pode integrar-se bem com orquestração e observabilidade. Efetivamente, isso move o aspecto à prova de falhas para uma camada diferente de OPS, em vez de para a camada de desenvolvedor.

Comportamento consistente da camada

É onde as coisas começam a ficar interessantes. Não se trata de escolher entre à prova de falhas e à prova de falhas. Trata-se de escolher a camada certa para eles. Por exemplo, se um erro for tratado em uma camada profunda usando uma abordagem à prova de falhas, ele não será percebido. Isso pode ser bom, mas se esse erro tiver um impacto adverso (desempenho, dados inúteis, corrupção, segurança, etc.), teremos um problema mais tarde e não teremos a menor ideia.

A solução certa é lidar com todos os erros em uma única camada; em sistemas modernos, a camada superior é a camada OPS e faz mais sentido. Ele pode relatar o erro aos engenheiros mais qualificados para lidar com o erro. Mas também podem fornecer mitigação imediata, como reiniciar um serviço, alocar recursos adicionais ou reverter uma versão.

Novas tentativas não são à prova de falhas

Recentemente, participei de uma palestra onde os palestrantes listaram sua arquitetura de nuvem atualizada. Eles optaram por usar um atalho para microsserviços usando uma estrutura que lhes permite tentar novamente em caso de falha. Infelizmente, o fracasso não se comporta da maneira que gostaríamos. Você não pode eliminá-lo completamente apenas com testes. Tentar novamente não é à prova de falhas. Na verdade, pode significar uma catástrofe.


Eles testaram o sistema e “funciona”, mesmo em produção. Mas vamos supor que ocorra uma situação catastrófica, seu mecanismo de nova tentativa pode operar como um ataque de negação de serviço contra seus próprios servidores. O número de maneiras pelas quais arquiteturas ad-hoc como essa podem falhar é impressionante.


Isto é especialmente importante quando redefinimos as falhas.

Redefinindo o fracasso

Falhas em sistemas de software não são apenas falhas. Um acidente pode ser visto como uma falha simples e imediata, mas há questões mais complexas a serem consideradas. Na verdade, as falhas na era dos contêineres são provavelmente as melhores falhas. Um sistema reinicia perfeitamente, quase sem interrupção.

Corrupção de dados

A corrupção de dados é muito mais grave e insidiosa do que uma falha. Traz consigo consequências a longo prazo. Dados corrompidos podem levar a problemas de segurança e confiabilidade difíceis de corrigir, exigindo retrabalho extensivo e dados potencialmente irrecuperáveis.


A computação em nuvem levou a técnicas de programação defensivas, como disjuntores e novas tentativas, enfatizando testes e registros abrangentes para detectar e lidar com falhas normalmente. De certa forma, esse ambiente nos devolveu em termos de qualidade.


Um sistema à prova de falhas no nível dos dados poderia impedir que isso acontecesse. Resolver um bug vai além de uma simples correção. Requer a compreensão de sua causa raiz e a prevenção de recorrências, estendendo-se a registros abrangentes, testes e melhorias de processo. Isso garante que o bug seja totalmente resolvido, reduzindo as chances de ele ocorrer novamente.

Não corrija o bug

Se for um bug na produção, você provavelmente deverá reverter se não puder reverter a produção instantaneamente. Isso sempre deve ser possível e, se não for, é algo em que você deve trabalhar.


As falhas devem ser totalmente compreendidas antes que uma correção seja realizada. Nas minhas próprias empresas, muitas vezes pulei essa etapa devido à pressão, em uma pequena startup que é perdoável. Nas empresas maiores, precisamos compreender a causa raiz. Uma cultura de debriefing para bugs e problemas de produção é essencial. A correção também deve incluir mitigação de processos que evite que problemas semelhantes cheguem à produção.

Falha na depuração

Sistemas fail-fast são muito mais fáceis de depurar. Eles têm uma arquitetura inerentemente mais simples e é mais fácil identificar um problema em uma área específica. É crucial lançar exceções mesmo para violações menores (por exemplo, validações). Isso evita tipos de bugs em cascata que prevalecem em sistemas soltos.

Isso deve ser reforçado por testes de unidade que verificam os limites que definimos e verificam se as exceções adequadas são lançadas. As novas tentativas devem ser evitadas no código, pois tornam a depuração excepcionalmente difícil e seu lugar adequado é na camada OPS. Para facilitar ainda mais isso, os tempos limite devem ser curtos por padrão.

Evitando falhas em cascata

O fracasso não é algo que possamos evitar, prever ou testar totalmente. A única coisa que podemos fazer é suavizar o golpe quando ocorre uma falha. Freqüentemente, essa "suavização" é alcançada por meio de testes de longa duração destinados a replicar condições extremas, tanto quanto possível, com o objetivo de encontrar os pontos fracos de nossas aplicações. Isso raramente é suficiente. Sistemas robustos muitas vezes precisam revisar esses testes com base em falhas reais de produção.

Um ótimo exemplo de segurança contra falhas seria um cache de respostas REST que nos permite continuar trabalhando mesmo quando um serviço está inativo. Infelizmente, isso pode levar a problemas de nicho complexos, como envenenamento de cache ou uma situação em que um usuário banido ainda tenha acesso devido ao cache.

Híbrido em Produção

A proteção contra falhas é melhor aplicada apenas na produção/preparação e na camada OPS. Isto reduz a quantidade de mudanças entre produção e desenvolvimento, queremos que sejam tão semelhantes quanto possível, mas ainda assim é uma mudança que pode impactar negativamente a produção. No entanto, os benefícios são enormes, pois a observabilidade pode obter uma imagem clara das falhas do sistema.


A discussão aqui é um pouco influenciada pela minha experiência mais recente na construção de arquiteturas de nuvem observáveis. No entanto, o mesmo princípio se aplica a qualquer tipo de software, seja ele incorporado ou na nuvem. Nesses casos muitas vezes optamos por implementar fail-safe no código, neste caso eu sugeriria implementá-lo de forma consistente e consciente em uma camada específica.


Há também um caso especial de bibliotecas/estruturas que geralmente fornecem comportamentos inconsistentes e mal documentados nessas situações. Eu próprio sou culpado de tal inconsistência em alguns dos meus trabalhos. É um erro fácil de cometer.

Palavra final

Este é meu último post sobre a série de teoria da depuração que faz parte do meu livro/curso sobre depuração. Muitas vezes pensamos na depuração como a ação que tomamos quando algo falha. Não é. A depuração começa no momento em que escrevemos a primeira linha do código. Tomamos decisões que afetarão o processo de depuração à medida que codificamos; muitas vezes, simplesmente não temos consciência dessas decisões até ocorrer uma falha.


Espero que esta postagem e série ajudem você a escrever um código preparado para o desconhecido. A depuração, por sua natureza, lida com o inesperado. Os testes não podem ajudar. Mas, como ilustrei nos meus posts anteriores, existem muitas práticas simples que podemos adotar e que tornariam a preparação mais fácil. Este não é um processo único, é um processo iterativo que requer reavaliação das decisões tomadas à medida que encontramos falhas.