5 princípios para o desenvolvimento de sistemas distribuídos no Itaú

Itaú Tech
ItauTech
Published in
10 min readMar 14, 2023

--

Por Denner Vinicius Almeida, Team Member na Comunidade de Cash Management do Itaú

No lado esquerdo da imagem, há a frase “Desenvolvendo sistemas distribuídos: cinco princípios para um processo de desenvolvimento com foco em padronização e qualidade”. No lado direito, há a foto de um homem negro, sentado em uma cadeira preta na frente de uma tela de computador. Ele veste uma camiseta branca e usa headphones azuis.

Com o aumento das transações digitais nos últimos anos, o Itaú Unibanco reforçou as práticas de sistemas distribuídos para o desenvolvimento de serviços cada vez mais sustentáveis, seguros e confiáveis.

Afinal, com o crescimento exponencial de usuários de serviços de internet, também tem crescido a necessidade de construir sistemas distribuídos para atender a essa demanda.

Isso porque essa arquitetura traz consigo várias vantagens, como escalabilidade, seja vertical ou horizontal, além de independência de tecnologias, implantações isoladas e mais agilidade para criar funcionalidades. Porém, trabalhar com sistemas distribuídos exige uma equipe multidisciplinar com padrões de desenvolvimento bem definidos e uma infraestrutura robusta, estável e sofisticada.

Neste artigo, detalhamos 5 princípios para construir sistemas distribuídos padronizados, com foco em ampliar a qualidade em práticas operacionais e organizacionais e garantir um impacto positivo aos nossos clientes e parceiros.

1. Estabilidade e confiabilidade

Seguindo nossas premissas de centralidade no cliente como pilar essencial de tudo que fazemos no Itaú, vamos relatar a seguir algumas iniciativas que consideramos necessárias para a detecção de falhas ou surpresas negativas em nosso ecossistema antes que elas cheguem aos nossos clientes. São elas:

Ciclo de desenvolvimento padronizado: padronização do ciclo de desenvolvimento para garantir a consistência e produtividade no desenvolvimento de um projeto. Para isso, é necessário utilizar repositórios centralizados com GIT ou SVN para versionamento de código, além de fluxos de trabalhos com branchs e práticas de code review realizadas preferencialmente por dois ou mais desenvolvedores.

Testes: não há como garantir qualidade sem testes, certo? Sendo assim, serviços ou aplicações devem ser testados para garantir que façam exatamente o que precisam fazer. Testes de lint, unitários, integração, “end to end” e de performance, são essenciais para assegurar a estabilidade e confiabilidade do projeto.

Pipeline de deployment: estabilidade e confiabilidade também vêm de uma pipeline bem padronizada e segmentada por ambientes que realizam inspeção contínua de qualidade, detecção de bugs e code smells, operações de rollback e health check antes de uma nova disponibilização completa. Além disso, também é preciso considerar as fases de implantação até chegar ao ambiente de produção. São elas: desenvolvimento, homologação e estratégias de deployment (canary, blue-green ou rolling).

2. Escalabilidade e desempenho

Devido ao grande número de clientes que utilizam nossos serviços simultaneamente, precisamos de serviços rápidos e escaláveis para não impactarmos negativamente a experiência de clientes. Com a modernização do banco e sua migração para uma infraestrutura em cloud, podemos utilizar serviços como ECS, EKS, DynamoDB, SQS e várias outras aplicações em AWS para isso, já que elas nos proporcionam possibilidades de trabalhar com arquiteturas e tecnologias mais robustas — e, consequentemente, trazer mais estabilidade e desempenho.

Escalabilidade vertical e horizontal: todo sistema distribuído deve estar preparado para ser escalado, seja com aumento de réplicas para dividir cargas de trabalho (Escalabilidade Horizontal) ou adicionando mais poder de processamento e memória (CPU e/ou RAM) para otimizar o desempenho (Escalabilidade Vertical). Ambas as estratégias são capazes de suportar grandes números de tarefas ou solicitações simultâneas, porém devem ser bem configuradas para estarem sempre preparadas em caso de crescimento futuro de solicitações.

Banco de dados relacionais e não relacionais: uma boa escolha de qual tipo de banco de dados usar poderá fazer toda a diferença em termos de performance. Se a escala de crescimento de um serviço for alta, os serviços dependentes também precisam escalar para que não haja gargalo durante o aumento de solicitações. Na maioria dos casos, bancos relacionais atendem essa necessidade muito bem, principalmente se tratando de DBaaS (Database As a Service ou Banco de Dados como Serviço).

Vale lembrar que os bancos de dados como Serviços são escaláveis, possuem alta performance e têm nós de replicação somente para leituras e disponibilidades em múltiplas regiões. Quando há necessidade de obter mais performance, a escolha certa é utilizar bancos de dados não relacionais, que suportam altos volumes de solicitações de leitura ou gravação e contam com uma flexibilidade de estrutura de dados e esquemas dinâmicos.

Sistemas de cache e comunicação assíncrona: a comunicação entre sistemas distribuídos muitas vezes é complexa, e pode ser feita realizando requisições REST, gRPC ou utilizando comunicação por meio de sistemas de mensageria com filas ou tópicos. Quando um serviço precisa obter dados que estão sob domínio de outros serviços e devolvê-los ao solicitante, em um cenário em que há milhares de solicitações simultâneas, esse movimento pode causar problemas de latência.

Como escalar um serviço irá gerar mais custos financeiros, como primeira opção, pode ser considerado o uso de um serviço de cache que a latência seja menor durante as solicitações, o que evitará a sobrecarga dos serviços dependentes. Nos casos em que é necessário enviar dados para outro serviço sem a necessidade de resposta imediata, a abordagem mais comum é a utilização de serviços de mensageria com filas ou tópicos. Serviços de mensageria são extremamente usados em sistemas distribuídos, pois contribuem para performance e são confiáveis. Além disso, ainda são resilientes por possuírem estratégias de retry e back-off.

3. Resiliência

Com o crescimento de sistemas distribuídos, novos pontos de falhas são criados — e componentes individuais podem falhar. Por isso, precisamos assumir essa possibilidade para prepará-lo de forma que essas falhas sejam toleradas e evitarmos impacto em clientes. Existem alguns princípios de resiliência que demonstram o mínimo que se espera de um serviço resiliente. Afinal, nenhuma falha em qualquer parte do ecossistema deve interromper a experiência do usuário, e todo serviço deve tomar uma ação corretiva automaticamente quando um problema é encontrado.

Outro ponto importante é que o serviço deve ser bem monitorado para ser capaz de mostrar o que está acontecendo, a qualquer momento. Considerando esses princípios, existem algumas estratégias que podem ser usadas para construir um sistema mais resiliente:

Retry Pattern: esse padrão é muito utilizado em cenários em que as chamadas para um serviço podem falhar por um curto período (o que normalmente pode ser corrigido rapidamente). A implementação desse padrão consiste em definir o número máximo de tentativas, o tempo de espera, o tipo de back-off (uma fórmula para incrementar o tempo de espera a cada nova tentativa) e, em alguns casos, uma política de fallback (resposta alternativa para lidar temporariamente com o problema).

Quando esse padrão é implementado, as falhas que ocorrem podem ser recuperadas dentro da mesma chamada/método, evitando uma perda total de todo o fluxo e o retrabalho de ter que refazê-lo desde o início. O retry pattern é uma das várias soluções que colaboram para um sistema robusto, e funciona como uma pequena engrenagem que deve ser utilizada para formar um sistema altamente resiliente.

Circuit Breaker: existem alguns cenários em que esse padrão pode ser implementado. Caso um serviço tente chamar um outro ou acessar um recurso compartilhado que esteja falhando, o circuit breaker evita que essa chamada aconteça até que o serviço ou recurso esteja estável. Isso é feito para evitar um bombardeamento do serviço que está em falha com chamadas inúteis, o que evita desperdício de tempo e recurso, além de dar a ele algum tempo para se recuperar. O circuit breaker trabalha muito bem em conjunto com retry pattern e, certamente, traz vantagens em chamadas para outros serviços.

Dead Letter: quando se trata de comunicações entre sistemas usando filas ou tópicos, existem casos em que a mensagem pode não ser totalmente processada, mesmo após várias tentativas de reenvio. É sempre uma boa prática configurar um dead-letter (fila ou tópico de devoluções das mensagens não processadas) para fazer o tratamento da mensagem em um fluxo alternativo. As alternativas são tratar a mensagem utilizando um Saga Pattern para o gerenciamento de falhas, criar alertas e abrir incidentes ao time para resolver o problema — ou até mesmo reprocessar essas mensagens manualmente em outro momento ou com serviços agendados.

Com a adoção de práticas de resiliência para nossos serviços, diminuímos os impactos ocasionais de instabilidade e falhas sem a necessidade de interromper o fluxo de uma execução que estava em andamento com retentivas e/ou notificações sobre o ponto de falha. Dessa forma, conseguimos saber onde o ponto de falha ocorreu e ainda temos um sistema pronto para lidar com problemas durante seu funcionamento, o que aumenta a sua confiabilidade e melhora a experiência dos clientes.

4. Monitoramento

Sistemas distribuídos precisam ser adequadamente monitorados. Esse é um dos princípios mais importantes na construção de um sistema. Todo serviço necessita de um monitoramento apropriado por meio de dados de logging, tracing e metrics para que se torne confiável e com garantia de alta disponibilidade. Além disso, é fundamental o acompanhamento de dashboards para análise visual, com informações claras e objetivas de todas as fontes de dados que foram coletadas nos serviços, de forma que qualquer pessoa com os conhecimentos mínimos consiga entender o que está acontecendo.

Aqui no Itaú, possuímos várias aplicações e serviços funcionando em conjunto, e adoção de um bom monitoramento nos trouxe a possibilidade de resolver problemas mais rapidamente e com mais facilidade, além de ter nos oferecido uma base de conhecimento mais ampla para futuras resoluções de problemas e para a utilização de métricas para tomarmos melhores decisões de custos ou de arquitetura. Abaixo, compartilho algumas de nossas premissas de monitoramento:

Logging: logs são essenciais para descrever o estado do serviço em determinado momento. Quando falamos de sistemas distribuídos, existem algumas premissas que devem ser levadas em consideração:

— Logar somente o que é relevante para ser observado, já que, se toda informação for considerada importante, nenhuma delas será considerada relevante de fato. Lembre-se de que logs são dados. Por isso, gravá-los, acessá-los e mantê-los tem um custo e deve ser feito de forma estratégica.

— Se possível, utilizar a versão do sistema nos logs para mitigar e resolver, de forma rápida e eficaz, os problemas de falhas específicas de uma recente implantação.

— Nunca gravar arquivos de logs dentro do próprio serviço, pois quando se trata de sistemas distribuídos, logs precisam ser escaláveis, disponíveis, facilmente acessíveis e pesquisáveis.

— Jamais logar informações em nível de servidor e de infraestrutura, ou informações sensíveis que apresentam risco à segurança, como documentos de identificação, nomes de clientes e outros dados privados.

Algumas ferramentas nos dão a possibilidade de centralizar logs de forma estruturada, com mecanismos avançados de pesquisa e criação de gráficos por meio de informações de logs.

Tracing: utilizar rastreabilidade nos permite visualizar toda a trajetória percorrida de um serviço para outro, assim é possível analisar a causa raiz de falhas. Cada requisição possui um identificador único, que é enviado para cada serviço até finalizar a pilha de requisições. Algumas ferramentas disponíveis no mercado fornecem mecanismos para rastreabilidade distribuída.

Métricas: algumas das métricas que devem ser capturadas são as de servidor, infraestrutura e dos próprios serviços em todo ambiente. As métricas principais de servidor e infraestrutura são as de CPU, RAM, threads e conexões com outros serviços externos (banco de dados, brokers de mensageria etc.). Já no caso das métricas dos serviços, as principais são as métricas específicas da linguagem, disponibilidade, volumetria, latência, taxa de sucesso por endpoint, códigos de status das respostas por endpoint, produção e consumo de mensagens. Com essas métricas é possível ter insumos suficientes para gerar gráficos e alertas com base nesses dados.

Logging, tracing e metrics: são dados brutos que devem ser refinados. Quanto mais esses dados são trabalhados, mais informações relevantes podem ser obtidas para criação dos dashboards e alertas.

5. Documentação

A documentação de sistemas distribuídos se estende muito mais do que um swagger em uma API. Uma documentação de baixa qualidade prejudica a produtividade da equipe de desenvolvimento, já que sem o entendimento de todo o ecossistema, cada novo desenvolvedor precisará começar do zero para entender o funcionamento de cada serviço. Uma boa documentação consiste em adotar algumas estratégias como:

— Possuir a documentação em uma base de conhecimento centralizada e de fácil acesso, que seja atualizada a cada mudança significativa.

— Conter descrição dos serviços, diagramas da arquitetura, guias de bordo e desenvolvimento.

— Inserir informações úteis de contatos e links.

— Em alguns casos, ter uma seção de FAQ.

— Documentar os serviços por completo, informações sobre o fluxo de solicitações, endpoints e integrações.

Realizar uma boa documentação é uma prática fundamental para o sucesso dos projetos de uma empresa, pois trazem vantagens no processo de comunicação, produtividade da equipe, fluxo de trabalho e reduz custos e erros, aumentando o valor das entregas.

No Itaú não é diferente: realizamos documentações de nossos serviços, possuímos um repositório centralizado de todas as documentações, vídeos de explicações e portal de desenvolvedor, tudo para aumentar a produtividade e o envolvimento das equipes.

Com esse artigo, esperamos ter contribuído com sua jornada de conhecimento sobre sistemas distribuídos — e que você tenha conseguido ter uma ideia de como eles são geridos aqui no Itaú Unibanco. Se você tem alguma experiência com esse tema ou algum ponto que gostaria de compartilhar com a gente, insira nos comentários abaixo!

--

--