Entendendo sistemas distribuídos tolerantes a falhas (Parte 1)

Todos os sistemas computacionais são passíveis de erros em algum momento. No entanto, existe uma forma de minimizar as perdas e efeitos negativos, ao se desenvolver sistemas tolerantes a falha, que em face a um problema tem um comportamento bem definido ou o oculta do usuário final.
Os desafios de se desenvolver um sistema tolerante a falhas passam pela definição de uma terminologia que diminua ambiguidade até o controle de todos os estados normais do sistema e como reagir em cada um dos diferentes estados de falha possíveis.
No contexto de sistemas distribuídos, a tolerância a falha é atingida ao se utilizar componentes redundantes, que em caso de problemas são acionados para substituir os defeituosos. Estes componentes podem ser divididos conceitualmente em serviços, servidores e a relação entre dependência dos mesmos.
Segundo Flaviu Cristian no artigo Understanding fault-tolerant distributed systens, um serviço especifica uma coleção de operações cuja execução pode ser iniciada por entradas de um usuário do serviço ou pela passagem de tempo. Um servidor por sua vez, implementa o serviço sem expor os usuários ao estado interno do serviço.
Dada a definição de servidor apresentada acima podemos inferir que um servidor implementa o seu serviço usando serviços implementados por outros servidores. Quando um serviço A precisa que um outro serviço B funcione corretamente para ele também funcionar, dizemos que A depende de B.
Classificação de falhas
Todo serviço deve ter uma especificação, que é um documento que descreve como ele deve reagir em determinada circunstancia(entrada). Um serviço pode então ser considerado correto, se ele responde as entradas da forma como sua especificação foi construída.
Com relação aos tipos de falhas, podemos citar a falha por omissão, que é quando um serviço não responde a uma determinada entrada. Outro tipo de falha comum é conhecida como falha de tempo, ela ocorre quando a resposta enviada, apesar de correta, não foi entregue no intervalo de tempo esperado. Neste caso ou a resposta é entregue antes do esperado, ou após. Uma falha de resposta ocorre quando existe uma resposta incorreta ou o sistema vai para uma transação de estado incorreta.
Semântica da falha
Além de desenvolver a especificação padrão dos serviços, onde se descreve o comportamento esperado em caso de não existir a falha, um sistema tolerante a falhas deve descrever qual o comportamento do sistema perante todas as possíveis falhas.
Quanto mais falhas um serviço permitir em um design, mais fraco ele é considerado. Um sistema é considerado forte quando ele permite apenas um número pequeno de falhas. Quanto mais forte é a especificação semântica de falhas de um servidor mais cara e complexa se torna a sua construção.
Ocultando erros hierarquicamente
Um sistema com vários servidores que possuam relação de dependência, precisam de uma forma de reconhecer um mal funcionamento dos serviços de mais baixo nível na hierarquia e ocultam o mesmo para o usuário final. Ele pode fazer isso tanto refazendo a chamada para o serviço que apresentou defeito quanto fazendo a chamada para outro serviço redundante.
O erro deve ser registrado em forma de log para que possa ser analisado posteriormente. Quando não há nenhuma forma de contornar o erro em uma instância hierárquica superior ocorre uma quebra do sistema, ou crash fail.
Ocultando erros através de grupos
A ocultação de erros usando grupos, diferentemente da ocultação hierárquica não é passada para as instâncias superiores da hierarquia de dependência. Quando um erro ocorre dentro de um grupo, ele mesmo se responsabiliza por ocultar o problema do usuário final, utilizando servidores redundantes e fisicamente independentes para que no caso de um deles cair os restantes continuem ofertando o serviço.
Normalmente a ocultação hierárquica e por grupos é usada em conjunto. Por exemplo em caso de existir uma falha em todo o grupo, o servidor que está agindo como cliente do grupo precisa lidar com esta falha para que o sistema inteiro não quebre.
Existe uma relação direta entre o custo e a força da semântica de erros de um grupo. Quanto mais fraca a semântica de erros, mais custosa a implementação do grupo será. Além disso, deve-se evitar a utilização de redundância e grupos em sistemas de muito baixo nível, pois esta prática também aumenta o custo e possui pouquíssimo impacto na solidez do sistema.
Como saber qual falha deve ser inclusa na semântica de falhas?
Existem falhas que possuem probabilidade extremamente baixa de acontecer em um determinado contexto e por este motivo o custo de inclui-la na semântica de falhas do serviço é extremamente alto.
A questão é, como saber qual falha pode ser deixada de fora e qual não pode?
Um servidor deve possuir na sua especificação não somente os requisitos funcionais mas também uma especificação probabilística, que deve abordar qual probabilidade mínima de um comportamento padrão ser observado em tempo de execução e qual a probabilidade máxima de um evento potencialmente catastrófico, e que não está na descrição padrão de possíveis falhas, possa acontecer.
Deve-se verificar se a probabilidade de se observar eventos especificados é maior do que a probabilidade mínima definida, e que a probabilidade de observar eventos não especificados é menor do que a probabilidade máxima definida como tolerável no projeto.
Conclusão
Nessa primeira parte, discutimos alguns dos pilares de um sistema distribuído tolerante a falhas. Esses conceitos são fundamentais para entendermos como tornar projetos de hardware e software o mais confiável possível dadas as limitações de recurso e técnicas disponíveis. Nas próximas partes, discutiremos alguns exemplos práticos de projetos de desenvolvimento de hardware e software.