2 pontos por GN⁺ 2025-07-07 | 1 comentários | Compartilhar no WhatsApp
  • Jake Gold executou um programa CGI em Go + SQLite em um AMD 3700X de 16 threads para verificar até que ponto o CGI no estilo dos anos 1990 aguenta em hardware moderno
  • O resultado mostra que, mesmo em hardware comum, é possível processar mais de 2.400 requisições por segundo e mais de 200 milhões de requisições por dia
  • Como o CGI inicia, executa e encerra um processo a cada requisição, no passado havia uma sobrecarga grande; para reduzi-la, surgiram abordagens como PHP e FastCGI
  • Em 2020, no datasette-ripgrep, Simon Willison chamou a CLI ripgrep baseada em Rust para processar buscas, o que mudou a antiga crença de que era preciso evitar executar processos durante requisições web
  • Com linguagens de inicialização rápida como Go e Rust e ambientes com múltiplas CPUs, o processamento no estilo CGI pode ser uma opção mais viável do que antes, embora não seja a recomendação geral

Reavaliando o desempenho do CGI em hardware moderno

  • Serving 200 million requests per day with a cgi-bin é um texto em que Jake Gold testa o desempenho do CGI no estilo dos anos 1990 usando um programa CGI em Go + SQLite
  • O ambiente de teste é um sistema baseado em AMD 3700X de 16 threads
  • O principal resultado é que, mesmo com CGI, hardware comum consegue processar mais de 2.400 requisições por segundo e mais de 200 milhões de requisições por dia
  • O CGI funciona iniciando um processo separado para cada requisição recebida, executando-o e depois encerrando-o
  • Para evitar essa sobrecarga, a comunidade web inicial criou PHP e FastCGI, que mantinham o código residente na memória para reduzir custos adicionais

Por que o CGI talvez não seja tão ruim quanto antes

  • Um dos gargalos do CGI no passado era o fato de scripts web serem escritos em linguagens como Perl, Python e Java, que não foram projetadas com foco em inicialização extremamente rápida
  • Hoje, usando Go e Rust, o processamento de requisições no estilo CGI pode funcionar de forma muito mais eficaz
  • Em 2020, ao criar o datasette-ripgrep, Simon Willison chamou via shell a ferramenta CLI ripgrep, escrita em Rust, para executar buscas, e obteve bons resultados
  • Como programas CGI são executados como processos separados, eles também podem se ajustar bem a arquiteturas que aproveitam várias CPUs
    • Servidores modernos podem ter 384 threads de CPU
    • Mesmo VMs pequenas podem ter 16 CPUs
    • O desempenho de CPU e memória também ficou muito mais rápido do que no passado
  • A forma de escrever aplicações web de 1998 também se torna um experimento interessante quando encontra Go e Rust, mas não é a escolha padrão que a maioria dos serviços deve seguir

1 comentários

 
GN⁺ 2025-07-07
Opiniões no Hacker News
  • Mesmo usando algo como Python, hoje em dia CGI é bem rápido
    Se iniciar um script CGI consome 400 ms de CPU e o servidor tem 64 núcleos, ele consegue lidar com 160 requisições por segundo, ou 14 milhões de hits por dia por servidor
    Para um serviço web que sofre para atender alguns milhões de requisições por dia, excluindo assets estáticos, o gargalo não é o tempo de inicialização do processo CGI
    Antigamente eu diria que era uma “tecnologia chata, com suporte de longa data na biblioteca padrão do Python”, mas os mantenedores restantes do Python parecem ver estabilidade e compatibilidade retroativa como prejudiciais, e têm removido da biblioteca padrão módulos que são chatos e estáveis demais
    O módulo cgi foi removido no Python 3.13
    Usei Python diariamente durante a maior parte dos últimos 25 anos, então ainda tenho o hábito de usá-lo para prototipagem, mas agora lamento isso e estou em dúvida entre JS e Lua

    • A justificativa para a remoção do cgi está em https://peps.python.org/pep-0594/#cgi
      Curiosamente, ali há um link para https://peps.python.org/pep-0206/, de 14 de julho de 2000, ou seja, 25 anos atrás, e já naquela época o pacote cgi era descrito como “mal projetado e agora quase impossível de consertar”
      O pacote https://github.com/jackrosenthal/legacy-cgi parece oferecer um substituto drop-in para o módulo da biblioteca padrão
    • O que os mantenedores do Python estão removendo é o módulo chamado cgi, não o suporte em si à implementação de scripts CGI
      Esse suporte continua no CGIHTTPRequestHandler do módulo http.server
      O que havia no módulo cgi eram apenas algumas funções para fazer o parse de dados de formulários HTML
    • Dá para entender a insatisfação com o Python remover CGI da biblioteca padrão
      Mas não entendo muito bem considerar, como alternativa em seguida, JS, que não tem biblioteca padrão propriamente dita
      Lua também não tem um módulo CGI na biblioteca padrão
    • Antigamente, web de alta performance era uma espécie de arte técnica
      Hoje está mais para encontrar as partes absurdamente desperdiçadoras que foram colocadas para lançar rápido e fazê-las parar
      Se você tentar direito, a aplicação pode praticamente não acrescentar latência além do acesso ao armazenamento
    • Nesse tipo de caso, prefiro mais PHP ou JS, que têm JIT por padrão
      Aprendi Python desde a versão 1.6, mas o usei principalmente para scripting de sistema operacional
      Entre 1999 e 2003 usei Tcl em módulos do Apache e do IIS, e aprendi muitas lições da forma difícil enquanto reescrevia continuamente módulos em C
  • Recentemente testei em um mini servidor de US$ 350 um binário em Golang, RabbitMQ, Redis e MySQL, todos rodando na mesma máquina, e ele sustentou 5.000 requisições por segundo
    Convertendo para 24 horas por dia, são 400 milhões de requisições
    É impressionante como as ferramentas gratuitas de hoje são excelentes, e também é impressionante que ainda assim paguemos tanto dinheiro aos provedores de nuvem
    Não é uma comparação totalmente equivalente, mas foi muito bom desenvolver e ajustar tudo em uma máquina no porão

    • Vejo casos demais em que se implanta um monstro de microsserviços em Kubernetes que torna o desenvolvimento 10 vezes mais lento, sem nem saber que o servidor consegue processar muito mais do que 1 requisição por segundo
      É estranho continuar arcando com esse overhead sem motivo além de “o Google faz assim”
      Preciso mesmo escrever um texto sobre a arquitetura de monólito modular que temos usado bem
    • Não é obrigatório pagar a um provedor de nuvem
      Dá para alugar um servidor dedicado de uma empresa de hospedagem e usá-lo à vontade. Só que você fica bastante preso a limites de largura de banda ou de transferência
      A nuvem é usada porque há muitos interesses envolvidos. Por exemplo, VCs e investidores também têm participação em empresas de nuvem e temem que o investimento fracasse por causa de um pico de tráfego imaginário que, na prática, nunca virá
      Vendas de nuvem são hábeis em explorar as ansiedades dos investidores
    • Já pensei em hospedar um projeto paralelo no porão, mas aí ele ficaria vulnerável a quedas de energia e falhas do ISP
      Se um drive quebrar enquanto eu estiver viajando, não há solução; e, se eu me trancar para fora por engano, também não há terminal serial de fallback
      Dá para montar algo como o pessoal do r/homelab, mas aí fica incerto se isso realmente economiza dinheiro, especialmente considerando meu tempo
      No fim, vejo os provedores de nuvem como um negócio bastante bom graças à economia de escala
    • Olhando o custo de VMs, o motivo de serem caras não é precisar de muito processamento, mas sim de servidores com muito disco local
      Estranhamente, não dá para obter só uma dessas coisas separadamente
      Imagine o tamanho do negócio ou serviço que você poderia operar com 4 discos de 20 TB e uma CPU razoável
      Não é fácil conseguir essa configuração de um provedor de nuvem
  • A parte que diz que “as primeiras comunidades da web aprenderam rapidamente que isso era uma má ideia e inventaram tecnologias como PHP” é, estritamente falando, que a tecnologia essencial foi mod_php
    O PHP em si não era diferente de Perl na forma de execução, mas, graças às escolhas de projeto do mod_php em comparação com o mod_perl, scripts PHP podiam ser simplesmente jogados no servidor e rodavam rápido, enquanto o mod_perl exigia um pouco de reflexão e magia para funcionar

    • Naquela época, eu estava desenvolvendo com um amigo algo que mais tarde seria chamado de sistema de gestão de aprendizagem
      Tinha gerenciamento de conteúdo, upload de tarefas, calendário de eventos, gestão de notas, chat em tempo real e fóruns, tudo em C puro sobre CGI, e trabalhar nisso era um inferno
      No dia em que conheci PHP, quase chorei, porque tudo que eu vinha implementando com dificuldade do zero, lendo RFCs ou fazendo engenharia reversa de HTTP, em PHP era uma simples chamada de função
      Eu não precisava mais depurar uma implementação capenga de urlencode nem perder um dia por causa de um carriage return estranho em um cabeçalho HTTP
    • Sim, mas mod_php foi adicionado cedo ao ecossistema PHP e rapidamente se tornou o modo padrão de distribuição
      Pelo que vejo, a primeira versão do módulo Apache foi para PHP/FI Version 2.0, em 1996: https://www.php.net/manual/phpfi2.php#module
  • Se o cgi-bin precisar acessar o DB, será necessário abrir uma conexão toda vez que o processo iniciar
    Deixar o código na memória, como no FastCGI, não é só para evitar o custo de tempo de inicialização, mas também porque permite ter um pool de conexões com o DB, ou pelo menos conexões persistentes por thread

    • Quando a escala aumenta, o banco de dados sofre por causa do número de conexões
      Foi o que aconteceu quando se combinou “Python é single-thread, então vamos subir muitas instâncias” com “Python é lento, então vamos subir muitas instâncias”
      Quando a escala aumenta, no fim é preciso usar um pool de conexões compartilhado fora do Python, como o pgbouncer, e muita otimização para lidar com a carga sem matar o DB
      Claro que, depois disso, ao reimplementar em uma linguagem multithread com desempenho razoável, tudo voltou a ficar bem simples
    • Por isso, no fim, CGI evoluiu para um modelo que mantém algum estado entre requisições
    • Também havia uma forma padrão de lidar com isso
      Na prática, consistia em subir um daemon separado que atuava como proxy
      Usando soquetes Unix em vez de TCP/IP, o custo de conexão fica relativamente baixo
    • Basta usar UDP
  • Neste hardware, uma aplicação hello world com 2.400 requisições por segundo não é meio ruim?
    Também não sei exatamente o que se ganha ao abrir mão de desempenho. O código nem ficou mais simples

    • Só é um número ruim quando você precisa passar de 2.000 requisições por segundo
      Sites assim são apenas uma parte do total
    • Não é excelente, mas é suficiente para muitos casos de uso
      Parece que aguentaria até uma bomba de tráfego do HN
  • Isso também foi discutido ontem: https://news.ycombinator.com/item?id=44464272

  • Na empresa, ainda servimos um diretório cgi-bin para apps web internos feitos rapidamente de vez em quando
    Se mantiver simples, a usabilidade é boa
    CGI não significa que você precise dar print diretamente de HTTP/1.0 para stdout
    Por exemplo, usando o wsgiref.handlers.CGIHandler embutido do Python, qualquer app WSGI pode ser executado como um script CGI
    import wsgiref.handlers, flask
    app = flask.Flask(name)
    wsgiref.handlers.CGIHandler().run(app)
    Executamos os scripts com uwsgi e seu plugin CGI[1]
    Acho mais simples e flexível do que subir Apache ou lighttpd por causa do mod_cgi
    Como o uwsgi roda como uma unidade do systemd, também podemos aproveitar todos os recursos de hardening e sandboxing do systemd
    Uma conveniência no tratamento de CGI do uwsgi que não existe no mod_cgi é poder especificar o interpretador a ser usado para determinados tipos de arquivo
    cgi = /cgi-bin=/webapps/cgi-bin/src
    cgi-allowed-ext = .py
    cgi-helper = .py=/webapps/cgi-bin/venv/bin/python3 # all dependencies go here
    O tempo até o primeiro byte fica entre 250 e 350 ms, e para nosso uso é aceitável
    [1]: https://uwsgi-docs.readthedocs.io/en/latest/CGI.html

    • Boa informação, e o wsgiref.handlers.CGIHandler parece que ainda não está previsto para descontinuação
  • Usei Apache em um projeto paralelo recente, e um dos motivos era o recurso .htaccess, então tive uma conversa parecida
    Você pode colocar arquivos .htaccess em qualquer lugar, e o Apache pode lê-los a cada requisição como configuração adicional do servidor: https://httpd.apache.org/docs/2.4/howto/htaccess.html
    Um grande motivo para evitar isso era desempenho. Exigia acesso adicional ao disco em cada requisição, e, quando possível, sempre era melhor colocar no arquivo de configuração principal
    Mas hoje a maioria dos servidores tem SSD, e é bem provável que o Linux tenha RAM sobrando para usar no cache do sistema de arquivos
    Claro que o Apache ainda precisa fazer o parsing da configuração a cada requisição, em vez de uma única vez, então o desempenho ainda piora um pouco
    Ainda assim, considerando que a maioria das CPUs de servidor hoje é mais potente, isso é aceitável em muitos casos de uso
    O projeto paralelo está em uma versão bem inicial, mas já está em uso: https://github.com/StaticPatch/StaticPatch/tree/main

    • Citando Rasmus Lerdorf:
      “Eu não sou um programador de verdade. Vou juntando coisas até funcionar e então sigo em frente. Programadores de verdade diriam: ‘Certo, funciona, mas está vazando memória por todos os lados. Será que não deveríamos consertar?’ Eu simplesmente reinicio o Apache a cada 10 requisições.”
      O PHP avançou muito desde então, mas boa parte disso foi o processo de corrigir os erros iniciais
      “O motivo de o PHP 8 ser muito melhor é que há muito menos do meu código nele.”
    • Não entendo por que o Apache simplesmente não monitora o sistema de arquivos
      Por causa dessa escolha, 99,99% das requisições HTTP acabam ficando mais lentas por leituras de disco desnecessárias
  • Pensei mais sobre isso como parte de um fluxo de trabalho de prototipagem rápida
    Nas linguagens JIT modernas, acho que o tempo de inicialização será dominado principalmente pelos imports, a menos que se use o modelo FastCGI
    Comecei a pensar nisso quando passei a usar o servidor web h2o para scripts locais; com mruby e um handler FastCGI, dá para escrever arquivos de configuração de forma limpa e rápida, e o desempenho também é muito alto: https://h2o.examp1e.net/configure/fastcgi_directives.html
    Outro caso útil é quando se quer permitir que clientes estendam um software local com o próprio código
    Por exemplo, em vez de fazer com que usem MCP para estender uma ferramenta de IA, você poderia pedir que implementassem uma estrutura específica de requisição como CGI

    • Um frontend MCP para programas CGI no ambiente do usuário final não é uma ideia ruim
      Fico me perguntando se o próprio serviço MCP também não poderia ser implementado em CGI
      Um framework MCP poderia expor funcionalidades na forma de um programa que suporte ambos os modos de execução
      Vou ter que investigar melhor a especificação
    • FastCGI tende a perder quase todas as vantagens do CGI
  • Para mim, inetd era praticamente CGI
    Graças a isso, a internet ficou muito mais divertida
    Eu mesmo hospedei vários scripts de shell com inetd, e também tinha um servidor HTTP escrito inteiramente em Bash
    Aquele VPS desapareceu há muito tempo, e na época eu nem usava controle de versão
    O notebook em que escrevi isso também sumiu
    Mesmo assim, foi muito divertido
    O deploy era tão simples quanto Makefile + scp, e os testes eram outro script Bash que misturava vários netcat com grep
    Bons tempos