7 pontos por GN⁺ 2025-07-07 | 4 comentários | Compartilhar no WhatsApp
  • 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

 
kansm 2025-07-09

Nossa... será que vamos voltar a usar CGI?? haha
Uau... faz quanto tempo que eu não ouvia falar de CGI...

 
tujuc 2025-07-08

Há conteúdo atualizado em 7/7.

Serving a half billion requests per day with Rust + CGI

500 milhões de requisições...

 
GN⁺ 2025-07-07
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)

      • Um ambiente sem estado residual, com core dumps e depuração muito fáceis, e que também pode escalar de forma simples principalmente com um modelo linear de requisições
      • Elogio à simplicidade de apenas ler de stdin e escrever em stdout; WebSockets aumentam um pouco a complexidade, mas não a ponto de preocupar
      • Recordação de como a ascensão do Java acelerou fortemente a transição para servidores de aplicação, para evitar o custo de fork() e os riscos do C; agora seria possível voltar à simplicidade
      • Apesar de não gostar de Rust, há expectativa de que, se chegar uma era em que esse tipo de código de backend web possa ser escrito facilmente, isso também será atraente para desenvolvedores de node/js, php e python
  • Experiê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 custo de inicialização de processo com linkagem dinâmica, incluindo carregamento do interpretador PHP, várias bibliotecas e parsing de arquivos, é muito maior
      • Convicção de que usar Go é uma abordagem que já poderia ter sido competitiva o bastante até 25 anos atrás
      • Destaque para o fato de que abrir um banco SQLite tem desempenho quase equivalente a passar um socket por context switch, e é muito mais rápido do que se conectar a um mysql remoto
      • Defesa de que o FastCGI continua sendo uma excelente escolha para novas aplicações
    • O CGI não era muito pesado em custo financeiro nem em desempenho em ambientes de baixa carga

      • Em situações de alta carga, processos persistentes como os do FastCGI são mais vantajosos
      • O CGI também consegue chegar a 2.000 rps, mas o FastCGI pode alcançar desempenho muito mais alto
      • Como basta adicionar um processo de servidor separado e reiniciá-lo na hora de atualizar, isso vale a pena quando desempenho é importante
    • 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

      • Perl e Python tinham custo considerável de inicialização do interpretador e compilação, e Java era, na prática, ainda mais lento
      • Concordância com a ideia de que AWS Lambda = reencarnação do modelo CGI
      • Sensação de que agora voltamos a um modelo quase idêntico ao do FastCGI gerenciado
      • Lamento diante da enxurrada de tecnologias que adicionam muita complexidade quando deveria bastar subir um executável e rodá-lo
  • 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 aninhados

    • A 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

      • Surpresa com a enorme quantidade de casos em que pessoas sobem para cargos de Director/VP apenas acrescentando camadas de abstração sobre a tecnologia central
  • A vantagem do CGI é que, em um ambiente multitenant, não há necessidade de reconstruir primitivas de isolamento

    • Mesmo que uma requisição tenha um bug, o isolamento entre processos impede impacto sobre outras requisições
    • Até loops infinitos não levam a negação de serviço (DoS), graças ao escalonamento preemptivo
    • É possível encerrar à força requisições demoradas com rlimit
    • Com cgroup, é possível alocar de forma justa o uso de memória, CPU e IO de disco/rede por tenant
    • Namespaces/jails e separação de privilégios permitem restringir as permissões de acesso por requisição
  • Foi 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

      • Menção de que scripts no estilo #!/bin/tcc -run do branch mob do tcc são 1,3x mais rápidos que perl
      • Casos como Julia, Java VM e thread PHP também mostram tempos de inicialização muito longos
      • Fenômeno de as pessoas passarem a depender por hábito de "grandes ambientes"
      • Isso também se repete na comunidade Lisp com o uso de imagens, e o meme "emacs is bloated" nasceu daí
      • A sensação é de que o auge do Perl em meados e no fim dos anos 1990 só foi realmente possível graças ao CGI
      • Recordação de uma época em que nem mesmo getline era padrão, então bibliotecas C de terceiros eram feitas com algumas centenas ou milhares de linhas
      • No fim, a tecnologia é escolhida pela "reputação", e a maior parte do aprendizado acontece por recomendação de amigos
  • Ao 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

    • Melhor experiência em navegador desktop
    • Com base em dados de tráfego do HN, é possível verificar na web o fluxo real de funcionamento
  • 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

      • Na prática, a latência de quase toda web API é determinada por consultas ao banco de dados ou a modelos de ML
      • O resto do processo é pouco relevante, mesmo usando uma linguagem lenta como Python
      • Se só forem retornados dados que mudam raramente, é fácil chegar até o limite da NIC
  • Destaque para o fato de que isso se parece com arquitetura serverless, mas é muito mais simples e barato

    • Curiosidade se há casos reais de uso desse tipo em ambientes de negócio
  • Lamento por terem simplesmente criado um novo paradigma chamado "serverless functions" sem reconsiderar essas estruturas tradicionais

    • Embora funções serverless como Lambda tenham mecanismos extras de proteção (microVMs etc.), pensa-se que, na prática, só com CGI e ajuste de permissões já teria sido possível ir muito longe com bem menos complexidade
 
regentag 2025-07-07

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?