2 Punkte von GN⁺ 2025-07-07 | 1 Kommentare | Auf WhatsApp teilen
  • Auch mit CGI-Programmierung lassen sich pro Tag mehr als 200 Millionen Web-Anfragen verarbeiten
  • Durch die jüngsten Verbesserungen der Hardwareleistung haben sich die Nachteile des CGI-Ansatzes stark verringert
  • Ein CGI-Programm mit Go und SQLite zeigt auf einer CPU mit 16 Threads eine hervorragende Leistung
  • CGI bietet eine Struktur, die sich besonders gut für die Nutzung mehrerer CPU-Kerne eignet
  • Dank moderner Technik erweisen sich auch frühere Ansätze der Webapplikationsentwicklung wieder als durchaus praktikabel

Vergangenheit und Gegenwart von CGI

  • Ende der 1990er begann der Autor mit CGI in der Webentwicklung und nutzte damals Systeme wie NewsPro
  • CGI verursacht einen hohen Overhead, da für jede Web-Anfrage ein neuer Prozess gestartet und beendet wird
  • Aus diesem Grund wurden effizientere Alternativen wie PHP und FastCGI entwickelt

Fortschritte bei der Hardwareleistung

  • In den vergangenen mehr als 20 Jahren sind Geschwindigkeit und Leistung von Computern stark gestiegen
  • 2020 entdeckte der Autor bei der Nutzung von in Go und Rust entwickelten Tools (wie ripgrep) die Praxistauglichkeit des Prozessstarts erneut

Vorteile eines modernen CGI-Ansatzes

  • Wird CGI in schnellen Sprachen wie Go und Rust implementiert, verschwinden die meisten Nachteile des klassischen CGI
  • CGI-Programme laufen pro Anfrage als eigener Prozess und sind damit optimal für die Nutzung von Multi-Core-CPUs geeignet
    • In einer Umgebung mit 16 Threads wurden beispielsweise mehr als 2400 Anfragen/Sekunde bestätigt = Potenzial für über 200 Millionen Anfragen/Tag
    • Große Server können mehr als 384 CPU-Threads bereitstellen

Einblick in die Entwicklungskultur

  • Durch die Verbreitung von Sprachen wie Go und Rust kann der CGI-Ansatz der 1990er Jahre heute wieder relevant werden
  • Dennoch ist er weiterhin nicht für jede Umgebung geeignet und wird nicht als Mainstream-Ansatz empfohlen
  • Wichtig ist, dass experimentell gezeigt wurde, dass CGI heute keine so ineffiziente Lösung wie früher mehr ist

Fazit

  • Mit moderner Hardware und schnellen Sprachen erreicht CGI-Programmierung eine Leistung, die mit der Vergangenheit nicht vergleichbar ist
  • Als Beispiel dafür, wie sich die Vorteile von Multi-Prozess-Architekturen maximal nutzen lassen, liefert dies interessante Impulse für Webentwickler

1 Kommentare

 
GN⁺ 2025-07-07
Hacker-News-Kommentare
  • Heutzutage fühlt sich selbst CGI mit Python ziemlich schnell an.
    Selbst wenn ein CGI-Skript beim Start 400 Millisekunden CPU-Zeit verbraucht, kann ein Server mit 64 Kernen 160 Requests pro Sekunde verarbeiten und pro Tag 14 Millionen Anfragen pro Server bewältigen.
    Selbst Traffic in der Größenordnung von hunderten Millionen Requests pro Tag (ohne statische Assets) bedeutet also nicht, dass der CGI-Prozessstart der Flaschenhals ist.
    Früher dachte ich, so eine Technik sei „langweilig stabile Technik“ und deshalb immer Teil der Python-Standardbibliothek, aber die heutigen Python-Maintainer stehen Stabilität und Abwärtskompatibilität eher negativ gegenüber.
    Deshalb werden allzu „langweilig stabile“ Module aus der Standardbibliothek entfernt; tatsächlich wurde das cgi-Modul in Version 3.13 gelöscht.
    Es ist wohl eine Gewohnheit aus fast 25 Jahren, Python fürs Prototyping zu verwenden, aber inzwischen bereue ich das.
    Ich schwanke gerade zwischen JS und Lua.

    • Der offizielle Link zur Entfernung von cgi ist PEP 594 cgi.
      Von dort geht es weiter zu PEP 206, das im Jahr 2000 (vor 25 Jahren) geschrieben wurde und schon damals erklärte, dass „das cgi-Paket schlecht designt und schwer anzufassen ist“.
      Im Repository jackrosenthal/legacy-cgi gibt es einen Drop-in-Replacement, der das Standardbibliotheksmodul unverändert ersetzt.

    • Die Python-Entwickler haben nur das Modul mit dem Namen cgi entfernt.
      Die Implementierung von CGI-Skripten wird weiterhin über CGIHTTPRequestHandler im Modul http.server unterstützt.
      Es wird auch darauf hingewiesen, dass das cgi-Modul ursprünglich nur ein paar Funktionen zum Parsen von HTML-Formulardaten enthielt.

    • Ich verstehe die Kritik daran, dass das cgi-Modul aus der Python-Standardbibliothek verschwindet, aber das oft genannte JS hat in der Regel überhaupt keine Standardbibliothek.
      Es wird auch angemerkt, dass Lua ebenfalls kein CGI-Modul in der Stdlib hat.

    • Persönlich bevorzuge ich PHP oder JS.
      In solchen Fällen ist ein JIT direkt mitgeliefert, was bequem ist.
      Ich nutze Python seit Version 1.6, aber hauptsächlich nur noch für OS-Skripting.
      Früher habe ich Tcl mit Apache- oder IIS-Modulen integriert und dabei immer wieder Module in C neu geschrieben (1999–2003).

    • Wenn ein CGI-Skript 400 Millisekunden CPU-Zeit verbraucht, dann liegt die Antwortzeit dieses Endpunkts zwangsläufig mindestens in dieser Größenordnung, was die Nutzbarkeit beeinträchtigt.

  • Kürzlich habe ich auf einem 350-Dollar-Miniservier einen Golang-Binary, rabbitmq, redis und MySQL betrieben und auf demselben Server konstant 5.000 req/s verarbeitet.
    Das entspricht 400 Millionen Requests in 24 Stunden.
    Man merkt wieder, wie großartig die heutigen kostenlosen Tools sind.
    Trotzdem finde ich die Cloud-Kosten viel zu hoch.
    Natürlich ist ein 1:1-Vergleich schwierig, aber es war sehr befriedigend, Entwicklung und Tuning direkt auf einem Server im heimischen Keller machen zu können.

    • Es gibt Fälle, in denen die Entwicklungsgeschwindigkeit mit einem Kubernetes-basierten Microservice-Konglomerat zehnmal langsamer wird.
      Viele scheinen nicht zu wissen, dass ein Server keine Maschine ist, die nur einen Request pro Sekunde verarbeiten kann.
      Die Realität ist, dass man übermäßigen Overhead bezahlt, nur weil Google es auch so macht.
      Ich denke inzwischen auch, dass ich mal einen Text über die „modulare Monolith“-Architektur schreiben sollte, die für unser Team gut funktioniert.

    • Ich wollte Side-Projects direkt von zu Hause hosten, aber die Risiken sind groß: Stromausfälle, ISP-Downtime, kein Remote-Zugriff, defekte Festplatten usw.
      Wenn man die eigene Zeit mit einrechnet, ist der wirtschaftliche Vorteil am Ende fraglich.
      Cloud-Services profitieren von Skaleneffekten und sind deshalb in der Praxis oft eine vernünftige Wahl.

    • Es muss nicht unbedingt die Cloud sein; man kann auch bei einem Hosting-Provider dedizierte Server mieten.
      Natürlich gibt es Beschränkungen bei Bandbreite und Traffic.
      Dass die Cloud Mainstream ist, liegt daran, dass VCs und Investoren Anteile an diesen Unternehmen haben oder Angst davor schüren, dass „jederzeit unendlicher Traffic einschlagen könnte“.
      Cloud-Vertriebsleute nutzen diese Ängste der Investoren geschickt aus.

    • Man muss nicht zwingend nur die Cloud nutzen.

    • In realen Diensten sind VM-Kosten oft nicht wegen High-Performance-Compute hoch, sondern weil enorme Mengen lokaler Festplattenkapazität gebraucht werden.
      Man braucht keine enorme Rechenleistung; vier 20-TB-Festplatten und eine halbwegs ordentliche CPU reichen schon, um einen beeindruckenden Dienst zu bauen.
      In der Cloud ist so eine Kombination fast unmöglich zu finden.

  • Wenn man in cgi-bin auf eine DB zugreifen muss, ist es lästig, dass der Prozess jedes Mal eine neue DB-Verbindung aufbaut.
    Wenn der Code im Speicher läuft (etwa mit fastcgi), spart man nicht nur Startzeit, sondern kann auch einen DB-Connection-Pool oder dauerhafte Verbindungen pro Thread behalten.

    • Im großen Maßstab steigen die DB-Verbindungszahlen so stark an, dass die DB darunter leidet.
      Man betreibt dann viele Prozesse nach dem Muster „Python ist Single-Threaded, also mehrere Prozesse; Python ist langsam, also noch mehr Prozesse“.
      Am Ende trennt man einen Shared Connection Pool außerhalb der Python-Prozesse aus (etwa pg bouncer) und braucht allerlei Tuning.
      Schließlich habe ich es in einer besser beherrschbaren Sprache (mit Multithreading-Unterstützung und besserer Performance) neu implementiert, und dadurch wurde alles deutlich einfacher.

    • Deshalb hat sich CGI letztlich zu einem Modell weiterentwickelt, das Informationen zwischen Requests behalten kann (etwa fastcgi).

    • Traditionell hat man auch einen unabhängigen Daemon gestartet, der als Proxy fungiert, und wenn man Unix-Sockets verwendet, sind Verbindungen deutlich effizienter als über TCP/IP.

    • Jemand meint, man solle UDP verwenden.

  • Für mich ist inetd praktisch CGI selbst.
    Dadurch wurde das Internet für mich viel interessanter.
    Damals habe ich direkt mit inetd verschiedene Shell-Skripte betrieben, sogar HTTP, das nur in Bash geschrieben war.
    Alte VPS und Laptops ohne Backups oder Versionskontrolle sind verschwunden, aber es sind schöne Erinnerungen.
    Deployment war simpel mit Makefile + scp, und Tests ließen sich mit Bash-Skripten schreiben, die netcat und grep nutzten.
    Wir leben wirklich in guten Zeiten.

  • Dass eine Hello-World-App 2400 rps erreicht, wirkt gemessen an heutiger Hardware nicht besonders beeindruckend.
    Der Code ist auch nicht einfacher geworden, daher frage ich mich, wofür man überhaupt Performance opfert.

    • Wenn man nicht mehr als 2000 rps verarbeiten muss, ist das kein Problem.
      Die Aussage ist, dass nur sehr wenige Websites solchen Traffic überhaupt brauchen.

    • Zahlenmäßig ist das nicht hoch, aber in der Praxis reicht es für viele Umgebungen aus.
      Es sollte sogar reichen, um den auf HN oft erwähnten „Hug of Death“ (plötzlicher Traffic-Schub zu einem bestimmten Zeitpunkt) auszuhalten.

  • In unserem Unternehmen nutzen wir bis heute das cgi-bin-Verzeichnis, um einfache interne Web-Apps schnell bereitzustellen.
    Wenn man es schlicht hält, ist die Entwicklungseffizienz sehr hoch.
    Selbst mit CGI muss man nicht direkt HTTP/1.0 ausgeben; mit Pythons wsgiref.handlers.CGIHandler kann man jede WSGI-App als CGI-Skript ausführen.
    Auch das Flask-Beispiel ist so einfach:

     import wsgiref.handlers, flask
     app = flask.Flask(__name__)
     wsgiref.handlers.CGIHandler().run(app)
    

    In der Praxis führen wir Skripte mit dem CGI-Plugin von uwsgi aus.
    Das wirkt viel einfacher und flexibler, als mod_cgi in Apache oder lighttpd laufen zu lassen.
    Weil uwsgi als Systemdienst läuft, kann man auch das komplette Hardening und Sandboxing von systemd nutzen.
    Außerdem kann man bei der CGI-Verarbeitung von uwsgi für jeden Dateityp einen eigenen Interpreter festlegen.

     cgi = /cgi-bin=/webapps/cgi-bin/src
     cgi-allowed-ext = .py
     cgi-helper = .py=/webapps/cgi-bin/venv/bin/python3 # all dependencies go here
    

    Die Zeit bis zum ersten Byte liegt bei 250–350 ms, und für unseren Zweck ist das völlig akzeptabel.
    uwsgi-Dokumentation zu CGI

    • Danke für den guten Tipp.
      Die Information, dass wsgiref.handlers.CGIHandler noch nicht deprecated ist, ist nützlich.
  • Verwandter Thread von gestern: Link

  • Als ich kürzlich Apache für ein Side-Project genutzt habe, fand ich die .htaccess-Funktion nützlich.
    Man kann in jedem Verzeichnis einfach eine .htaccess-Datei ablegen, und bei jedem einzelnen Request wird zusätzliche Serverkonfiguration geladen.
    Offizielle .htaccess-Dokumentation
    Früher hieß es wegen des Overheads durch Festplattenzugriffe bei jedem Request, man solle .htaccess aus Performance-Gründen möglichst vermeiden und stattdessen alles in die Hauptkonfiguration übernehmen.
    Heute gibt es aber ausreichend SSDs und RAM; natürlich kostet es noch immer ganz leicht Performance, aber CPUs sind inzwischen gut genug, dass die meisten das ignorieren können.
    [Mein Projekt StaticPatch][https://github.com/StaticPatch/StaticPatch/tree/main] nutzt das ebenfalls bereits.

    • Ein berühmtes Zitat von PHP-Erfinder Rasmus Lerdorf:
      „Ich bin kein echter Programmierer. Ich bringe Dinge einfach zum Laufen und ziehe weiter. Echte Programmierer sagen: ‚Das hat schlimme Memory Leaks, das müssen wir reparieren.‘ Ich starte Apache einfach jedes zehnte Mal neu.“
      PHP hat seitdem eine lange Reise hinter sich und sich stark weiterentwickelt, während frühe Fehler überwunden wurden.
      Es heißt auch, er habe gesagt: „PHP 8 ist besser, je weniger Code von mir darin steckt.“

    • Ich verstehe nicht, warum Apache nicht einfach das Dateisystem überwacht und nur bei Änderungen neu einliest, statt bei jedem Request unnötige Festplattenzugriffe auszulösen.
      Dadurch werden 99,99 % aller HTTP-Requests langsamer.

  • In meinem aktuellen Workflow denke ich für schnelles Prototyping wieder über so eine Struktur nach.
    Bei JIT-Sprachen wird import zum Flaschenhals, wenn man nicht fastcgi-artig arbeitet.
    Der von mir verwendete Webserver h2o hat einfache Konfigurationsdateien für mruby- und fast-cgi-Handler und eignet sich perfekt für lokale Skriptarbeit.
    h2o-fastcgi-Dokumentation
    Ein weiterer Vorteil ist, dass sich damit lokale Software erweitern lässt, wenn Kunden eigenen Custom-Code hinzufügen sollen.
    Früher hätte man dafür MCP verwenden müssen, aber jetzt reicht es, strukturierte Requests über CGI zu implementieren.

    • Als Endnutzer-Umgebung wäre es vielleicht auch eine interessante Idee, CGI-Programme an ein MCP-Frontend anzubinden.
      Auch MCP-Services ließen sich wahrscheinlich gut als CGI implementieren.
      Ich habe das Gefühl, dass ich mir die Spezifikation genauer ansehen sollte.

    • Bei fastcgi bleibt von den Vorteilen von cgi fast nichts mehr übrig, so die Frage.

  • Früher habe ich selbst C-Programme in Kombination mit CGI verwendet.
    Damals gab es keine über 100 Kerne und auch nicht viel RAM; es lief sogar mit maximal etwa 1 GB Speicher.
    Wenn das damals möglich war, bin ich sicher, dass es heute noch viel einfacher wäre.

    • 1995 nutzte Amazon C++-Executables als CGI.
      Als Lastverteilung nötig wurde, gab es Schwierigkeiten bei der Skalierung, aber bis dahin funktionierte das ziemlich gut.
      Zur Einordnung: Frontend und Backoffice waren zwei getrennte Executables.