Arquitetura real · Pleno
Segment: adeus, microsserviços
Arquitetura real em produção. Pontos centrais: Microsserviços vs Monólito e Filas de Mensagens.
Resposta curta
Arquitetura real em produção. Pontos centrais: Microsserviços vs Monólito e Filas de Mensagens. A infraestrutura de dados de clientes do Segment recebe eventos, objetos JSON gerados por apps web e mobile com informações sobre usuários e suas ações, e os repassa a APIs de parceiros, chamadas de destinos.
01 · CONTEXTO
O que o pipeline do Segment faz
A infraestrutura de dados de clientes do Segment recebe eventos, objetos JSON gerados por apps web e mobile com informações sobre usuários e suas ações, e os repassa a APIs de parceiros, chamadas de destinos. Segundo o time, são centenas de milhares de eventos por segundo e mais de cem tipos de destinos.
No início a arquitetura era simples: uma API recebia os eventos e os publicava em uma fila de mensagens distribuída. Ao consumir da fila, as configurações do cliente decidiam quais destinos recebiam cada evento, e o evento era então enviado a cada um deles. Quem usava o produto só precisava enviar o evento a um endpoint, em vez de construir várias integrações.
Quem usa o Segment envia o evento a um único endpoint, em vez de construir uma integração para cada destino.
A API de ingestão publica o evento em uma fila de mensagens distribuída.
Ao consumir da fila, as configurações do cliente são consultadas para saber quais destinos devem receber o evento.
O evento é enviado à API de cada destino selecionado.
Quando uma entrega falha, o sistema às vezes tenta de novo mais tarde, mas nem toda falha merece nova tentativa. Erros que o destino poderia aceitar sem mudança no evento, como HTTP 500, limite de taxa e timeout, são repetíveis. Erros como credenciais inválidas ou campos obrigatórios ausentes nunca serão aceitos e não são repetíveis.
02 · O PROBLEMA DA FILA ÚNICA
Um destino lento atrasa todos
Nessa versão, uma única fila continha tanto os eventos mais novos quanto os que já tinham passado por várias tentativas, de todos os destinos. O resultado foi head-of-line blocking: se um destino ficava lento ou caía, os retries inundavam a fila e atrasavam todos os destinos.
Imagine que um destino passa a responder com timeout a todas as requisições.
Cada evento que falha volta para a fila para uma nova tentativa, e forma-se um grande backlog.
O aumento súbito da fila supera a capacidade de escalar, e os eventos mais novos sofrem atraso.
Os tempos de entrega de todos os destinos aumentam por causa do problema de um só.
O time descreve que os sistemas escalavam automaticamente com o aumento de carga, mas o aumento súbito da profundidade da fila superava a capacidade de escalar. Como os clientes dependem da pontualidade da entrega, o aumento de espera em qualquer ponto do pipeline não era aceitável.
03 · A SOLUÇÃO MICROSSERVIÇOS
Isolar cada destino funcionou, e depois cobrou
Para resolver o bloqueio, o time criou um serviço e uma fila para cada destino. Um processo roteador recebe os eventos de entrada e distribui uma cópia a cada destino selecionado. Assim, só a fila do destino com problema acumula, e os demais não são afetados. Essa arquitetura em estilo de microsserviços, segundo o time, isolou os destinos uns dos outros, o que era crucial quando um destino tinha problemas.
Um roteador recebe os eventos de entrada.
O roteador distribui uma cópia do evento para a fila de cada destino selecionado.
O destino X passa a ter problemas, e as entregas do serviço dele falham.
Só a fila do destino X acumula; as filas dos outros destinos não são afetadas.
Cada API de destino usa um formato de requisição próprio, então cada destino precisa de código para traduzir o evento. Inicialmente todo esse código ficava em um repositório e um teste quebrado fazia falhar os testes de todos os destinos. Em resposta, o código de cada destino foi para o seu repositório.
Com o crescimento, vieram mais de 50 novos destinos e mais de 50 repositórios, e o time criou bibliotecas compartilhadas para transformações e funcionalidades comuns, como tratamento de requisições HTTP. Só que testar e implantar mudanças nessas bibliotecas impactava todos os destinos, e as versões delas passaram a divergir entre as bases de código.
Havia ainda o problema de carga: alguns serviços tratavam poucos eventos por dia e outros milhares por segundo, e cada um precisava de uma combinação própria de CPU e memória, o que tornava o ajuste do autoscaling mais arte que ciência. O time adicionava em média três destinos por mês, e o custo operacional crescia linearmente com cada destino.
04 · CONSOLIDAÇÃO
De mais de 140 serviços para um
O primeiro item da lista foi consolidar os mais de 140 serviços em um único serviço. O custo de gerenciá-los era um grande imposto sobre o time, e era comum o engenheiro de plantão ser acionado por picos de carga.
Havia um obstáculo: com uma fila por destino, cada worker teria de checar todas as filas, uma complexidade que o time não queria. Isso inspirou o Centrifuge, que substituiria as filas individuais e enviaria os eventos ao serviço único.
No monorepo, o time se comprometeu com uma só versão para cada uma das 120 dependências distintas, atualizando cada destino ao movê-lo. Segundo o time, foi preciso uma suíte de testes sólida para colocar tudo em um repositório.
O motivo original da separação em repositórios era isolar falhas de teste, mas isso se mostrou uma falsa vantagem. A causa principal das falhas eram as requisições HTTP aos endpoints dos destinos durante os testes, e alguns destinos levavam até 5 minutos para rodar os seus. Com a suíte completa podendo levar até uma hora, o time criou o Traffic Recorder: na primeira execução ele grava requisições e respostas em arquivo e, nas seguintes, as reproduz. Depois disso, os testes de todos os mais de 140 destinos rodavam em milissegundos.
O código de todos os destinos passa a viver em um só serviço, com uma só versão de cada dependência.
O Centrifuge substitui as filas individuais e envia os eventos ao serviço único.
O serviço entrega o evento a cada destino a partir de um grande pool de workers.
Em contrapartida, um bug em um destino que derrube o serviço derruba todos os destinos.
05 · RESULTADO E TROCAS
O que o monólito deu e o que cobrou
Com tudo em um serviço, um engenheiro consegue implantar em questão de minutos, e não é mais preciso implantar mais de 140 serviços para mudar uma biblioteca compartilhada. Como medida de velocidade, o time cita 32 melhorias nas bibliotecas compartilhadas com os microsserviços e 46 um ano depois da mudança.
Na operação, a mistura de destinos intensivos em CPU e em memória facilitou o escalonamento, e o grande pool de workers absorve picos de carga, de modo que o time deixou de ser acionado por destinos de pouco volume.
| Ponto | Um serviço por destino | Monólito de destinos |
|---|---|---|
| Falha de um destino | Só a fila do destino com problema acumula; os demais não são afetados. | Um bug que derrube o serviço derruba todos os destinos. |
| Cache em memória | Destinos de pouco tráfego tinham poucos processos, e o cache em memória de dados do control plane ficava quente. | O cache se espalha por milhares de processos e acerta bem menos. |
| Dependências | As versões das bibliotecas divergiam entre as bases de código. | Uma só versão de cada dependência; atualizar pode quebrar vários destinos. |
| Escala | Cada serviço tinha uma combinação própria de CPU e memória, e o ajuste do autoscaling era mais arte que ciência. | Há uma boa mistura de destinos intensivos em CPU e em memória, e o pool grande absorve picos. |
Antes de dividir um sistema em serviços, vale perguntar quem vai pagar o custo operacional de cada serviço adicional, e se a equipe tem as ferramentas de teste e implantação para mudanças em lote.
Definições usadas pelo texto: microsserviços são aplicações de servidor construídas combinando muitos serviços de rede pequenos e de propósito único; num monólito, muita funcionalidade vive em um único serviço, testado, implantado e escalado como uma unidade.
Perguntas frequentes
O que é Segment: adeus, microsserviços?
Arquitetura real em produção. Pontos centrais: Microsserviços vs Monólito e Filas de Mensagens.
O que o design de Segment: adeus, microsserviços cobre?
Ele está organizado nestas seções: O que o pipeline do Segment faz, Um destino lento atrasa todos, Isolar cada destino funcionou, e depois cobrou, De mais de 140 serviços para um e O que o monólito deu e o que cobrou.
Quanto tempo leva Segment: adeus, microsserviços e qual é o nível?
Leva cerca de 15 minutos para ler e tem nível Pleno.
Agora projete você mesmo
Rode este caso como uma entrevista na escola de System Design: você toma as decisões, o tutor as questiona e você vê a nota do seu desenho.