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:
- 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.
- 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!
Descubra mais sobre Régys Borges da Silveira
Assine para receber nossas notícias mais recentes por e-mail.
Dê-nos sua opinião, seu comentário ajuda o site a crescer e melhorar a qualidade dos artigos.