BT

De microservices ao monolítico: a jornada da Segment

| por Mark Little Seguir 15 Seguidores , traduzido por Andrea Mussap Seguir 7 Seguidores em 27 ago 2018. Tempo estimado de leitura: 9 minutos |

Tivemos uma série de artigos direcionados à experiência em torno da adoção de microservices ao longo dos anos, abrangendo sucessos e falhas. Um artigo recente de Alexandra Noonan, da Segment, conta sua jornada do monolítico ao microservices e também o caminho contrário. No artigo, Noonan descreve como mudaram sua arquitetura original e simples para microservices:

Havia uma API que recebia eventos e os encaminhava para uma fila de mensagens distribuídas. Um evento, nesse caso, é um objeto JSON gerado por um aplicativo web ou mobile que contém informações sobre usuários e suas ações. Como os eventos eram consumidos na fila, as configurações gerenciadas pelo cliente eram verificadas para decidir quais destinos deviam receber o evento. [...] O evento era então enviado para a API de cada destino, um após o outro, o que era útil porque os desenvolvedores só precisavam enviar o evento para um endpoint único, a API da Segment, em vez de criar potencialmente dezenas de integrações.

A não entrega de eventos fazia com que o evento fosse colocado em fila novamente no sistema, o que significa que, em alguns casos, os funcionários eram responsáveis pela entrega de novos eventos, bem como pela tentativa de entrega de eventos que falharam, o que poderia resultar em atrasos em todos os destinos. Como Noonan explica:

Para resolver o problema de bloqueio da cabeça da fila (HOL blocking), a equipe criou um serviço e uma fila separados para cada destino. Essa nova arquitetura consistia em um processo de roteador adicional que recebia os eventos de entrada e distribuía uma cópia do evento para cada destino selecionado. Agora, se um destino tivesse problemas, apenas sua respectiva fila teria uma cópia de backup e nenhum outro destino seria afetado. Essa arquitetura em estilo de microservices isolava os destinos uns dos outros, o que era crucial quando um destino experimentava problemas como costuma acontecer.

Em seguida, o artigo discute como a equipe de desenvolvimento da Segment originalmente tinha todo o código em um único repositório, e como isso gerava problemas:

Um grande ponto de frustração era que um único teste quebrado fazia com que os testes falhassem em todos os destinos. Quando queríamos implantar uma alteração, tínhamos que gastar tempo corrigindo o teste quebrado, mesmo que as alterações não tivessem nada a ver com a alteração inicial. Em resposta a esse problema, decidiu-se dividir o código de cada destino em seus próprios repositórios.

Isso levou a melhorias na flexibilidade para as equipes de desenvolvimento. No entanto, como o número de destinos cresceu, o mesmo aconteceu com o número de repositórios. Para aliviar o fardo dos desenvolvedores terem que manter essas bases de código, a equipe da Segment criou várias bibliotecas compartilhadas para transformações e funcionalidades comuns em todos os destinos. Esse conjunto de bibliotecas compartilhadas trouxe consigo um benefício óbvio para a manutenção. No entanto, houve uma desvantagem menos óbvia: a atualização e o teste de alterações nas bibliotecas compartilhadas começaram a ocupar muito tempo e introduziram um elemento de risco por medo de quebrar destinos não relacionados. Por fim, versões diferentes dessas bibliotecas começaram a surgir e divergiram uma da outra, levando a um problema imprevisto, em que cada base de código de destino dependia de versões diferentes das bibliotecas compartilhadas. Como Noonan admite, eles poderiam ter construído ferramentas para ajudar a automatizar o lançamento de mudanças nessas bibliotecas. No entanto, mais ou menos nessa época, eles estavam encontrando outros problemas com sua arquitetura de microservices.

O problema adicional foi que cada serviço tinha um padrão de carga distinto. Alguns serviços lidariam com um punhado de eventos por dia, enquanto outros com milhares de eventos por segundo. Para destinos que lidavam com um pequeno número de eventos, um operador teria que dimensionar manualmente o serviço para atender à demanda sempre que houvesse um aumento inesperado na carga.

O escalonamento automático era um recurso implementado no sistema, mas como cada serviço normalmente exigia recursos específicos de CPU e memória, o ajuste da configuração de escalonamento automático era "mais arte que ciência". Como mencionado anteriormente, o número de repositórios aumentava cada vez que eles adicionavam destinos, e em um determinado momento, a equipe estava adicionando três destinos por mês em média, exigindo também mais filas e ainda mais serviços.

No início de 2017, atingimos um ponto de inflexão com uma peça central do produto da Segment. Parecia que estávamos caindo da árvore de microservices, atingindo todos os galhos na descida. Em vez de nos permitir ir mais rápido, a pequena equipe se viu mergulhada em uma complexidade explosiva. Os benefícios essenciais dessa arquitetura tornaram-se ônus. Quando a nossa velocidade despencou, nossa taxa de defeitos explodiu. [...] Por isso, decidimos dar um passo atrás e repensar todo o pipeline.

No restante de seu artigo, Noonan descreve como eles se distanciaram de sua arquitetura de microservices , que incluiu o desenvolvimento da Centrífuga responsável por substituir todas as suas filas individuais e, em vez disso, enviar eventos para um único serviço monolítico. Eles também moveram todo o seu código de destino para um único repositório, mas desta vez impondo algumas regras específicas para o gerenciamento do código: haveria uma versão para todos os destinos e estes seriam atualizados de acordo. Eles não precisavam mais se preocupar com diferenças entre versões de dependência, pois todos os destinos usavam a mesma versão e continuariam a fazê-lo. Para os desenvolvedores, tornou-se muito menos demorado e menos arriscado manter um número crescente de destinos.

Há muito mais no artigo de Noonan sobre sua jornada de volta a um serviço monolítico, e vale verificar, pois inclui detalhes sobre a arquitetura, pensamentos sobre estrutura de repositório, e abordagem para construir um conjunto de testes resiliente. No entanto, o resumo dos benefícios que a equipe encontrou inclui o seguinte:

Em 2016, quando nossa arquitetura de microservices ainda estava em vigor, fizemos 32 melhorias em nossas bibliotecas compartilhadas. Neste ano, fizemos 46 melhorias. Fizemos mais melhorias em nossas bibliotecas nos últimos seis meses do que em 2016. A mudança também beneficiou nossa história operacional. Com cada destino vivendo em um único serviço, tínhamos uma boa combinação de de CPU e destinos de memória intensos, o que tornava o dimensionamento do serviço para atender à demanda significativamente mais fácil. Um grande worker pool pode absorver picos de carga, portanto, não seremos mais paginados por destinos que processam pequenas quantidades de carga.

No entanto, existem algumas desvantagens / compensações para esta nova arquitetura que incluem o fato de que o isolamento de falhas é difícil (se um bug em um destino faz com que o serviço falhe, ele falha em todos os outros destinos), e a atualização da versão de uma dependência pode quebrar alguns outros destinos que precisam ser atualizados também. Noonan termina o artigo em uma nota pragmática:

Ao decidir entre microservices ou um monólito, há diferentes fatores a serem considerados em cada um deles. Em algumas partes de nossa infraestrutura, os microservices funcionam bem, mas os destinos do lado do servidor foram um exemplo perfeito de como essa tendência popular pode realmente prejudicar a produtividade e o desempenho. Acontece que a solução para nós era um monólito.

Na verdade, algumas dessas preocupações com microservices podem soar familiares. No início deste ano, informamos que os microservices sugeridos pela ThoughWorks não alcançariam o Adopt Ring em seu Technology Radar. Conforme relatado, "uma das principais razões para isso é que muitas empresas simplesmente não estão prontas para microservices, faltando algumas práticas fundamentais em torno de operações e automação".

Além disso, como Jan relatou em outro artigo sobre falhas com microservices alguns anos atrás, Richard Clayton, engenheiro chefe de software da Berico Technologies, sugeriu um problema que eles tiveram na época:

Equilibrar o desejo de compartilhar o código comum entre serviços contra o de serviços independentes com funcionalidade replicada tornou-se uma grande desvantagem, finalmente, levando a uma grande refatoração.

De volta ao artigo original, tem havido muita discussão em outros lugares sobre o tema, incluindo no Hacker News e no Reddit; com vários deles, sugerindo que as preocupações em torno de outras áreas além dos microservices podem ter sido a causa. Por exemplo, alguns comentários apontam que não há referência ao CI no artigo original de Noonan, só ao CD, que é uma combinação ímpar, pelo menos. Um outro comentário sugeriu que talvez os problemas não fossem específicos aos microservices, mas de sistemas distribuídos em geral, sobre os quais já falamos antes, referindo-se a uma experiência semelhante com a SOA:

Eu trabalhei em uma base de código como aquela quando era chamada SOA, e antes da nuvem. Cada chamada para um serviço iniciaria uma instância completa do serviço, chamaria um método e encerraria a instância. Acho que precisamos tornar a latência da rede um elemento obrigatório dos diagramas de arquitetura.

Curiosamente, muitos dos comentários discutem problemas com dados no contexto de microservices, algo que já cobrimos muitas vezes em outros lugares e é uma fonte comum de problemas, bem como divergências. Como um comentário no Hacker News:

É pior que isso; e minha observação é que a maioria das arquiteturas de microservices simplesmente ignoram completamente a consistência ("não precisamos de transações mal cuidadas!") e seguem cegamente o caminho feliz. Eu nunca entendi muito bem por que as pessoas acham que usar módulos de software e separá-los por uma conexão de rede lenta e não confiável com o tedioso processamento REST com conexão manual deve, de alguma forma, melhorar a arquitetura. Eu acho que é uma daquelas coisas que dá a ilusão de produtividade: "Fiz todo esse trabalho, e agora tenho essa função que nem é tão útil assim rodando como um serviço! Olhe para a pequena luz de status verde nesse painel bacana que passamos os últimos dois meses construindo! "

Além disso, a definição de domínios para microservices é algo que levantamos ao longo dos anos como sendo importante para implantações bem-sucedidas de microservices. Na verdade, houve uma apresentação sobre o uso do DDD para desconstruir monólitos e isso pode ser relevante para outra coisa discutida no tópico do Reddit:

Criar uma boa arquitetura de microservices é difícil - e tenho a tendência de pensar que é tudo uma questão de segregar corretamente seus domínios com sucesso e reavaliar esse aspecto de forma consistente quando o sistema evolui. Apesar do nome, os microservices não precisam ser pequenos, mas sim cumprir certas características da arquitetura - essa é a maior armadilha que a maioria parece cair.

E você, o que acha? Por exemplo, a arquitetura de microservices da Segment teve problemas que poderiam ter sido resolvidos de outras maneiras sem ter que retornar a uma abordagem baseada em monolito? Ou a arquitetura original baseada em monólitos poderia ter evoluído para acomodar melhor suas necessidades crescentes sem a introdução de microservices?

Esta notícia foi atualizada em 16 de julho de 2018 com detalhes da Hacker News e Reddit.

Avalie esse artigo

Relevância
Estilo/Redação

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

Dê sua opinião

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão
Comentários da comunidade

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Receber mensagens dessa discussão

Dê sua opinião
BT