7 puntos por GN⁺ 2025-07-07 | 4 comentarios | Compartir por WhatsApp
  • Se comprobó mediante experimentos que los programas CGI, ampliamente usados en la primera era de la web, todavía pueden ofrecer alto rendimiento en hardware moderno
  • CGI procesa las solicitudes por proceso, lo que permite gestión automática de memoria y la ventaja de un despliegue simple
  • Los resultados del benchmark demostraron que incluso en un servidor común con CPU de 16 hilos es posible procesar más de 2,400 requests por segundo, o más de 200 millones al día
  • Se publicó como open source un ejemplo de guestbook.cgi escrito en Go y SQLite, junto con su Dockerfile
  • Aunque CGI ya no es de uso común hoy en día, muestra que todavía puede ser una alternativa práctica y moderna

Programas CGI y cómo funcionan

  • A inicios de los 2000, los programas CGI (Common Gateway Interface) eran una de las principales formas de construir sitios web dinámicos
  • La mayoría estaban escritos en Perl o C, y a veces se elegía C para mejorar el rendimiento
  • El concepto de CGI es simple pero potente
    • El servidor web configura en variables de entorno los metadatos de la solicitud (headers HTTP, query, etc.)
    • Crea un proceso separado para ejecutar el programa CGI
    • Envía el cuerpo de la solicitud por stdin
    • Captura el stdout del programa como respuesta HTTP
    • Envía la salida de stderr al log de errores del servidor
    • Cuando el programa termina de atender la solicitud, el proceso finaliza y los file descriptors y la memoria se liberan automáticamente
  • Desde la perspectiva del desarrollador, desplegar una nueva versión también era muy simple: bastaba con copiar el archivo al directorio cgi-bin/

Hug of death (avalancha de tráfico)

  • A inicios de los 2000, la mayoría de los servidores web solían tener 1 o 2 CPU y entre 1 y 4 GB de memoria
  • Debido a que el servidor web Apache usaba una estructura en la que hacía fork de un proceso httpd por cada conexión, los requisitos de memoria aumentaban cuando había muchas conexiones
  • Era difícil superar las 100 conexiones simultáneas, y bastaba con que un sitio famoso pusiera un enlace para que el servidor se sobrecargara fácilmente
    • ( Slashdot Effect: si aparecía un enlace en Slashdot, que era muy popular en esa época, llegaba una avalancha de tráfico. Algo parecido a subir hoy a la portada de Hacker News)

CGI en entornos de servidores modernos

  • Hoy existen servidores con 384 hilos de CPU, y hasta una VM relativamente pequeña puede ofrecer 16 CPU
  • El rendimiento de CPU y memoria ha mejorado enormemente
  • Como los programas CGI se basan en procesos separados, pueden aprovechar de forma natural los multicore
  • Por eso se realizó un benchmark para medir directamente qué tan rápido puede ser CGI en hardware moderno
  • El experimento se ejecutó en un servidor con AMD 3700X (16 hilos)

Principales resultados del benchmark

  • Se probó un programa CGI simple tanto en un entorno con Apache como con un servidor Go net/http
  • Descripción del programa guestbook.cgi

  • Con la herramienta de generación de carga HTTP plow se ejecutaron 100 mil requests con 16 conexiones
  • Incluso en hardware común, es posible procesar más de 2,400 requests por segundo, es decir, más de 200 millones de requests al día
  • CGI no es la corriente principal hoy, pero todavía puede usarse en operación real de servicios
  • Benchmark de escritura en entorno Apache

    • Se procesaron aprox. 2468 requests por segundo, con una latencia promedio de 6.47 ms
    • 100 mil requests POST se procesaron en solo 40.5 segundos
    • La mayoría de las solicitudes respondieron en menos de 7 ms, y solo una fracción muy pequeña superó los 100 ms
    • Esto demuestra en la práctica un alto rendimiento de escritura
  • Benchmark de lectura en entorno Apache

    • Se procesaron aprox. 1959 requests por segundo, con una latencia promedio de 8.16 ms
    • 100 mil requests GET se procesaron en 51 segundos
    • Más de la mitad de las solicitudes respondieron en menos de 8 ms, y la latencia máxima fue de apenas 31 ms
    • El rendimiento de lectura también es suficientemente bueno
  • Benchmark de escritura en entorno Go net/http

    • Se procesaron aprox. 2742 requests por segundo, con una latencia promedio de 5.83 ms
    • 100 mil requests POST se procesaron en 36.4 segundos
    • El throughput fue en promedio de 2,742 RPS, con latencia media de 5.8 ms, mostrando mejores cifras que Apache
    • Más del 95% de las solicitudes se procesaron en menos de 6 ms
    • CGI en entorno Go también ofrece rendimiento suficiente para producción
  • Benchmark de lectura en entorno Go net/http

    • Se procesaron aprox. 2469 requests por segundo, con una latencia promedio de 6.47 ms
    • 100 mil requests GET se procesaron en 40.4 segundos
    • La mayoría de las solicitudes pudieron atenderse en menos de 7 ms
    • Tanto el throughput de lectura como la velocidad de respuesta son similares o superiores a los de Apache

Conclusión y enlaces

  • Los programas CGI tienen ventajas como concurrencia ultrarrápida en hardware moderno, despliegue simple y liberación automática de recursos por parte del sistema operativo
  • Aunque son extremadamente simples frente a frameworks modernos, todavía pueden usarse en producción para servicios de cierto tamaño
  • El ejemplo del libro de visitas y los datos del benchmark están publicados en el siguiente GitHub
    https://github.com/Jacob2161/cgi-bin

4 comentarios

 
kansm 2025-07-09

Vaya... ¿vamos a volver a usar CGI?? jaja
Guau... de qué época será eso de CGI...

 
tujuc 2025-07-08

Parece que hay contenido actualizado al 7/7.

Serving a half billion requests per day with Rust + CGI

Nada menos que 500 millones de requests...

 
GN⁺ 2025-07-07
Comentarios en Hacker News
  • Recuerdo que incluso en los años 90 los programas CGI escritos en C mostraban una velocidad realmente alta, aunque admito que su desventaja era que fallaban mucho; programas modernos como los de Go o lenguajes como Nim, mientras no hagan conexión a base de datos, se sienten muy rápidos y con muy poca latencia en localhost, casi como usar fork & exec en una utilidad CLI; comparado con la latencia de red, el costo era casi despreciable

    • También se menciona una cultura propensa a volverse adicta a cierta tecnología; por ejemplo, una vez que te acostumbras a lenguajes con alto costo de arranque como el intérprete de Python, terminas necesitando un modelo multishot o persistente

    • El modelo one-shot del HTTP temprano surgió del problema de que no había suficiente memoria para que un servidor FTP mantuviera durante mucho tiempo cientos de sesiones de login inactivas

    • Se menciona que combinar pre-forking en CGI (que puede ocultar la latencia) con un lenguaje seguro como Rust permitiría diseños de sistema sobresalientes; además, la terminación TLS puede manejarse en un servidor web multihilo (o en una capa como CloudFront), lo que lo vuelve muy conveniente

      • Al no dejar estado, el entorno facilita muchísimo los core dumps y el debugging, y además se puede escalar de forma simple con un modelo de solicitudes mayormente lineal
      • Se elogia la simplicidad de solo tener que leer de stdin y escribir a stdout; WebSockets sí aumentan un poco la complejidad, pero no a un nivel preocupante
      • Se recuerda cómo, con el ascenso de Java, ocurrió una transición acelerada hacia los application servers para evitar tanto el costo de fork() como los riesgos de C; ahora se argumenta que ya podemos volver a la simplicidad
      • Aunque no le guste Rust, espera que cuando llegue una era en la que este tipo de código backend web pueda escribirse con facilidad, también resulte atractivo para desarrolladores de node/js, php y python
  • Haber empezado a desarrollar desde la época de CGI le dejó una fuerte aversión a ejecutar subprocesos de vida corta

    • Explica que PHP y FastCGI surgieron para escapar del problema de rendimiento de crear un proceso nuevo por cada petición web

    • Gracias al avance reciente del hardware, se dio cuenta de que el costo de iniciar procesos en realidad ya no es un gran problema

    • Menciona que este benchmark puede manejar 2000 solicitudes por segundo, y que incluso con solo unas pocas centenas ya es fácil escalar con varias instancias en un entorno moderno

    • Coincide con la opinión de describir AWS Lambda como un renacimiento del modelo CGI; le parece una analogía bastante acertada

    • Comenta que, si los scripts CGI se hubieran desplegado como binarios C con enlace estático y cuidando incluso el tamaño, probablemente la decepción habría sido menor

      • El costo de arranque de proceso por el enlazado dinámico —cargar el intérprete de PHP, varias bibliotecas, parsear archivos, etc.— es mucho mayor
      • Está convencido de que usar Go es un enfoque que ya habría sido suficientemente competitivo hace 25 años
      • Destaca que abrir una base de datos SQLite tiene un rendimiento casi comparable a pasar un socket mediante context switch, y es muchísimo más rápido que conectarse a un mysql remoto
      • Sostiene que FastCGI sigue siendo una gran opción incluso para aplicaciones nuevas
    • CGI no era una carga grande ni en costo ni en rendimiento en entornos de baja carga

      • En situaciones de alta carga, un proceso persistente como FastCGI resulta más ventajoso
      • CGI también puede manejar hasta 2,000 rps, pero FastCGI puede alcanzar un rendimiento mucho mayor
      • Solo requiere agregar un proceso de servidor aparte y reiniciarlo en el momento de actualizar, y afirma que vale la pena cuando el rendimiento importa
    • Antes de que apareciera Go, en los 2000 hacer programas CGI en C/C++ implicaba grandes riesgos y alta dificultad de desarrollo

      • Perl y Python tenían un costo considerable de arranque del intérprete y compilación, y Java en la práctica era aún más lento
      • Coincide en que AWS Lambda = una reencarnación del modelo CGI
      • Ahora se siente como si hubiéramos vuelto a un modelo casi idéntico a FastCGI administrado
      • Lamenta esta avalancha de tecnologías que agregan muchísima complejidad cuando bastaría con subir un ejecutable y ponerlo a correr
  • Hoy vivimos en una época en la que un servidor puede tener 384 hilos de CPU y hasta una VM pequeña puede tener 16 CPUs

    • En hardware así, si desarrollas con Kestrel, puedes manejar sin problema hasta billones de solicitudes al día

    • Se puede ofrecer una experiencia de desarrollo similar a PHP usando operadores de interpolación de strings

    • Con LINQ y String.Join() es fácil plantillar tablas HTML y elementos anidados

    • La verdadera dificultad es saber cómo esquivar bien el campo minado del ecosistema de MVC/Blazor/EF

    • También es posible ejecutar todo el programa como un solo archivo de nivel superior desde la CLI, pero si no conoces la palabra clave "Minimal APIs", es fácil perderse en un laberinto de documentación equivocada

      • Le sorprende mucho ver cuántos casos hay de gente que asciende a puestos de Director o VP solo por añadir capas de abstracción encima de la tecnología base
  • La ventaja de CGI es que en un entorno multitenant no hace falta reconstruir primitivas de aislamiento desde cero

    • Aunque haya un bug en una solicitud, gracias al aislamiento por procesos no afecta a las demás
    • Incluso un bucle infinito no termina en una denegación de servicio (DoS) gracias al scheduling preventivo
    • Con rlimit es posible terminar a la fuerza solicitudes que tardan demasiado
    • Usando cgroup se puede asignar de forma justa por tenant el uso de memoria, CPU y disco/IO de red
    • Con namespaces/jails y separación de privilegios se pueden limitar los permisos de acceso por solicitud
  • Gracias a los scripts CGI, perl fue optimizado para tener tiempos de arranque rápidos

    • Al ejecutar el comando time perl -e '', perl tarda 5ms, python3 33ms y ruby 77ms, lo que confirma el rápido tiempo de arranque de perl

      • Se menciona que los scripts estilo #!/bin/tcc -run de la rama mob de tcc son 1.3 veces más rápidos que perl
      • Julia, la Java VM, thread PHP y otros también son ejemplos de tiempos de arranque muy largos
      • La gente termina dependiendo por costumbre de "entornos grandes"
      • En la comunidad Lisp esto también se repite mediante el uso de imágenes, y de ahí nació el meme de "emacs is bloated"
      • El apogeo de Perl a mediados y finales de los 90 realmente fue posible gracias a CGI
      • Recuerda una época en la que ni siquiera getline era estándar, y se hacían bibliotecas C de terceros de cientos o miles de líneas
      • Al final, la tecnología se elige por la "reputación", y la mayoría aprende a partir de lo que recomiendan sus amigos
  • Si pruebas apache tomcat 11, basta con subir por ssh un archivo .jsp o toda una aplicación java servlet (.war) y simplemente funciona

    • Se obtiene el máximo rendimiento con una JVM compartida

    • El pool de conexiones DB, cachés y demás también pueden compartirse entre aplicaciones

    • Es una experiencia realmente impresionante

      • Depende del patrón real de uso

      • Para servicios grandes es excelente, pero si tienes 50 aplicaciones pequeñas que solo deben procesar unos cientos de casos al día cada una, se señala que el overhead de memoria de Tomcat es demasiado grande frente a Apache/Nginx basado en scripts CGI

      • Expresa nostalgia por la época en que desplegar era simplemente copiar archivos

      • Lamenta que el proceso de despliegue se haya vuelto tan complicado

      • Comparte que incluso hoy sigue usando Jetty con gusto para aplicaciones web backend

      • Le parece que el stack Tomcat/Jakarta EE/JSP es sorprendentemente bastante sólido

      • Permite mezclar HTML y código como en PHP, y también usar rutas en Java puro

      • Tiene soporte para WebSockets, y al ser un modelo multihilo de proceso único también tiene ventajas para comunicación en tiempo real

      • Si hace falta, se pueden compartir datos entre solicitudes, y el código JSP por defecto queda limitado al alcance de la solicitud

      • El despliegue es realmente fácil: basta con subir un archivo nuevo al directorio webapps y Tomcat carga automáticamente la nueva app y descarga la anterior

      • Como desventaja, menciona que puede haber fugas del classloader que impidan al garbage collection limpiar bien, algo propio del modelo de proceso único

  • Se creó una herramienta de visualización para solicitudes de apache: ibrahimdiallo.com/reqvis

    • Ofrece la mejor experiencia en navegadores de escritorio
    • Permite ver en la web el flujo real de funcionamiento a partir de datos de tráfico de HN
  • Últimamente le parecía sospechoso que estuviéramos yendo hacia arquitecturas tan complejas, y menciona la posibilidad de que en realidad todavía podamos usar tecnologías anteriores si contamos con buen hardware

    • Ante la pregunta de cómo diseñar un sistema para informar precios de acciones en tiempo real a millones de personas, primero pensó en estructuras complejas de streaming como Kafka o pubsub, pero al final también consideró el enfoque simple de dejar archivos estáticos en el servidor

    • Le da curiosidad el costo real de operar algo así

      • En la práctica, la latencia de casi todas las web APIs la determinan las consultas a la base de datos o a modelos de ML
      • El resto del proceso, incluso usando un lenguaje lento como Python, está a un nivel poco relevante
      • Si solo devuelves datos que cambian rara vez, es fácil llegar incluso al límite de la NIC
  • Se enfatiza que es parecido a una arquitectura serverless, pero mucho más simple y barata

    • Se pregunta si en el mundo real hay casos de negocio donde se use así
  • Lamenta que no se haya replanteado esta estructura tradicional y que en cambio solo se haya creado un nuevo paradigma llamado "serverless functions"

    • Aunque funciones serverless como Lambda sí tienen mecanismos de protección aparte (micro-VMs, etc.), cree que en esencia con CGI y ajuste de permisos se habría podido llegar muy lejos con mucha menos complejidad
 
regentag 2025-07-07

Bueno, lo de cgi pase, pero me sorprende la reacción sobre jsp jaja
¿jsp ya se convirtió en una reliquia tan antigua?