Pular para o conteúdo

Arquitetura real · Sênior

Fury Streams: distribuir permits para não derrubar o destino

Exercício clássico com contexto real: um destino de 100k RPM e mais de 800 instâncias efêmeras. A divisão igual de permits processa só 70k; a segunda iteração distribui por uso real.

17 min de leitura · Fonte: Mercado Livre · Conteúdo revisado em 23 de outubro de 2026

Resposta curta

Exercício clássico com contexto real: um destino de 100k RPM e mais de 800 instâncias efêmeras. A divisão igual de permits processa só 70k; a segunda iteração distribui por uso real. O serviço de streams da plataforma Fury, uma ferramenta interna do Mercado Libre para desenvolvedores, transporta informação em tempo real de muitas origens para destinos como filas, armazenamento chave-valor…

01 · CONTEXTO

Um destino que não escala

O serviço de streams da plataforma Fury, uma ferramenta interna do Mercado Libre para desenvolvedores, transporta informação em tempo real de muitas origens para destinos como filas, armazenamento chave-valor e bancos relacionais. O relato fala de mais de 30 milhões de mensagens, vindas de mais de 12 mil origens e ligadas a mais de 15 mil destinos.

O problema nasce de um desenho comum: a aplicação processa informação e entrega o resultado a um serviço complementar, como um banco ou uma aplicação externa. Diante de um pico, ela opta por escalar horizontalmente. O desafio aparece quando o volume passa da capacidade do destino, que pode ser um banco herdado ou uma aplicação externa fora do controle do time, e então escalar o destino não é viável.

  1. Várias instâncias da aplicação processam informação e enviam o resultado a um serviço complementar.

    Diagrama da arquitetura: Streams: instâncias e um destino limitado
    CPInstância A
    CPInstância B
    CPInstância nova
    DBDestino com capacidade fixa
  2. O destino tem uma capacidade que não dá para ampliar escalando horizontalmente.

    Diagrama da arquitetura: Streams: instâncias e um destino limitado
    CPInstância A
    CPInstância B
    CPInstância nova
    DBDestino com capacidade fixacapacidade fixa
  3. Quando o volume somado das instâncias passa da capacidade do destino, o limite é ultrapassado, e o número de instâncias flutua o tempo todo.

    Diagrama da arquitetura: Streams: instâncias e um destino limitado
    CPInstância A
    CPInstância B
    CPInstância novainstância nova
    DBDestino com capacidade fixacapacidade em jogo

A pergunta do exercício é: se o destino aguenta 100 mil requisições por minuto, como a aplicação mantém o total abaixo disso com um número de instâncias que muda? No serviço real, a entrega saía de mais de 800 instâncias que flutuavam constantemente.

Ao descrever o impacto do problema no time de Streams, o relato menciona cerca de 600 alertas nos últimos meses de 2023, cada um exigindo em média 30 minutos de atenção de uma pessoa.

02 · PRIMEIRA ITERAÇÃO

Dividir os permits por igual

Na primeira iteração, o time equilibrou a carga por instância levando em conta quantas estavam ativas, e dividiu o total de permits de forma igual. Com um destino de 100 mil requisições por minuto e dez instâncias ativas, cada uma recebia 10 mil permits.

O sistema tinha três peças: uma fila de mensagens, um banco e uma aplicação adicional que compila e guarda os dados, o worker-balancer. Cada vez que uma instância nova começava a processar, enviava um sinal de vida pela fila. O sinal era gravado no banco com um tempo de vida (TTL) limitado. Depois, a aplicação worker consultava o balancer para saber quantas instâncias estavam vivas e repartia os permits proporcionalmente.

  1. Cada instância que começa a processar envia um sinal de vida pela fila de mensagens.

    Diagrama da arquitetura: Streams: permits divididos por igual
    CPInstância A
    CPInstância B
    QFila de sinais de vidasinais de vida
    CPworker-balancer
    DBBanco de instâncias vivas
    DBDestino com capacidade fixa
  2. O worker-balancer compila os sinais e os grava no banco com um TTL limitado.

    Diagrama da arquitetura: Streams: permits divididos por igual
    CPInstância A
    CPInstância B
    QFila de sinais de vida
    CPworker-balancercompila os sinais
    DBBanco de instâncias vivassinais com validade
    DBDestino com capacidade fixa
  3. A aplicação worker consulta o balancer para saber quantas instâncias estão vivas e reparte os permits proporcionalmente.

    Diagrama da arquitetura: Streams: permits divididos por igual
    CPInstância A
    CPInstância B
    QFila de sinais de vida
    CPworker-balancer
    DBBanco de instâncias vivas
    DBDestino com capacidade fixa
  4. Com a divisão repartida, o destino não é sobrecarregado por pedidos em excesso.

    Diagrama da arquitetura: Streams: permits divididos por igual
    CPInstância A
    CPInstância B
    QFila de sinais de vida
    CPworker-balancer
    DBBanco de instâncias vivas
    DBDestino com capacidade fixaprotegido

O ponto fraco é a premissa: a estratégia supunha que toda instância executaria a mesma carga de processamento, o que nem sempre acontece. O relato aponta o desempenho do load balancer e a distribuição do tráfego como fontes de disparidade, e lembra que tarefas diferentes custam tempos diferentes: uma nota fiscal com milhares de linhas leva mais tempo que uma com poucas.

O exemplo numérico do relato mostra o efeito. Com 100 mil requisições por minuto e duas instâncias, A e B, cada uma recebe 50 mil. Se A só processa 20 mil e B processa 80 mil, o modelo igual deixa processar 70 mil no total, e 30 mil por minuto ficam sem processar, gerando atraso. O destino fica protegido, mas a capacidade da aplicação é subutilizada.

InstânciaPermits recebidosCapacidade realProcessado
Instância A50 mil RPM20 mil RPM20 mil RPM
Instância B50 mil RPM80 mil RPM50 mil RPM
Total100 mil RPM—70 mil RPM

03 · SEGUNDA ITERAÇÃO

Reportar o uso sem atrapalhar o trabalho

Com essas conclusões, o time reestruturou a aplicação para informar a disponibilidade de cada host e detalhes do estado de carga atual de cada um. O worker-balancer foi ajustado para analisar essas informações e funcionar como centro de distribuição da carga. Entre os dados enviados estão o identificador da instância, o instante e os permits designados.

Enviar informação sincronizada durante o trabalho não é prático nem possível, porque exigiria mais cálculo e atrapalharia o processamento. O time adotou então uma abordagem parecida com a de bibliotecas de monitoramento como DataDog ou NewRelic: uma estrutura de dados em memória, com fila assíncrona, que compila, consolida e envia as informações em uma thread assíncrona.

No fluxo, os detalhes de processamento e os permits se acumulam em um buffer em memória; uma thread dedicada comprime e esvazia o buffer a cada minuto; e a informação comprimida é despachada para a fila de mensagens.

  1. Cada instância acumula detalhes de processamento e permits em um buffer em memória.

    Diagrama da arquitetura: Streams: permits distribuídos pelo uso
    CPInstância Abuffer em memória
    CPInstância Bbuffer em memória
    QFila de dados de uso
    CPworker-balancer
    DBBanco de uso por instância
    DBArmazenamento de longo prazo
    DBDestino com capacidade fixa
  2. Uma thread dedicada comprime e esvazia o buffer a cada minuto e despacha o conteúdo para a fila de mensagens.

    Diagrama da arquitetura: Streams: permits distribuídos pelo uso
    CPInstância A
    CPInstância B
    QFila de dados de uso
    CPworker-balancer
    DBBanco de uso por instância
    DBArmazenamento de longo prazo
    DBDestino com capacidade fixa
  3. O balancer recebe da fila e faz um upsert em lote no banco, gravando conforme o instante de cada tupla, o que alivia a fila e elimina a dependência da ordem das mensagens.

    Diagrama da arquitetura: Streams: permits distribuídos pelo uso
    CPInstância A
    CPInstância B
    QFila de dados de uso
    CPworker-balancer
    DBBanco de uso por instânciauso persistido
    DBArmazenamento de longo prazo
    DBDestino com capacidade fixa
  4. O balancer avalia a distribuição a cada minuto: o excedente de permits de um host que usa menos é realocado a outro host que usa todos os seus.

    Diagrama da arquitetura: Streams: permits distribuídos pelo uso
    CPInstância A
    CPInstância B
    QFila de dados de uso
    CPworker-balancerrecalcula a distribuição
    DBBanco de uso por instância
    DBArmazenamento de longo prazo
    DBDestino com capacidade fixa
  5. A distribuição avaliada é guardada de forma persistente em armazenamento de longo prazo, o Amazon S3.

    Diagrama da arquitetura: Streams: permits distribuídos pelo uso
    CPInstância A
    CPInstância B
    QFila de dados de uso
    CPworker-balancer
    DBBanco de uso por instância
    DBArmazenamento de longo prazodistribuição guardada
    DBDestino com capacidade fixa
  6. Quando chega um pedido de distribuição de host, o arquivo é lido e o resultado volta ao cliente com tempo de resposta mínimo.

    Diagrama da arquitetura: Streams: permits distribuídos pelo uso
    CPInstância A
    CPInstância B
    QFila de dados de uso
    CPworker-balancer
    DBBanco de uso por instância
    DBArmazenamento de longo prazo
    DBDestino com capacidade fixa

04 · DISTRIBUIÇÃO E DISPONIBILIDADE

Guardar proporções e servir arquivos prontos

A distribuição guarda o percentual de permits de cada host, e não um valor absoluto, o que evita reprocessar tudo quando o número de permits muda. Com 100 mil requisições por minuto divididas em 80% para o host A e 20% para o B, a proporção se mantém mesmo se o total subir para 200 mil permits, com reflexo rápido nas instâncias.

O algoritmo é simples: se um host usa menos permits do que recebeu, o excedente é realocado a outro host que usa todos os seus. A avaliação roda minuto a minuto, o que permite ajustes contínuos até chegar à distribuição ideal.

Consultar os permits de cada instância a cada pedido seria lento, então o processamento da distribuição ocorre a cada minuto e o resultado é guardado no Amazon S3. Quando um pedido chega, o arquivo é lido e devolvido com tempo de resposta mínimo.

Na persistência, o balancer recebe os dados da fila e faz um UPSERT em lote, gravando só se o instante da tupla for menor que o do novo elemento. Isso alivia a fila e elimina a dependência da ordem das mensagens. Os elementos têm um TTL de três minutos, útil por exemplo quando um host para.

AspectoPrimeira iteraçãoSegunda iteração
O que a instância informaUm sinal de que está vivaIdentificador, instante e permits designados
Como os permits são repartidosPor igual entre as instâncias vivasO excedente de quem usa menos vai a quem usa tudo
Premissa sobre a cargaSupõe a mesma carga em toda instânciaDistribuição reavaliada minuto a minuto

05 · RESULTADO E LIMITES

O que mudou e o que levar para a entrevista

O relato apresenta a reestruturação como resultado de várias iterações, com uma queda de 95% nos alertas de lag e um ambiente mais amigável para quem desenvolve na plataforma.

O serviço Fury Streams aparece também em outro artigo da equipe, sobre a migração para virtual threads. Nele, o componente pusher lê dos streams do Kinesis e entrega a vários destinos, desacoplando os consumidores de qualquer tecnologia específica, e usa milhares de threads para paralelizar consumo e entrega. Para restringir tarefas concorrentes, o artigo sugere trocar o pool de threads de tamanho limitado por um método baseado em semáforo.

Os dois artigos tratam de limites em níveis diferentes: o semáforo restringe concorrência dentro de um processo, enquanto os permits distribuídos limitam a vazão contra o destino somando todas as instâncias.

Perguntas frequentes

O que é Fury Streams: distribuir permits para não derrubar o destino?

Exercício clássico com contexto real: um destino de 100k RPM e mais de 800 instâncias efêmeras. A divisão igual de permits processa só 70k; a segunda iteração distribui por uso real.

O que o design de Fury Streams: distribuir permits para não derrubar o destino cobre?

Ele está organizado nestas seções: Um destino que não escala, Dividir os permits por igual, Reportar o uso sem atrapalhar o trabalho, Guardar proporções e servir arquivos prontos e O que mudou e o que levar para a entrevista.

Quanto tempo leva Fury Streams: distribuir permits para não derrubar o destino e qual é o nível?

Leva cerca de 17 minutos para ler e tem nível Sênior.

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.