- Mesmo com programação em CGI, é possível processar mais de 200 milhões de requisições web por dia
- Com a melhora recente no desempenho de hardware, as desvantagens do modelo CGI diminuíram bastante
- Um programa CGI usando Go e SQLite mostrou desempenho excelente em uma CPU de 16 threads
- O CGI oferece uma estrutura especialmente adequada para aproveitar vários núcleos de CPU
- Com a tecnologia moderna, até formas antigas de desenvolver aplicações web podem ser totalmente viáveis na prática
O passado e o presente do CGI
- No fim dos anos 1990, o autor começou a desenvolver para web com CGI e, na época, usava sistemas como o NewsPro
- O CGI gerava alto overhead porque, a cada requisição web, repetia a execução e o encerramento de um novo processo
- Por esse motivo, foram desenvolvidas tecnologias alternativas mais eficientes, como PHP e FastCGI
Evolução do desempenho de hardware
- Ao longo dos últimos mais de 20 anos, a velocidade e o desempenho dos computadores aumentaram drasticamente
- Em 2020, o autor redescobriu a praticidade da execução por processo ao usar ferramentas feitas em Go e Rust, como o
ripgrep
Vantagens do CGI moderno
- Ao implementar CGI com linguagens de execução rápida como Go e Rust, a maioria das desvantagens do CGI antigo desaparece
- Programas CGI funcionam como processos separados por requisição, o que os torna ideais para aproveitar CPUs multicore
- Por exemplo, em um ambiente com 16 threads, foi confirmada a possibilidade de processar mais de 2.400 requisições por segundo = mais de 200 milhões de requisições por dia
- Servidores de grande porte podem oferecer mais de 384 threads de CPU
Insights sobre cultura de desenvolvimento
- Hoje, com a adoção de linguagens como Go e Rust, o modelo de CGI dos anos 1990 pode voltar a fazer sentido
- Ainda assim, ele não é adequado para todos os ambientes e não é recomendado como abordagem principal
- O ponto importante é que, no momento atual, foi demonstrado experimentalmente que o CGI não é mais uma solução tão ineficiente quanto antes
Conclusão
- Com hardware moderno e suporte de linguagens rápidas, a programação em CGI mostra um desempenho incomparável ao do passado
- Como um caso que aproveita ao máximo as vantagens do multiprocesso, isso oferece reflexões interessantes para desenvolvedores web
1 comentários
Comentários no Hacker News
Hoje em dia, mesmo em Python, CGI parece ter um desempenho bem rápido
Mesmo que um script CGI use 400 ms de CPU na inicialização, se o servidor tiver 64 núcleos, dá para processar 160 requisições por segundo, ou cerca de 14 milhões de acessos por dia por servidor
Ou seja, mesmo tráfego diário na casa das centenas de milhões (excluindo assets estáticos) não significa que o startup do processo CGI seja o gargalo
Antes eu pensava que esse tipo de tecnologia era "estável a ponto de ser entediante", então sempre estaria na biblioteca padrão do Python, mas hoje os mantenedores do Python parecem até ter uma postura negativa em relação à estabilidade e à compatibilidade retroativa
Por isso, módulos "estáveis e entediantes" demais estão sendo removidos da biblioteca padrão, e de fato o módulo
cgifoi apagado na versão 3.13É hábito de quase 25 anos usando Python para prototipagem, mas agora estou começando a me arrepender
Estou dividido entre JS e Lua
O link da explicação oficial sobre a remoção do
cgié PEP 594 cgiEsse link leva ao PEP 206, escrito em 2000 (há 25 anos), onde já se dizia que "o pacote
cgitem um design ruim e é difícil de mexer"No repositório jackrosenthal/legacy-cgi dá para ver um drop-in replacement que substitui o módulo da biblioteca padrão praticamente sem mudanças
Os desenvolvedores de Python só removeram o módulo chamado
cgiA implementação de scripts CGI continua sendo suportada em
CGIHTTPRequestHandler, do módulohttp.serverVale lembrar que o módulo
cgioriginalmente só tinha algumas funções para fazer parsing de dados de formulários HTMLEntendo a crítica ao fato de o módulo
cgiestar saindo da biblioteca padrão do Python, mas o JS que costuma ser citado como alternativa nem sequer tem biblioteca padrão de verdadeE o Lua também não tem módulo CGI na stdlib
Pessoalmente, prefiro PHP ou JS
Nesses casos, ter JIT disponível por padrão já traz conveniência
Uso Python desde a versão 1.6, mas principalmente para scripting de sistema
Antigamente eu integrava Tcl como módulo do Apache ou do IIS e acabava sempre reescrevendo módulos em C de novo e de novo (1999~2003)
Se um script CGI usa 400 ms de CPU, então o tempo de resposta desse endpoint também passa a ser no mínimo isso, o que afeta a usabilidade
Recentemente subi um binário em golang, rabbitmq, redis e MySQL em um mini servidor de 350 dólares, e ele sustentou 5.000 req/s na mesma máquina
Em 24 horas, isso dá capacidade para 400 milhões de requisições
Dá para sentir como as ferramentas gratuitas de hoje são realmente excelentes
Ainda assim, acho o custo de cloud alto demais
Claro que não é uma comparação 1:1, mas foi muito satisfatório poder desenvolver e fazer tuning diretamente em um servidor no porão de casa
Existem casos em que usar um amontoado de microsserviços baseados em Kubernetes deixa o desenvolvimento 10 vezes mais lento
Muita gente parece não perceber que servidor não é uma máquina que só processa 1 requisição por segundo
Existe uma realidade em que se paga overhead demais só porque o Google faz assim
Eu mesmo estou pensando em escrever um texto sobre arquitetura de "monólito modular", que funciona bem para o nosso time
Já tentei hospedar projetos paralelos em casa, mas os riscos são grandes: queda de energia, indisponibilidade do ISP, falta de acesso remoto, falha de disco etc.
No fim, quando considero também o valor do meu tempo, o ganho econômico fica duvidoso
Serviços de cloud se beneficiam de economia de escala, então na prática acabam sendo uma escolha razoável
Nem precisa ser cloud; também dá para alugar servidor dedicado de um provedor de hospedagem
Claro, existem limites de banda/tráfego
O motivo de a cloud ser dominante tem a ver com VC, investidores que têm participação nessas empresas ou o medo de que "o tráfego exploda sem limite"
Profissionais de vendas de cloud exploram muito bem a ansiedade dos investidores
Não é como se todo mundo usasse cloud obrigatoriamente
Em serviços reais, o custo alto de VM muitas vezes não vem de computação de alto desempenho, mas da necessidade de uma quantidade enorme de disco local
Não é preciso tanto poder de cálculo; com 4 discos de 20 TB e uma CPU razoável já dá para imaginar um serviço impressionante
Na cloud, é quase impossível encontrar esse tipo de combinação
Quando o
cgi-binprecisa acessar um banco de dados, é incômodo ter de criar uma nova conexão a cada processoSe o código roda em memória, como no fastcgi, você não só reduz o tempo de startup como também pode manter um pool de conexões com o banco ou conexões persistentes por thread
Em escala, o número de conexões com o banco cresce demais e o banco começa a sofrer
Com argumentos como "Python é single-threaded, então precisamos de vários processos" e "Python é lento, então precisamos de ainda mais processos", acaba-se operando uma grande quantidade de processos
No fim, separa-se um shared connection pool fora dos processos Python, com algo como
pg bouncer, e várias afinações passam a ser necessáriasNo meu caso, no final reimplementei em uma linguagem mais fácil de controlar, com suporte a multithreading e melhor desempenho, e tudo ficou muito mais simples
Foi por isso que o CGI acabou evoluindo para modelos que preservam informações entre requisições, como fastcgi
Tradicionalmente, também se colocava um daemon independente para atuar como proxy, e usar socket Unix para a conexão é muito mais eficiente do que TCP/IP
Alguém sugeriu usar UDP
Para mim,
inetdé praticamente o próprio CGIIsso deixou a internet muito mais divertida
Foi uma época em que eu fazia rodar vários shell scripts via
inetd, até mesmo HTTP escrito só em BashVPS antigos e laptops sem backup nem controle de versão desapareceram, mas ficam as boas lembranças
O deploy também era simples, com Makefile +
scp, e os testes podiam ser escritos em Bash comnetcategrepRealmente parecia uma ótima época para viver
A impressão é que alcançar 2400 rps em um app hello world não é grande coisa para o hardware atual
O código nem ficou mais simples, então fica a dúvida: em nome de quê estamos sacrificando desempenho?
Se você não precisa processar mais de 2000 rps, então não há problema
A ideia é que pouquíssimos sites realmente precisam desse volume de tráfego
Em números absolutos não parece alto, mas na prática é suficiente para muitos ambientes
Inclusive seria o bastante para aguentar o "hug of death" que os desenvolvedores do HN costumam mencionar, quando há um pico súbito de acessos
Na nossa empresa, ainda usamos diretórios
cgi-binpara subir rapidamente webapps internos simplesPara casos simples, a eficiência de desenvolvimento é muito boa
Mesmo com CGI, não é preciso dar
printmanual em HTTP/1.0; dá para usarwsgiref.handlers.CGIHandlerdo Python e executar qualquer app WSGI como script CGIUm exemplo com Flask é simples assim
Em produção, executamos scripts com o plugin CGI do
uwsgiA sensação é que isso é muito mais simples e flexível do que rodar
mod_cgino Apache ou no lighttpdComo o
uwsgiroda como unidade de sistema, também dá para aproveitar todo o hardening e sandboxing dosystemdAlém disso, no tratamento CGI do
uwsgié possível definir um interpretador para cada tipo de arquivoO primeiro byte sai em 250~350 ms, o que é totalmente aceitável para o nosso uso
documentação do uwsgi sobre CGI
Foi útil saber que
wsgiref.handlers.CGIHandlerainda não foi marcado como deprecatedThread relacionada discutida ontem: link
Recentemente, ao usar Apache em um projeto paralelo, achei o recurso
.htaccessútilBasta colocar um arquivo
.htaccessem qualquer diretório para carregar configuração adicional do servidor em cada requisição individualdocumentação oficial do htaccess
Antigamente recomendava-se evitar
.htaccesspor causa do overhead de acessar disco em toda requisição, e integrar tudo à configuração principal sempre que possívelMas hoje, com SSD e RAM sobrando, embora haja uma pequena perda de desempenho, a CPU é boa o bastante para que, na maioria dos casos, isso seja irrelevante
[meu projeto StaticPatch][https://github.com/StaticPatch/StaticPatch/tree/main] já usa isso na prática
Uma frase famosa do criador do PHP, Rasmus Lerdorf
"Eu não sou realmente um programador de verdade, eu só faço as coisas funcionarem e sigo em frente. Programadores de verdade dizem: 'isso tem muitos memory leaks, precisamos consertar'. Eu simplesmente reinicio o apache a cada 10 requisições"
O PHP depois percorreu uma longa jornada, superando erros iniciais e evoluindo muito
Dizem também que ele deixou a frase: "PHP 8 fica melhor quanto menos código meu ele executa"
Não entendo por que o Apache não observa o sistema de arquivos e lê só quando há mudança, em vez de causar acessos desnecessários a disco a cada requisição
A crítica é que, com isso, 99,99% das requisições HTTP acabam ficando mais lentas
Tenho pensado nessa estrutura recentemente para prototipagem rápida no meu workflow
Em linguagens com JIT, se não for em formato fastcgi, o import acaba virando gargalo
O servidor web
h2o, que já usei, tem configuração simples paramrubye handler fast-cgi, então serve perfeitamente para scripts locaisdocumentação do fastcgi no h2o
Outra vantagem é quando se quer permitir que o próprio cliente adicione código customizado para estender um software local
Por exemplo, antes seria preciso usar MCP para extensões, mas agora basta implementar requisições estruturadas em CGI
Para uso em ambiente de usuário final, ligar um programa CGI como front para MCP também parece uma ideia interessante
Serviços MCP talvez também possam ser implementados tranquilamente como CGI
Fiquei com vontade de olhar melhor a especificação
Fica a dúvida se o fastcgi não elimina quase todas as vantagens que o cgi tinha
Antigamente usei diretamente a combinação de programa em C com CGI
Na época, não havia mais de 100 núcleos nem RAM abundante, e tudo rodava com no máximo 1 GB de memória
Pensando no que já era possível naquela época, tenho certeza de que hoje seria muito mais fácil
Depois que precisou de balanceamento de carga, escalar ficou mais difícil, mas antes disso funcionava bem
E, por sinal, front-end e backoffice eram dois executáveis separados