Quando perguntamos para uma pessoa desenvolvedora “o que você mais gosta de fazer no seu trabalho?”, dizer que a maior motivação é resolver problemas é, geralmente, uma resposta padrão. Nós vemos oportunidades para solucionar problemas em todos os lugares, incluindo esses que afetam nossa produtividade e velocidade.
Eu trabalhei como desenvolvedora Rails por alguns anos antes de entrar na thoughtbot, e comigo não foi diferente. Nos projetos em que trabalhei, tive excelentes referências de boas práticas de construção de produto, desde concepção até entrega. Apesar disso, nós estávamos começando a enfrentar dificuldades que normalmente são sintomas de pequenos projetos - que não necessariamente tinham planos de escalabilidade inicialmente - se tornando projetos grandes muito rapidamente.
De um lado, nós estávamos tentando entregar novas funcionalidades o mais rápido possível e expandir os projetos na mesma velocidade em que as empresas cresciam. De outro lado, nós acumulávamos débito técnico, e nossa agilidade e confiança nas entregas eram gradativamente comprometidas conforme o tempo passava.
Depois que eu entrei no time DevOps, SRE & Cloud Platform da thoughtbot e comecei a aprender mais sobre os fundamentos de SRE, tive a oportunidade de enxergar várias estratégias que poderíamos ter usado aplicando os princípios de SRE para contornar esses obstáculos.
Vamos começar olhando alguns dos desafios mais comuns com os quais eu lidava nos meus times. Você provavelmente já deve ter visto alguns desses ou até esteja os enfrentando nesse momento!
Gargalo na Integração Contínua
A quantidade de mudanças incorporadas todos os dias aumentava ao passo em que os times cresciam. Unido a isso, nossos testes eram pouco otimizados, que mesmo rodando paralelamente na integração contínua, demoravam 20 a 30 minutos para terminar - dependendo do projeto, chegavam a demorar 40 minutos! O processo de adicionar mudanças em produção ficava estagnado, assim como as pessoas que dependiam dessas mudanças para continuar trabalhando.
Erros acumulados
Resolver erros pouco urgentes que surgiam em produção nunca era uma prioridade frente a entregar novas features. Com o tempo, os erros iam se acumulando e o time tinha de aprender um contexto adicional (e atípico): quais erros podiam ser ignorados porque não eram importantes o suficiente para serem resolvidos. Não tinha como isso dar certo - inevitavelmente nós deixávamos passar alguns erros que precisavam de atenção. Sem um plano para resolvê-los eles não iam para lugar nenhum e cresciam de mãos dadas com as novas funcionalidades.
Pouca confiança para colocar mudanças em produção
Mesmo que todo pedaço novo de código fosse testado antes de ser implantado, nós tínhamos uma cobertura de testes abaixo do desejado, principalmente nos códigos mais antigos. Além disso, nós não tínhamos boas práticas de testes difundidas pelo time. Isso causava ao time medo de alterar algum comportamento do código antigo ou, ainda, de fazer uma alteração que tivesse algum impacto desconhecido em outra ponta da aplicação, já que os fluxos eram muito longos e altamente dependentes entre si.
Outro agravante significativo do medo que as pessoas tinham era, infelizmente, descobrir problemas na aplicação somente em produção em vez de em ambiente de desenvolvimento ou staging. Sem dúvidas, os testes eram o problema, mas nesses casos, mais que isso, a aplicação crescia de maneira tão complexa e acoplada com código legado - onde ninguém tinha muita coragem de mexer - que pela falta de domínio de um contexto gigante as pessoas desenvolvedoras não conseguiam imaginar todos os cenários de teste, que acabavam acontecendo nas mãos dos usuários.
O risco de colocar bugs em produção refletia em uma insegurança ainda maior sabendo que isso podia causar perdas financeiras muito grandes.
Leads perdidos através dos fluxos
Os times sabiam onde os fluxos de usuários eram mais lentos por causa das chamadas de APIs externas que demoravam muito; eles também sabiam que muitos usuários desistiam de continuar nos fluxos nesses momentos. Era possível ter essas informações identificando qual foi o último estágio que os clientes registraram nos sistemas.
Ter noção de que esses problemas existem já é um bom primeiro passo, mas seria melhor se pudéssemos extrair mais dados para entender o que podemos fazer para evitar essa perda de usuários: quantos usuários perdemos por dia por causa da lentidão? Quais são os motivos que os levam a desistir de seguir o fluxo (impaciência, falsa sensação de que o site não funcionava)? Quais são os exatos momentos de lentidão? Com essas informações em mãos poderíamos construir uma estratégia melhor para evitar perder usuários ao longo do fluxo.
Usando SRE para tratar débito técnico
Esses não eram os únicos problemas que tínhamos, mas esses exemplos ajudam a pintar a imagem inteira de como eles afetavam os projetos (e a nossa satisfação desenvolvendo código). Todos esses defeitos e obstáculos que se acumulavam eram sintomas fortes de débito técnico. Todos esses obstáculos crescentes eram fortes sintomas de débito técnico. O acúmulo de débito técnico muitas vezes é reflexo de uma cultura que não escuta, não de propósito, mas pela ausência de meios de mensurar e ilustrar esses problemas para que as pessoas envolvidas pudessem traçar um plano para resolver esses problemas. Isso porque o conceito de débito técnico em si pode ser abstrato e complicado de ser identificado.
É aí que SRE, Site Reliability Engineering, entra em cena: através da aplicação dos seus princípios e práticas, é possível ter uma ampla visibilidade de falhas, construir estratégias de escalabilidade e contenção de possíveis danos e definir metas de melhorias do que já existe, além de desenvolver novas funcionalidades com mais confiança e velocidade. Pessoalmente, ter aprendido sobre os fundamentos de SRE representou uma expectativa sobre como pode ser prazeroso desenvolver código para grandes projetos sem maiores crises.
“SRE é o que acontece quando você pede para um Engenheiro de Software projetar um time de operações.” - Google SRE Book
Sendo assim, os cinco primeiros passos que poderiam ser dados para começar a implementar práticas SRE nos projetos que enfrentavam os problemas citados seriam:
Passo 1: Definir SLIs e SLOs
Identificar o débito técnico é um dos desafios para resolvê-lo. Problemas relacionados a uptime e deployment são frequentemente sinais de que há um débito técnico que está prejudicando ativamente a sua aplicação. Os SLOs são explicitamente uma forma de medir a confiabilidade e podem ser usados para obter dados concretos sobre onde está parte do seu débito técnico. Conforme determinado pela Google no SRE Book, o SLI (Service Level Indicator) consiste em uma medida quantitativa de algum aspecto de nível de serviço fornecido, enquanto o SLO (Service Level Objective) é o valor alvo ou variação alvo de um nível de serviço medido por um SLI.
Os SLOs são medidas chave para tomar decisões de confiabilidade orientadas a dados, e são as principais medidas nas práticas de SRE. Utilizando essas métricas, seríamos capazes de ter informações relevantes de disponibilidade para os usuários. Podemos, por exemplo, rastrear os pontos de demora no fluxo para sabermos como agir para perder menos usuários.
Nesse exemplo, supondo que o fluxo de usuário envolvesse uma busca, o SLI seria o tempo de retorno de resultados, e um SLO que poderíamos almejar seria retornar o resultado de 99% das buscas em menos de 1.000 milissegundos e de 90% das buscas em menos de 100 milissegundos.
Passo 2: Determinar Error Budgets
O conceito de Error Budget (orçamento de erro, em português) está intrínseco a definição dos SLIs e SLOs. Como o próprio nome sugere, os orçamentos de erros são dados pelo quanto é permitido que a aplicação se comporte fora do esperado dentro daquela métrica. Em outras palavras, o quanto a aplicação pode violar um SLO.
Um aspecto da utilização de Error Budgets é que é necessário haver um acordo dentro de toda a organização de que quando um orçamento for queimado, todo o time responsável por aquela área deve parar qualquer outro trabalho de desenvolvimentos e resolver aquele problema. Pode soar difícil de convencer toda a organização sobre como usar os Error Budgets dessa maneira, mas não deveria ser se você tiver SLOs realistas que são necessários para manter sua aplicação funcionando bem para os usuários. Assumindo que você tenha definido seus SLOs para atingir o nível ideal de serviço, se comprometer a priorizar o trabalho que garante este nível de serviço deve ser crucial.
Pelo exemplo do passo 1, poderíamos determinar que o nosso Error Budget permitiria que até 1% de 1.000.000 de buscas demorem 1.000 milissegundos ou mais para retornarem um resultados, e até 10% de 1.000.000 de buscas demorem 100 milissegundos ou mais em uma janela de 3 meses.
Passo 3: Criar Alertas
É de se imaginar que as pessoas responsáveis pelo andamento do projeto queiram sempre saber quando tem algo de errado acontecendo, mas bons alertas não alertam o tempo todo! Na prática, notificações pouco informativas e que surgem o tempo todo se tornam barulhentas e passam a ser ignoradas com frequência, principalmente quando a maioria delas não exigem reação imediata. Como resultado, os alertas realmente importantes e urgentes podem passar despercebidos.
Então, qual o melhor momento para alertar? Sempre partindo do princípio que sabemos que as coisas não vão dar sempre certo, devemos ajustar os alertas pra quando as coisas estão dando errado demais, por tempo demais. Isso acontece quando, em determinada janela de tempo, os SLOs não estão sendo atingidos, afetando uma quantidade significativa de clientes. É importante alertar também quando o Error Budget estiver sendo queimado muito rapidamente, ou, claro, quando ele tiver sido esgotado.
Passo 4: Refatorando testes
Refatorar os testes ajuda a diminuir o tempo no CI e recuperar a confiança para fazer deploys. É primordial que o código esteja bem testado para garantir que ele seja confiável dali em diante. No entanto, reduzir o tempo do CI não é necessariamente uma responsabilidade de SRE; pode depender do seu objetivo. Nesse caso, como isso impacta diretamente na possibilidade dos usuários receberem funcionalidades e bug fixes mais rápido, é uma responsabilidade de SRE.
Passo 5: Utilizando Canary Releases
Quando falamos de aplicações que atingem uma larga escala de usuários, lançar mudanças de código pode ser cada vez mais arriscado. Mesmo testando muito uma funcionalidade e se sustentando em muitos Code Reviews, é preciso reconhecer o risco da imprevisibilidade.
E se houvesse uma maneira de diminuir o impacto de uma mudança defeituosa que pudesse causar uma catástrofe? O Canary Release é um mecanismo onde uma nova versão da aplicação é disponibilizada para uma pequena parcela de usuários. Para que isso aconteça, uma parte do tráfego da aplicação é direcionado para essa versão, enquanto o restante do tráfico é direcionado normalmente para a versão sabidamente estável. As métricas dessa nova versão disponibilizada para a pequena quantidade de usuários devem ser monitoradas. Uma vez que você estiver confiante de que a versão nova está funcionando bem, você pode, então, promovê-la a última versão estável e disponibilizá-la para todos as pessoas usuárias.
Diminuir o impacto dos riscos traz de volta a confiança para entregar mudanças!
Investir em implementar práticas de SRE traz benefícios saudáveis que reforçam as chances de sucesso da sua aplicação e a sustentabilidade dos projetos. O custo de começar a usar SRE se paga quando os problemas param de onerar tanto tempo e esforço do time.
Embora apenas algumas práticas de SRE tenham sido citadas para resolver esses problemas comuns, existem inúmeras outras mais adequadas para vários outros cenários. O DevOps, SRE & Cloud Platform da thoughtbot ficaria feliz em te ajudar a garantir que o seu site seja confiável - disponível e responsivo para os usuários - enquanto mantém um ritmo rápido de desenvolvimento de novas funcionalidades. Você pode ler mais sobre como trabalhar com o time de DevOps, SRE & Cloud Platform aqui.