Tecnologia além do código.
Régys Borges da Silveira
← Voltar para explorar os artigos

Como Reduzir o Consumo de Memória e Otimizar a Performance do Horse com o Novo Memory Buffer Pool

Neste artigo 5 tópicos

No desenvolvimento de microsserviços e APIs web com Delphi e Lazarus, o Horse consolidou-se como a escolha número um devido à sua simplicidade, robustez e performance extrema. No entanto, em sistemas que processam milhares de requisições por segundo, pequenos detalhes arquiteturais podem se tornar gargalos de infraestrutura.

Recentemente, foi incorporada ao core do Horse uma melhoria estratégica muito solicitada para cenários de alta concorrência: a centralização do pool de buffers de memória (IMemoryBufferPool). Neste artigo, vamos entender qual problema essa evolução resolve debaixo do capô e como você pode, opcionalmente, extrair o máximo dela em seus próprios middlewares e rotas.

O Gargalo Invisível: Alocação Dinâmica de Heap

Toda vez que uma API recebe dados (Request Body) ou envia uma resposta (Response Body), o framework precisa manipular esses bytes. Tradicionalmente, isso envolve a criação de instâncias de TMemoryStream ou TBytesStream.

Em um cenário com alta concorrência multithread, a alocação e liberação contínua de blocos de memória física (o famoso Heap Churn via GetMem/FreeMem) gera dois problemas crônicos:

  1. Fragmentação de Memória: O gerenciador de memória do sistema operacional (ou o FastMM no Delphi) divide a RAM em milhares de pequenos pedaços vazios, elevando o consumo fixo do processo ao longo do tempo.
  2. Contenção de Lock: Como o gerenciador de memória possui travas globais (mutexes) para garantir a segurança entre as threads, várias requisições paralelas acabam disputando o mesmo “lock de heap” para alocar memória, causando pequenos travamentos imperceptíveis que reduzem a vazão (throughput) total da CPU.

A Solução: Reciclagem de Buffers com THorseMemoryBufferPool

Para resolver esse gargalo, a nova arquitetura do Horse introduz um pool thread-safe baseado em pilha de arrays de bytes reutilizáveis (THorseMemoryBufferPool) e uma subclasse customizada de stream de memória: o THorsePooledStream (que herda diretamente de TStream).

A mecânica agora opera da seguinte forma:

  • Ao ler o corpo de uma requisição ou gerar dados de resposta, o Horse **adquire um buffer já existente** do pool global.
  • Quando o processamento termina e o método .Free é acionado no stream, **o buffer de bytes subjacente é devolvido imediatamente ao pool** em vez de ser liberado de volta ao sistema operacional.
  • Segurança contra inflação de RAM (Capping): Se um cliente fizer o upload de um arquivo gigantesco que force o buffer a crescer demais (acima de 2MB), o pool descarta esse buffer no momento do descarte para evitar que a RAM do servidor fique inflada permanentemente.

Essa mudança resulta em um fluxo zero-allocation para a vasta maioria das conexões HTTP normais do servidor.

Como isso afeta meu código atual?

A resposta curta é: de forma alguma. A otimização foi projetada para ser 100% transparente. Toda a sua base de código existente, incluindo rotas clássicas como a abaixo, se beneficia automaticamente do pool de buffers sem quebrar a retrocompatibilidade:

THorse.Get('/ping',
  procedure(Req: THorseRequest; Res: THorseResponse)
  begin
    Res.Send('pong'); // Recicla buffers automaticamente sob o capô
  end);

O ganho de performance e a estabilização do consumo de RAM acontecem silenciosamente nos bastidores.

Uso Avançado: Otimizando seus próprios fluxos de I/O

Se você desenvolve middlewares customizados ou cria endpoints que lidam com geração de arquivos pesados (como relatórios PDF/Excel em tempo real ou streaming de mídia), você pode aproveitar o pool global para turbinar a sua própria lógica de I/O:

uses
  Horse,
  Horse.Core.MemoryBufferPool,
  System.Classes;

begin
  THorse.Get('/relatorio',
    procedure(Req: THorseRequest; Res: THorseResponse)
    var
      LStream: TStream;
    begin
      // Adquire um stream pré-alocado do pool global de forma thread-safe
      LStream := THorseMemoryBufferPool.DefaultPool.AcquireStream;
      try
        // Grava os dados da sua regra de negócio no stream reciclado
        LStream.WriteBuffer(PChar('Dados em tempo real...')^, 23);
        
        // Retorna o stream. O Horse enviará os dados e chamará LStream.Free,
        // devolvendo o buffer de bytes automaticamente para o pool global.
        Res.SendFile(LStream, 'relatorio.txt');
      except
        LStream.Free; // Garante a devolução em caso de falhas
        raise;
      end;
    end);

  THorse.Listen(9000);
end.

Conclusão

Ao centralizar e reutilizar recursos na camada de transporte de dados, o Horse dá um salto qualitativo de escalabilidade. A eliminação de gargalos no alocador de heap permite que o framework entregue ainda mais throughput em benchmarks e garanta uma operação extremamente estável em ambientes de nuvem e Kubernetes.

Essa melhoria já está disponível na ramificação principal do projeto. Atualize suas dependências via Boss e leve a performance de suas APIs Delphi/Lazarus para o próximo nível!

Compartilhar por e-mail

0 comentário(s)

Dê-nos sua opinião, seu comentário ajuda o site a crescer e melhorar a qualidade dos artigos.

Este site utiliza o Akismet para reduzir spam. Saiba como seus dados em comentários são processados.

Um próximo passo para cada interesse.

Continue estudando com os livros, acompanhe as videoaulas ou consulte as soluções para o seu projeto.

Descubra mais sobre Régys Borges da Silveira

Assine agora mesmo para continuar lendo e ter acesso ao arquivo completo.

Continuar lendo