- Para criar um servidor web de alto desempenho, tradicionalmente eram usados vários modelos baseados em eventos, como select(), poll() e epoll
- Porém, devido aos limites de desempenho dessas chamadas de sistema, surgiu o io_uring, que introduz uma abordagem em que as requisições são colocadas em uma fila para o kernel processá-las de forma assíncrona
- O kTLS coloca o kernel como responsável pelo processamento da criptografia TLS, permitindo otimizações adicionais como uso de sendfile() e offloading por hardware
- A introdução de Descriptorless files oferece uma forma de acesso otimizada para o io_uring sem repassar diretamente descritores de arquivo
- Por meio do projeto open source tarweb, que combina Rust, io_uring e kTLS, é possível oferecer HTTPS sem chamadas de sistema adicionais por requisição, além de discutir questões de segurança e gerenciamento de memória
A evolução da arquitetura de servidores web de alto desempenho
- Desde o início dos anos 2000, cresceu a demanda por servidores web de alta capacidade
- No começo, era comum criar um novo processo para cada requisição, mas, devido ao alto custo, surgiu a técnica de preforking
- Depois vieram as threads e o uso de select() e poll(), evoluindo para uma forma de reduzir o custo de troca de contexto
- Ainda assim, select() e poll() têm limites de escalabilidade, porque, à medida que o número de conexões cresce, é necessário passar com frequência grandes arrays ao kernel
O surgimento do epoll
- No ambiente Linux, o epoll foi introduzido para permitir um tratamento de múltiplas conexões mais eficiente do que os métodos anteriores
- O epoll processa apenas as mudanças (delta), reduzindo o consumo desnecessário de recursos
- Nem todas as chamadas de sistema desaparecem completamente, mas o custo cai de forma significativa
Visão geral do io_uring
- O io_uring adiciona requisições a filas em memória para que o kernel possa processá-las de forma assíncrona, em vez de fazer uma chamada de sistema a cada solicitação
- Por exemplo, se
accept() for colocado na fila, o kernel processa a operação e devolve o resultado na fila de conclusão
- O servidor web funciona adicionando solicitações à fila e verificando os resultados em uma região de memória separada
- Para evitar um busy loop, quando não há mudanças na fila, tanto o servidor web quanto o kernel chamam chamadas de sistema apenas quando necessário, economizando energia
- Com bibliotecas adequadas, um servidor ativo pode operar sem chamadas de sistema adicionais durante o processamento das requisições
Ambiente multicore e NUMA
- Considerando o ambiente multicore dos CPUs modernos, uma estratégia eficaz é executar uma única thread por núcleo e minimizar o compartilhamento de estruturas de dados
- Em ambientes NUMA, cada thread pode ser otimizada para acessar apenas a memória do seu nó local
- O balanceamento perfeito da distribuição de requisições ainda exige mais pesquisa
Alocação de memória
- A alocação de memória continua existindo tanto no kernel quanto no servidor web, e as alocações no espaço do usuário acabam se conectando a chamadas de sistema
- Do lado do servidor web, blocos de memória de tamanho fixo podem ser pré-alocados por conexão para evitar fragmentação e falta de memória
- Do lado do kernel, também são necessários buffers de entrada e saída por conexão, com algum ajuste possível por meio de opções de socket
- Quando ocorre falta de memória, isso pode levar a falhas graves
Introdução ao kTLS (TLS no kernel)
- O kTLS é um recurso em que o kernel Linux assume as operações de criptografia e descriptografia
- O handshake é tratado pela aplicação, mas, depois disso, o kernel passa a tratar a transmissão dos dados como se fossem texto puro
- Isso permite usar
sendfile(), reduzindo cópias de memória entre o espaço do usuário e o do kernel
- Se a placa de rede der suporte, há ainda a vantagem de fazer offloading das operações de criptografia para o hardware
Descriptorless Files
- Essa abordagem surgiu para reduzir o overhead gerado ao repassar diretamente descritores de arquivo do espaço do usuário para o espaço do kernel
- Com
register_files, usa-se um número de arquivo separado, válido apenas para o io_uring, que não aparece em /proc/pid/fd
- O limite de
ulimit do sistema ainda se aplica
Introdução ao projeto tarweb
- O tarweb é um projeto open source de servidor web que aplica todas as tecnologias acima como exemplo
- Ele serve o conteúdo de um único arquivo tar, combinando tecnologias modernas de alto desempenho como Rust, io_uring e kTLS
- No uso prático, houve problemas de compatibilidade entre io_uring e kTLS (como falta de suporte a
setsockopt), e algumas dessas questões foram resolvidas por meio de Pull Requests
- O projeto ainda está incompleto, e a biblioteca rustls do Rust pode fazer alocação de memória durante o processo de handshake
- O ponto principal é que é possível oferecer serviço HTTPS sem chamadas de sistema adicionais por requisição
Benchmark e medição de desempenho
- O autor ainda não realizou benchmarks suficientes e pretende testar o desempenho depois de organizar melhor o código
Problemas de segurança em io_uring e Rust
- Diferentemente das chamadas de sistema síncronas, no io_uring os buffers de memória não podem ser liberados antes do evento de conclusão
- O crate io-uring não garante a segurança em tempo de compilação do Rust, e as verificações em tempo de execução também são insuficientes
- Se usado incorretamente, isso pode causar problemas graves semelhantes aos de C++, enfraquecendo a segurança inerente do Rust
- É necessário um crate separado, como o safer-ring, que aproveite ativamente pinning e o borrow checker
- Esse tema já está sendo discutido na comunidade
Referências e links adicionais
- Este conteúdo é um post discutido no HackerNews em 2025-08-22
Ainda não há comentários.