“Olá, meu nome é Don e… eu sou um arquiteto”.
“Oi, Don”.
Chegou o momento de mais uma reunião mensal e, dessa vez, estou no meio do público, observando um outro ser o porta-voz da nossa aflição particular. Sossegado, me pergunto se quando subi lá pela primeira vez eu aparentava tanto nervosismo.
“Obrigado. Eu trabalho para uma grande empresa Fortune 500, e minha função é ajudar a arquitetar e construir sistemas de missão crítica, e —”
Franzo a minha testa e, de forma previsível, como se seguisse uma deixa de roteiro, o líder do nosso grupo pigarreia. “Don, é o seguinte, temos uma regra específica aqui no nosso grupo, quanto à honestidade”.
“Não entendi”.
“Temos de ser honestos uns com os outros, o que resulta em honestidade com nós mesmos. Nesse caso, eu pediria que você evitasse ficar dourando a pílula — na verdade, ninguém aqui constrói sistemas de ‘‘missão crítica’, não é mesmo?”
“Eu, eu não… Como assim?” A expressão dele me lembrou o olhar do meu filho quando eu lhe disse pela primeira vez que ele não poderia levar o seu “cobertorzinho azul” para a escola.
A voz do líder do grupo é calma e tranqüilizadora, mas determinada. “A NASA cunhou este termo na década de 1950 em referência aos sistemas do programa espacial necessários para evitar que perdessem astronautas, sem falar em arruinar toda a missão. Quando foi a última vez que você construiu um sistema desses?”
Enquanto a intervenção continua —“Mas eu construo o notificadores RSS da minha empresa, e administrar a imagem pública da empresa é certamente uma missão crítica, correto?” — começo a pensar o seguinte: decerto alguns aplicativos são mais importantes que outros, mesmo que não mereçam a alcunha de ‘“missão crítica”. Então, o quê, exatamente, é preciso fazer para construir aplicativos robustos?
Tornou-se moda nos círculos de desenvolvimento falar que aplicativos e projetos são de “missão crítica”. Contudo, muito poucos aplicativos enquadram-se na definição do termo: um sistema que não pode falhar sem que haja sérias repercussões, e isso quer dizer vidas perdidas, e não telefonemas às três da manhã para reinicializar o servidor.
Pense nisso um momento: se um aplicativo é verdadeiramente de missão crítica, isso significa que a empresa fará de tudo para impedir qualquer tipo de interrupção de operação. Isto significa discos redundantes em cada computador envolvido no processo (o servidor web e o servidor de banco de dados são apenas os candidatos mais óbvios), com processos de failover (recuperação em caso de falhas), para que, no caso de um disco falhar, o sistema operacional mudará para o outro disco. Felizmente, matrizes RAID modernas, sejam implementadas por software ou hardware, são capazes de proporcionar uma troca instantânea de discos. Mas o que acontece se um chip de RAM dá problema? Ou a fonte de energia é interrompida? Ou a CPU tem um superaquecimento devido a uma falha da ventoinha e queima simplesmente? Ou o roteador é atacado por um vírus e morre?
Considere os perigos das falhas de software — um download automático de patch falha e deixa o sistema inutilizável; ou uma atualização manual de software dá um erro por falta de eletricidade (o que nunca deveria acontecer, porque, claro, todos os computadores — os servidores e o computador que está carregando o patch no servidor — estão conectados a um no-break, certo?); ou então um desenvolvedor declara que um bug está “corrigido”, quando, na verdade, não está. Todas essas falhas de software são igualmente possíveis e demandam uma solução de failover ou compensação de falhas, de preferência automatizada.
Considerando o custo de servidores, ISPs, roteadores e toda uma parafernália de hardware redundantes, sem falar no custo do desenvolvimento de sistemas de software com failover automatizado, a maioria das empresas descobrem rapidamente que não precisam que um sistema seja, necessariamente, de missão crítica. Em vez disso, o que as empresas mais querem é apenas uma confiabilidade suficiente para atender aos seus acordos de qualidade de serviço com sua base de clientes. E, para conseguir isso, os desenvolvedores têm de criar recursos de gerenciamento e monitoração para o sistema.
***
Como sempre, a confiabilidade de um aplicativo — de missão crítica ou não, corporativo ou não, os adjetivos realmente não importam muito — é uma negociação de custos. Quanto maior a confiabilidade um código tiver, mais custará para colocar essa confiabilidade em operação. Naturalmente, a moeda de pagamento por esse custo pode variar. Por exemplo, simplesmente optar por ambientes gerenciados (como .NET ou Java) pode acrescentar um grau de confiabilidade e segurança (que é um aspecto da confiabilidade) ao sistema, só porque um sistema com controle de erros tem muito mais dificuldade para colocar um processo em falha do que um sistema não gerenciado. Aqui, os custos estão na opção da tecnologia e as implicações (coleta de lixo, acesso restrito ao ambiente não gerenciado subjacente e assim por diante) para a maioria dos aplicativos são negociações aceitáveis.
Um dos aspectos centrais da confiabilidade de software, embora não pensemos nela dessa forma, é a segurança. Um aplicativo que libere seus dados ao primeiro “pirata” que estiver rondando não irá melhorar a reputação do seu criador tampouco aumentar a confiança dos seus usuários. Os efeitos dessa violação de segurança sobre a confiabilidade do aplicativo podem ser: sutis (como usuários de repente não conseguindo mais se conectar ao aplicativo porque o ataque desabilitou algumas contas de usuário); extremas (como usuários de repente descobrindo que não podem mais se conectar ao aplicativo porque o aplicativo e todos os seus dados foram apagados no servidor); ou embaraçosos (como usuários de repente descobrindo que seus dados estão abertos a todos os usuários do Facebook ou na página MySpace na web); ou piores ainda (como usuários de repente descobrindo que não podem mais se conectar ao aplicativo porque não há mais espaço em disco disponível no servidor, pois o atacante decidiu usar a unidade de disco do servidor para armazenar a sua coleção de “warez”[1]).
A segurança, então, conforma uma parte significativa dos esforços que visam dar confiabilidade ao aplicativo. Os desenvolvedores devem implementar um controle de acesso apropriado, e “apropriado” significa “só conseguir fazer aquilo que supostamente se tem permissão para fazer” (essa frase foi deliberadamente construída de forma vaga porque cada aplicativo tem requisitos de controle de acesso diferentes), bem como uma verificação apropriada de credenciais (você é quem alega ser e como confirmar isso?). Este tópico foi discutido em um artigo anterior desta série, portanto o deixaremos de lado por enquanto.
Outras formas de confiabilidade não são tão facilmente identificadas. Por exemplo, a discussão anterior mencionou determinadas opções de confiabilidade de hardware que podem impor custos financeiros e administrativos extravagantes. A configuração de um segundo servidor de banco de dados, espelhado/redundante, para servir de failover de um backup no caso de falha do servidor de banco de dados, duplica (e mais um pouco) o custo do hardware de banco de dados. Contudo, o custo não é apenas financeiro: agora os administradores de sistema devem verificar falhas de hardware e software no dobro de computadores, manter duas vezes mais patches e ter um espaço duplicado em disco rígido para backup (failover não significa que os backups se tornam menos necessários).
Além disso, os administradores de sistema e desenvolvedores devem testar o plano de failover periodicamente; e isto é algo que eles não tinham de fazer antes. Lembre-se: assim como um código não testado é um código pronto para falhar no pior momento possível, um plano de failover não testado é um plano pronto para falhar no pior momento possível. A última coisa que você vai querer descobrir é que o servidor de banco de dados caiu e o plano de failover não está funcionando por algum motivo. O mesmo vale para qualquer cenário de failover, não apenas para o servidor de banco de dados: unidades RAID, servidores de aplicativos etc.
No entanto, tudo isso diz respeito à confiabilidade de hardware e em geral representa investimentos que não são viáveis para todos os aplicativos. Os aplicativos que devem ter redundância para failover abaixo de um segundo, as soluções de hardware são úteis e, às vezes, um pré-requisito. Contudo, nem todos os aplicativos precisam desse tipo de redundância, um grau de surpreendente robustez e resiliência pode ser obtido pelo preço de algum tempo e esforço de desenvolvimento.
***
Consideremos um cenário hipotético: são três da manhã e o administrador de sistemas sênior está em casa, dormindo (ou, mais provavelmente, jogando o último game em que se viciou, World of Warcraft ou Guerra nas Estrelas: Galáxias), quando toca o telefone. São os caras do suporte de primeira camada, e eles estão informando que o site está fora do ar por algum motivo. Resmungando, porque esta é a terceira vez que isto acontece no mês, o administrador de sistemas pega o carro, dirige vinte minutos (na melhor das hipóteses) até o trabalho, entra no centro de dados, olha para os servidores, dá uma olhada em uns arquivos texto para tentar umas opções, dá de ombros, reinicializa aquela bagunça toda e volta para casa. Tempo total gasto: uma hora.
Mas há alguma chance de o problema não ser tão fácil assim de resolver. O software falhou por algum motivo e é bem plausível que a 'saída do ar' tenha deixado detritos espalhados (no banco de dados, no sistema de arquivos, sei lá) que precisem ser recolhidos por algum desenvolvedor antes que o sistema volte à operação. Isto significa que o administrador de sistemas sênior, após reinicializar e descobrir que os processos não estão sendo executados, então terá de tirar um desenvolvedor da cama. Agora é a vez do desenvolvedor se dirigir ao mesmo centro de dados para investigar as provas incriminadoras da falha (que podem se resumir a um arquivo de log de diagnóstico e/ou um despejo [dump] de console de rastreamento de pilha), entender o problema, reiniciar o aplicativo e esperar que a falha apareça rápido (para conseguir ver o problema e, se tudo der certo, conseguir fazer um melhor diagnóstico e depurá-lo) ou que o sistema seja executado sem problemas.
Muita coisa está errada nesta história, principalmente o fato de duas pessoas terem de se arrastar para fora da cama, dirigir até o escritório, entender um problema, depurá-lo, corrigi-lo (se correr tudo bem) e reiniciar os servidores. Fora o fato de que estamos pressupondo que o problema seja fácil de ser definido e resolvido, o que chama a atenção é que tanto o administrador do sistema quanto o desenvolvedor estão no centro de operações de rede (talvez de pijamas, dependendo do sono em que estavam quando foram acordados) para executar esta pequena intervenção.
Consideremos uma versão ligeiramente modificada desta história. No segundo cenário, o aplicativo do servidor falha, mas agora, em vez de uma mensagem de e-mail ou ligação de cliente acionar uma ligação para o administrador do sistema, um programa “watchdog” é ativado, gera um instantâneo do estado de execução do programa, incluindo a região do código que gerou a falha, e salva-a em um disco. Em seguida, é gerada uma mensagem de e-mail para um alias de e-mail, que inclui a equipe de desenvolvimento e os administradores de sistema (e possivelmente alguns elementos do gerenciamento), com referências ou anexos com o estado de execução e arquivos de log.
O “watchdog” então reinicia o aplicativo para manter o sistema em execução e os clientes do sistema percebendo o máximo de tempo de atividade possível.
Na manhã seguinte, quando os desenvolvedores e os administradores de sistema chegam, o e-mail sobre a falha vira o grande assunto do dia. Os desenvolvedores usam as informações presentes no despejo (dump) do estado de execução para fazer um rastreamento retrospectivo do problema, talvez até anexando um depurador ao despejo para examinar mais facilmente o estado do aplicativo em falha. Os logs de diagnóstico do aplicativo ajudam a fazer o rastreamento retrospectivo do estado em um período mais longo, mas é totalmente possível que os arquivos de log não tenham as informações desejadas. Freqüentemente, os arquivos de log são “reduzidos” em quantidade de detalhes que geram, para evitar o consumo de muito espaço de disco só com arquivos de log[2]. Para garantir, enquanto os desenvolvedores ainda trabalham para resolver o problema, os administradores executam um utilitário que “aumenta” a quantidade de detalhes nos arquivos de log de aplicativos e registra um script que é rodado toda semana para reduzir novamente o grau de detalhamento.
Há uma diferença estonteante entre os dois cenários, não é mesmo? No segundo caso, desenvolvedores e administradores de sistema podem dormir a noite toda e atacar em conjunto o problema de manhã, mas os clientes continuam a receber o serviço durante a noite. Qual história você preferiria que acontecesse na sua empresa?
***
Os desenvolvedores devem considerar com muito cuidado o que as facilidades de gerenciamento e monitoração representarão para um aplicativo — como os administradores de sistema (e desenvolvedores) interagem com o sistema em execução e como o sistema em execução interage com eles? Pode parecer uma tolice gastar tempo pensando diante de exigências de recursos e prazos apertados, mas com tanta coisa envolvida no desenvolvimento de software, algum tempo dedicado ao planejamento muitas vezes leva a grandes compensações mais tarde. Neste caso, o tempo de planejamento é gasto para responder uma questão simples: como nós (desenvolvedores e administradores de sistema ) conseguimos uma maior visibilidade — e, por extensão, controle — para o sistema em execução?
Em essência, o software é um processo abstrato e invisível. Diferentemente de todos os outros setores do planeta, o que construímos opera em um mundo que é quase completamente inacessível a nós: não podemos simplesmente “abrir o capô” e dar uma olhada no que está acontecendo lá dentro. Não apenas está acontecendo em um mundo imperceptível para os nossos instrumentos visuais, como também acontece a uma velocidade que mal podemos conceber. Considere esta comparação: um motor de carro funciona a milhares rotações por minuto; já o PC opera a bilhões de ciclos por segundo. A execução ocorre de fato dentro de um quadradinho de silício com algumas polegadas em cada lateral. Para realmente “ver” o que está acontecendo dentro do sistema, temos que colocar “ganchos” nele, pedindo ao código para “informar” eventos à medida que ocorrerem, para que nossas percepções humanas mais lentas possam compreendê-los.
Esses “ganchos” podem ser de diversas formas, incluindo, entre outras, contadores do Windows Performance Monitor, gravadores de log de eventos, contadores WMI, acionadores de linhas e/ou tabelas de bancos de dados e mais. E se esses “ganchos” não forem suficientes, os desenvolvedores sempre podem construir os seus próprios, mas isso normalmente requer um grau de comprometimento com a infra-estrutura que a maioria dos projetos não tem condições de assumir.
Mas, antes de mais nada, para que se preocupar com isso?
Em um emprego anterior, a equipe de desenvolvimento de um amigo estava padecendo com o chefe do CTO obcecado por inicialização. Ele perambulava pelas salas da equipe de desenvolvimento perguntando a cada desenvolvedor: “Quantas pessoas atendemos hoje?” e ao mesmo tempo estalava os dedos diante de cada um deles para lembrá-los de que estavam em “ritmo de Internet” (uma variação não muito sutil do refrão do chefe convencional, “trabalhe mais rápido!”). Este tipo de comportamento é uma chateação, para falar o mínimo, sem contar que é ineficiente e ineficaz.
Quando um superior meu começou a demonstrar sinais de que estava para adotar um comportamento semelhante, o nosso grupo construiu alguns ganchos no sistema: toda vez que um usuário criava uma nova sessão de aplicativo web, um contador era incrementado. Toda vez que um usuário confirmava um trabalho, um contador era incrementado. Toda vez que uma sessão de usuário era encerrada (normalmente em razão de tempo de limite esgotado, porque era impossível convencer os usuários a clicar no link “logout” no aplicativo web), um contador era incrementado e assim por diante.
Então, criamos uma página na web simples que se atualizava a cada cinco minutos; a cada atualização, a página lia os contadores, fazia alguns cálculos simples, criava um gráfico e processava tudo isso para o navegador. Sem firulas, apenas um gráfico linear simples. Em seguida colocamos um monitor de tela grande em uma mesa de café dentro de uma caixa de vidro no centro de operações de rede de modo que pudesse ser visto por qualquer pessoa que passasse por lá.
Como era de se esperar, cortamos as visitas do nosso chefe para fazer perguntas sem sentido do tipo: “Quantas pessoas atendemos hoje?” Mas o que nos admirou com essa solução específica foi outro comportamento colateral: gerentes enfeitados de terno e gravata se reuniam no corredor em frente ao centro de operações de rede e assistiam aos gráficos por horas (independentemente do fato de que eles podiam fazer isso em seus navegadores no conforto das suas salas), “analisando” os dados. “Olhe como os picos de trabalhos confirmados acontecem logo antes do almoço? Isto deve querer dizer que os usuários estão terminando algum trabalho um pouco antes de saírem para o almoço…” e por aí ia.
(Ei, apesar das análises não muito científicas que eles faziam, pelo menos nos deixaram em paz.)
E, mais importante ainda, descobrimos que os administradores de sistema começaram a usar os dados como indicador geral da integridade do sistema. Por exemplo, eles rapidamente entenderam que quando os contadores de “novas sessões web” caíam, isso significava que o servidor da web estava fora do ar ou a Internet estava caindo; ou quando o contador de “trabalho confirmado” diminuía, o banco de dados estava fora de operação ou alguém acidentalmente tinha tirado o cabo errado da tomada, interrompendo as conexões com o servidor de banco de dados. Os administradores de sistema podiam atacar os problemas antes que chegassem mais alto na hierarquia gerencial, o que fez as suas estatísticas de tempo de operação subirem e as chamadas para a linha de suporte caírem.
Isto tornou os administradores de sistema felizes. E, como constatamos, administradores de sistema felizes implica desenvolvedores felizes, ou pelo menos olhando de forma diferente para o Starcraft instalado em seus PCs de trabalho, pois podiam relaxar nas noites de sexta. Eu não vou prometer que os seus administradores de sistema ficarão da mesma forma felizes em tornar os desenvolvedores felizes, mas é incrível a quantidade de boa-vontade dedicada à equipe de desenvolvimento quando os administradores de sistema estão felizes.
***
Construir alguns recursos simples de monitoração e gerenciamento em um aplicativo pode ser mais que meio caminho andado para criar um software mais confiável. Isto não significa altos investimentos em um novo “servidor de gerenciamento” ou reescrever todos os códigos e atulhá-los com chamadas de WMI ou contadores de PerfMon. Às vezes, até mesmo as medidas menos complexas podem ser utilizadas para criar um maior sentido de confiabilidade.
Como exemplo prático, considere umas das principais coisas que os administradores de sistema fazem para que os seus aplicativos falhem: instalam uma nova versão deles. Nada parece ser mais eficiente para acionar uma falha do que a instalação de uma nova versão do código. Como compensar isto? Fácil: criar uma “página feliz” (pressupondo que o seu aplicativo seja de web). Crie uma página simples, acessível por administradores, que execute repetidamente uma lista de verificação de preocupações — versão do software, versões de dependências necessárias, acesso ao banco de dados etc. — e exiba um ícone de “sorriso verde” se a verificação passar ou um “franzido vermelho” se a verificação não passar. Para parafrasear o pessoal dos testes de unidade, “se a página não estiver verde, o sistema não está limpo”.
Agora, em cada nova instalação, os administradores de sistema têm uma única página para visitar e verificar o estado do sistema, completa e com um quadro geral do que exatamente falhou. Eles não só podem usá-la para verificar se a instalação está apropriada, como também poderão, quando o telefone tocar às três da madrugada e for um cliente ou parceiro reclamando que o sistema está fora do ar, abrir a “página feliz”, verificar se todos os componentes do sistema estão ativos e funcionando (ou não, e nesse caso eles têm uma dica de onde ir para resolver o problema) e voltar para a cama às 3h15.
E isso, pessoal, é um administrador de sistema feliz.
***
[1] História real — isso aconteceu em um lugar em que trabalhei, onde o “aplicativo” em questão era a nosso repositório de controle de códigos-fonte.
[2] Francamente, em um mundo em que unidades de 500GB e 1TB estão à venda em lojas de eletrônicos, parece um absurdo continuarmos reduzindo a quantidade de dados gerados em um log de diagnóstico para economizar espaço em disco; porém, se o servidor de aplicativos for executado sem parar por um período longo, mesmo 1TB pode ser insuficiente, portanto, o princípio ainda é válido… mesmo que um pouco implausível.