- Foi verificado em experimento que os programas CGI, muito usados no início da era web, ainda podem entregar alto desempenho em hardware moderno
- O CGI processa requisições por processo, o que traz gerenciamento automático de memória e a vantagem de uma implantação simples
- Os resultados de benchmark comprovaram que até mesmo em um servidor comum com CPU de 16 threads é possível processar mais de 2.400 requisições por segundo, ou mais de 200 milhões por dia
- O código de exemplo guestbook.cgi, escrito em Go e SQLite, além do Dockerfile, foi publicado como open source
- Embora CGI não seja mais algo comum hoje, o experimento mostra que ele ainda pode ser uma alternativa prática e moderna
Programas CGI e como funcionam
- No começo dos anos 2000, os programas CGI (Common Gateway Interface) eram a principal forma de construir sites dinâmicos
- A maioria era escrita em Perl ou C, e em alguns casos C era escolhido para melhorar o desempenho
- O conceito de CGI é simples, mas poderoso
- O servidor web define nos ambientes os metadados da requisição (headers HTTP, query etc.)
- Cria um processo separado para executar o programa CGI
- Envia o corpo da requisição para o
stdin
- Captura o
stdout do programa como resposta HTTP
- Envia a saída de
stderr para o log de erros do servidor
- Quando o programa termina a requisição, o processo é encerrado e descritores de arquivo e memória são liberados automaticamente
- Do ponto de vista do desenvolvedor, implantar uma nova versão também era muito simples: bastava copiar o arquivo para o diretório
cgi-bin/ e a implantação estava concluída
Hug of death (explosão de tráfego)
- No começo dos anos 2000, a maioria dos servidores web rodava em ambientes com 1~2 CPUs e 1~4 GB de memória
- Como o servidor web Apache tinha uma estrutura em que um processo
httpd fazia fork a cada conexão, a necessidade de memória aumentava quando havia muitas conexões
- Era difícil passar de 100 conexões simultâneas, e bastava um link em um site famoso para o servidor entrar facilmente em sobrecarga
- (Slashdot Effect: quando um link aparecia no então famoso Slashdot, o tráfego disparava. É algo parecido com chegar ao topo do Hacker News hoje)
CGI em ambientes de servidores modernos
- Hoje já existem servidores com 384 threads de CPU, e até VMs relativamente pequenas podem oferecer 16 CPUs
- O desempenho de CPU e memória melhorou muito
- Como os programas CGI são baseados em processos separados, eles conseguem aproveitar múltiplos núcleos de forma natural
- Por isso, foi feito um benchmark direto para medir quão rápidos os programas CGI podem ser em hardware moderno
- O experimento foi executado em um servidor com AMD 3700X (16 threads)
Principais resultados do benchmark
- Um programa CGI simples foi testado tanto em ambiente Apache quanto em um servidor Go
net/http
-
Descrição do programa guestbook.cgi
- Usando a ferramenta de geração de carga HTTP
plow, foram executadas 100 mil requisições com 16 conexões
- Mesmo em hardware comum, foi possível processar mais de 2.400 requisições por segundo, ou seja, mais de 200 milhões de requisições por dia
- CGI não é mais a abordagem dominante hoje, mas ainda pode ser usado em operação real de serviços
-
Benchmark de escrita no ambiente Apache
- Processamento de cerca de 2.468 requisições por segundo, com latência média de resposta de 6,47 ms
- 100 mil requisições POST foram processadas em apenas 40,5 segundos
- A maioria das requisições respondeu em até 7 ms, e apenas uma fração mínima passou de 100 ms
- Na prática, isso comprova um alto desempenho de escrita
-
Benchmark de leitura no ambiente Apache
- Processamento de cerca de 1.959 requisições por segundo, com latência média de resposta de 8,16 ms
- 100 mil requisições GET foram processadas em 51 segundos
- Mais da metade das requisições ficou dentro de 8 ms, e a latência máxima foi de apenas 31 ms
- O desempenho de leitura também é suficientemente bom
-
Benchmark de escrita no ambiente Go net/http
- Processamento de cerca de 2.742 requisições por segundo, com latência média de resposta de 5,83 ms
- 100 mil requisições POST foram processadas em apenas 36,4 segundos
- O throughput médio foi de 2.742 RPS, com latência média de 5,8 ms, mostrando números melhores que no Apache
- Mais de 95% das requisições foram processadas em até 6 ms
- O CGI em ambiente Go também mostra desempenho suficiente para uso real
-
Benchmark de leitura no ambiente Go net/http
- Processamento de cerca de 2.469 requisições por segundo, com latência média de resposta de 6,47 ms
- 100 mil requisições GET foram processadas em 40,4 segundos
- A maioria das requisições pode ser atendida em até 7 ms
- Tanto o throughput de leitura quanto a velocidade de resposta são semelhantes ou superiores aos do Apache
Conclusão e links
- Os programas CGI oferecem vantagens como concorrência muito rápida em hardware moderno, implantação simples e liberação automática de recursos pelo sistema operacional
- Em comparação com frameworks modernos, são extremamente simples, mas ainda podem ser usados na prática em serviços de certo porte
- O exemplo do livro de visitas e os dados do experimento de benchmark estão disponíveis no GitHub abaixo
https://github.com/Jacob2161/cgi-bin
4 comentários
Nossa... será que vamos voltar a usar CGI?? haha
Uau... faz quanto tempo que eu não ouvia falar de CGI...
Há conteúdo atualizado em 7/7.
Serving a half billion requests per day with Rust + CGI
500 milhões de requisições...
Opiniões do Hacker News
Lembrança de um ambiente em que programas CGI escritos em C mostravam velocidades realmente altas já nos anos 1990, embora se reconheça que a desvantagem era a grande quantidade de erros; programas modernos em linguagens como Go ou Nim, mencionados no artigo, desde que não façam conexão com banco de dados, parecem extremamente rápidos e com baixa latência em localhost, como usar fork & exec em um utilitário de CLI; comparado à latência de rede, o custo era quase desprezível
Menção também a uma cultura que tende a se viciar em certas tecnologias; por exemplo, depois que a pessoa se acostuma com linguagens com alto custo de inicialização, como o interpretador Python, passa a sentir necessidade de um modelo multishot ou persistente
O modelo one-shot do HTTP inicial surgiu do problema de falta de memória para que servidores FTP mantivessem por muito tempo centenas de sessões de login ociosas
Menção à possibilidade de um excelente desenho de sistema ao combinar pre-forking no CGI (que pode esconder a latência) com uma linguagem segura como Rust; destaque para a conveniência de deixar a terminação TLS a cargo de um servidor web multithread (ou de uma camada como o CloudFront)
fork()e os riscos do C; agora seria possível voltar à simplicidadeExperiência de ter começado a desenvolver na época do CGI e, por isso, ter adquirido uma forte aversão a rodar subprocessos de vida curta
Explicação de que PHP e FastCGI surgiram para escapar do problema de desempenho de criar um novo processo a cada requisição web
Com a evolução recente do hardware, percebeu-se a realidade de que o custo de inicialização de processo não é, na prática, um grande problema
Menção a que este benchmark consegue processar 2.000 requisições por segundo e que, mesmo processando apenas algumas centenas, é fácil escalar com várias instâncias no ambiente moderno
Concordância com a opinião de que o AWS Lambda é um renascimento do modelo CGI; a analogia parece bastante apropriada
Comentário de que a decepção teria sido menor se os scripts CGI tivessem sido distribuídos como binários C com linkagem estática, com preocupação até com o tamanho
O CGI não era muito pesado em custo financeiro nem em desempenho em ambientes de baixa carga
Situação dos anos 2000, antes da chegada do Go, em que criar programas CGI em C/C++ era difícil tanto em segurança quanto em desenvolvimento
Hoje em dia há servidores com 384 threads de CPU, e até uma VM pequena pode ter 16 CPUs
Em um hardware assim, desenvolver com Kestrel permitiria lidar tranquilamente até com trilhões de requisições por dia
É possível oferecer uma experiência de desenvolvimento parecida com a do PHP usando operador de interpolação de string
Com LINQ e
String.Join(), é fácil criar templates para tabelas HTML e elementos aninhadosA parte realmente difícil é saber como evitar bem o campo minado do ecossistema como MVC/Blazor/EF
Também dá para executar o programa inteiro como um único arquivo de nível superior pela CLI, mas, se não souber a palavra-chave "Minimal APIs", é fácil cair no labirinto da documentação errada
A vantagem do CGI é que, em um ambiente multitenant, não há necessidade de reconstruir primitivas de isolamento
rlimitcgroup, é possível alocar de forma justa o uso de memória, CPU e IO de disco/rede por tenantFoi por causa dos scripts CGI que perl foi otimizado para tempo de inicialização rápido
Ao executar o comando
time perl -e '', vê-se que perl leva 5ms, python3 33ms e ruby 77ms, confirmando o início rápido do perl#!/bin/tcc -rundo branch mob do tcc são 1,3x mais rápidos que perlAo usar apache tomcat 11, basta subir via ssh um arquivo .jsp ou uma aplicação java servlet inteira (.war) para tudo funcionar normalmente
Desempenho máximo garantido por uma única JVM compartilhada
Também é possível compartilhar pool de conexões com DB, caches etc. entre aplicações
Uma experiência realmente impressionante
Isso depende do padrão real de uso
Para serviços grandes é excelente, mas, se for preciso manter 50 pequenas aplicações processando apenas algumas centenas de acessos por dia cada, o overhead de memória do Tomcat é grande demais em comparação com Apache/Nginx baseados em scripts CGI
Um comentário nostálgico de saudade da época em que bastava copiar um arquivo para fazer deploy
Lamento sobre por que o processo de deploy ficou tão complicado
Compartilhamento da experiência de que ainda hoje se usa Jetty com satisfação para webapps de backend
Impressão de que a stack Tomcat/Jakarta EE/JSP é surpreendentemente bastante sólida
Dá para misturar HTML e código como no PHP, e também é possível usar rotas em Java puro
Suporte a WebSockets e modelo single-process multithread, o que também é uma vantagem para comunicação em tempo real
Se necessário, é possível compartilhar dados entre requisições, enquanto o código JSP fica limitado por padrão ao escopo da requisição
O deploy é realmente fácil: basta enviar um novo arquivo ao diretório webapps, e o Tomcat carrega automaticamente o novo app e descarrega o antigo
Como desvantagem, há o problema de vazamento de classloader, que pode impedir a coleta de lixo, um destino típico do modelo de processo único
Criação da ferramenta de visualização de requisições do apache ibrahimdiallo.com/reqvis
Havia suspeita em relação ao caminho atual rumo a arquiteturas complexas; menção à possibilidade de que, na prática, tecnologias antigas ainda bastem com um bom hardware
Diante da pergunta sobre como projetar um sistema que informe cotações em tempo real para milhões de pessoas, a primeira ideia foi uma estrutura complexa de streams com Kafka, pubsub etc., mas no fim também se considerou um método simples de deixar arquivos estáticos no servidor
Curiosidade sobre o custo real de operar algo assim
Destaque para o fato de que isso se parece com arquitetura serverless, mas é muito mais simples e barato
Lamento por terem simplesmente criado um novo paradigma chamado "serverless functions" sem reconsiderar essas estruturas tradicionais
Até CGI tudo bem, mas a reação ao JSP é surpreendente mesmo kkk
Será que o JSP já virou uma relíquia tão antiga assim?