- Durch ein Experiment wurde bestätigt, dass CGI-Programme, die in der frühen Web-Ära weit verbreitet waren, auf moderner Hardware noch immer hohe Leistung erzielen können
- CGI verarbeitet Requests pro Prozess, wodurch die Speicherverwaltung automatisch erfolgt und Deployments einfach bleiben
- Benchmark-Ergebnisse zeigen, dass selbst ein gewöhnlicher Server mit 16 CPU-Threads mehr als 2.400 Requests pro Sekunde und über 200 Millionen Requests pro Tag verarbeiten kann
- Beispielcode für guestbook.cgi, geschrieben in Go und SQLite, sowie ein Dockerfile wurden als Open Source veröffentlicht
- CGI wird heute nicht mehr häufig verwendet, zeigt hier aber, dass es nach wie vor eine praktische und moderne Alternative sein kann
CGI-Programme und ihre Funktionsweise
- Anfang der 2000er waren CGI(Common Gateway Interface)-Programme der wichtigste Weg, dynamische Websites zu bauen
- Die meisten wurden in Perl oder C geschrieben; zur Leistungssteigerung fiel die Wahl teils auf C
- Das CGI-Konzept ist einfach, aber wirkungsvoll
- Der Webserver setzt Request-Metadaten (HTTP-Header, Query usw.) als Umgebungsvariablen
- Er startet einen separaten Prozess und führt das CGI-Programm aus
- Der Request-Body wird über stdin übergeben
- stdout des Programms wird als HTTP-Response erfasst
- stderr-Ausgaben werden an das Server-Error-Log weitergeleitet
- Wenn das Programm den Request beendet, endet auch der Prozess, sodass File-Deskriptoren und Speicher automatisch freigegeben werden
- Aus Sicht von Entwicklern war auch das Deployment neuer Versionen extrem einfach: Es reichte, die Datei in das Verzeichnis
cgi-bin/ zu kopieren
Hug of death (Traffic-Ansturm)
- Anfang der 2000er waren bei Webservern meist Umgebungen mit 1–2 CPUs und 1–4 GB RAM üblich
- Da der Apache-Webserver strukturell für jede Verbindung einen httpd-Prozess forkte, stieg der Speicherbedarf bei vielen Verbindungen an
- Mehr als 100 gleichzeitige Verbindungen waren oft schwer zu bewältigen, und schon ein Link von einer bekannten Website konnte einen Server leicht überlasten
- ( Slashdot Effect : Wenn damals ein Link auf dem populären Slashdot erschien, strömte der Traffic herein. Vergleichbar damit, heute auf der Startseite von Hacker News zu landen)
CGI in modernen Serverumgebungen
- Heute gibt es bereits Server mit 384 CPU-Threads, und selbst relativ kleine VMs können 16 CPUs bereitstellen
- CPU- und Speicherleistung haben sich massiv verbessert
- Da CGI-Programme auf separaten Prozessen basieren, können sie Multicore-Systeme ganz natürlich nutzen
- Deshalb wurde per Benchmark direkt getestet, wie schnell CGI-Programme auf moderner Hardware tatsächlich sind
- Das Experiment wurde auf einem Server mit AMD 3700X (16 Threads) durchgeführt
Zentrale Benchmark-Ergebnisse
- Ein einfaches CGI-Programm wurde sowohl unter Apache als auch mit einem Go-
net/http-Server getestet
-
Beschreibung des Programms guestbook.cgi
- Mit dem HTTP-Load-Generator
plow wurden 100.000 Requests mit 16 Verbindungen ausgeführt
- Selbst auf gewöhnlicher Hardware sind mehr als 2.400 Requests pro Sekunde möglich, also über 200 Millionen Requests pro Tag
- CGI ist heute zwar nicht Mainstream, kann aber weiterhin auch im realen Betrieb eingesetzt werden
-
Write-Benchmark unter Apache
- Verarbeitung von rund 2468 Requests pro Sekunde bei einer durchschnittlichen Response-Latenz von 6,47 ms
- 100.000 POST-Requests wurden in nur 40,5 Sekunden verarbeitet
- Die meisten Requests wurden innerhalb von 7 ms beantwortet, nur sehr wenige überschritten 100 ms
- Damit wurde in der Praxis eine hohe Write-Performance nachgewiesen
-
Read-Benchmark unter Apache
- Verarbeitung von rund 1959 Requests pro Sekunde bei einer durchschnittlichen Response-Latenz von 8,16 ms
- 100.000 GET-Requests wurden in 51 Sekunden verarbeitet
- Mehr als die Hälfte der Requests blieb unter 8 ms, die maximale Latenz lag nur bei 31 ms
- Auch die Read-Performance ist klar ausreichend
-
Write-Benchmark mit Go net/http
- Verarbeitung von rund 2742 Requests pro Sekunde bei einer durchschnittlichen Response-Latenz von 5,83 ms
- 100.000 POST-Requests wurden in 36,4 Sekunden verarbeitet
- Der Durchsatz lag im Schnitt bei 2.742 RPS, die mittlere Latenz bei 5,8 ms und damit numerisch über Apache
- Mehr als 95 % der Requests wurden innerhalb von 6 ms verarbeitet
- CGI unter Go verfügt ebenfalls über ausreichend Performance für den Praxiseinsatz
-
Read-Benchmark mit Go net/http
- Verarbeitung von rund 2469 Requests pro Sekunde bei einer durchschnittlichen Response-Latenz von 6,47 ms
- 100.000 GET-Requests wurden in 40,4 Sekunden verarbeitet
- Die meisten Requests konnten innerhalb von 7 ms bedient werden
- Sowohl Read-Durchsatz als auch Response-Geschwindigkeit sind ähnlich gut oder besser als bei Apache
Fazit und Link
- CGI-Programme bieten auf aktueller Hardware Vorteile wie extrem schnelle Nebenläufigkeit, einfaches Deployment und automatische Ressourcenfreigabe durch das Betriebssystem
- Im Vergleich zu modernen Frameworks sind sie äußerst simpel, können für Dienste einer bestimmten Größenordnung aber auch heute noch produktiv genutzt werden
- Das Gästebuch-Beispiel und die Benchmark-Daten sind im folgenden GitHub-Repository veröffentlicht
https://github.com/Jacob2161/cgi-bin
4 Kommentare
Krass … Werden wir wirklich wieder CGI verwenden?? Haha
Wow … Aus welcher Zeit CGI noch stammt …
Es gibt offenbar ein Update vom 7.7.
Serving a half billion requests per day with Rust + CGI
500 Millionen Requests …
Hacker-News-Kommentare
Erinnerung an Umgebungen aus den 1990ern, in denen in C geschriebene CGI-Programme wirklich sehr schnell waren; zugleich wird anerkannt, dass die hohe Fehleranfälligkeit ein Nachteil war; moderne Sprachen wie die im Artikel erwähnten Go-Programme oder Nim wirken auf
localhostextrem schnell und latenzarm, solange keine Datenbankverbindung nötig ist, fast wiefork & execbei einem CLI-Utility; verglichen mit Netzwerklatenz waren die Kosten nahezu vernachlässigbarAllerdings wird eine Kultur erwähnt, die leicht von bestimmten Technologien abhängig wird; wenn man sich etwa an Sprachen mit hohen Startkosten wie den Python-Interpreter gewöhnt, braucht man irgendwann ein Multi-Shot- oder persistentes Modell
Das One-Shot-Modell des frühen HTTP entstand ursprünglich aus dem Problem, dass FTP-Server nicht genug Speicher hatten, um Hunderte inaktive Login-Sitzungen lange offen zu halten
Es wird die Möglichkeit eines hervorragenden Systemdesigns erwähnt, wenn man bei CGI Pre-Forking (das Latenz verbergen kann) mit sicheren Sprachen wie Rust kombiniert; TLS-Terminierung kann bequem von einem multithreaded Webserver (oder einer Schicht wie CloudFront) übernommen werden
stdinzu lesen und nachstdoutzu schreiben; WebSockets erhöhen die Komplexität zwar etwas, aber nicht in besorgniserregendem Maßfork()und die Risiken von C zu vermeiden; nun könne man wieder zur Einfachheit zurückkehrenErfahrung, seit der CGI-Zeit zu entwickeln und dadurch eine starke Abneigung gegen kurzlebige Subprozesse entwickelt zu haben
Erläuterung des Hintergrunds, dass PHP und FastCGI geschaffen wurden, um dem Performance-Problem zu entkommen, für jeden Web-Request einen neuen Prozess zu erzeugen
Dank der Fortschritte der aktuellen Hardware wurde klar, dass die Kosten des Prozessstarts in der Praxis gar kein großes Problem mehr sind
Hinweis auf dieses Benchmark, das 2000 Requests pro Sekunde verarbeiten kann, und dass sich schon bei einigen Hundert Requests moderne Umgebungen leicht über mehrere Instanzen skalieren lassen
Zustimmung zu der Meinung, AWS Lambda sei eine Wiedergeburt des CGI-Modells; eine ziemlich treffende Analogie
Wären CGI-Skripte als statisch gelinkte C-Binaries bereitgestellt worden, mit Blick sogar auf die Dateigröße, wäre die Enttäuschung wohl geringer ausgefallen
CGI war in Umgebungen mit niedriger Last finanziell und leistungsmäßig keine große Belastung
Vor dem Erscheinen von Go war es in den 2000ern sowohl in puncto Sicherheit als auch Entwicklungsaufwand schwierig, CGI-Programme in C/C++ zu bauen
Heute leben wir in einer Zeit, in der Server 384 CPU-Threads haben und selbst kleine VMs 16 CPUs besitzen können
Auf solcher Hardware kann man mit Kestrel problemlos Billionen Requests pro Tag verarbeiten
Eine ähnliche Entwicklungserfahrung wie mit PHP lässt sich über String-Interpolation-Operatoren bieten
Mit LINQ und
String.Join()lassen sich HTML-Tabellen und verschachtelte Elemente einfach templatisierenDie eigentliche Schwierigkeit besteht darin, zu wissen, wie man das Minenfeld des Ökosystems aus MVC/Blazor/EF geschickt umgeht
Man kann sogar das gesamte Programm als eine einzige Datei über die CLI ausführen, aber ohne das Stichwort "Minimal APIs" gerät man leicht in ein Labyrinth aus irreführender Dokumentation
Ein Vorteil von CGI ist, dass man in Multi-Tenant-Umgebungen keine neuen Isolationsprimitiven aufbauen muss
rlimitlassen sich lang laufende Requests zwangsweise beendencgrouplassen sich Speicher-, CPU- sowie Festplatten-/Netzwerk-I/O-Ressourcen fair pro Tenant zuweisenDank CGI wurde Perl für schnelle Startzeiten optimiert
Beim Ausführen des Befehls
time perl -e ''zeigt sich: Perl braucht 5 ms,python333 ms und Ruby 77 ms; daran sieht man die schnelle Startzeit von Perl#!/bin/tcc -runaus dem tcc-mob-Branch 1,3-mal schneller als Perl sindgetlinenoch kein Standard war und man Third-Party-C-Libraries mit einigen Hundert bis einigen Tausend Zeilen schriebWer Apache Tomcat 11 verwendet, kann
.jsp-Dateien oder ganze Java-Servlet-Anwendungen (.war) einfach persshhochladen, und sie funktionieren sofortMaximale Performance durch eine gemeinsame JVM
Auch DB-Connection-Pools, Caches usw. können zwischen Anwendungen geteilt werden
Eine wirklich beeindruckende Erfahrung
Es hängt von den tatsächlichen Nutzungsmustern ab
Für große Services ist das hervorragend, aber wenn 50 kleine Anwendungen jeweils nur einige Hundert Requests pro Tag verarbeiten müssen, ist der Speicher-Overhead von Tomcat im Vergleich zu Apache/Nginx auf CGI-Skriptbasis viel zu groß
Nostalgische Bemerkung, dass die Zeit vermisst wird, in der Deployment nur daraus bestand, Dateien einfach zu kopieren
Bedauern darüber, warum Deployment-Prozesse so kompliziert geworden sind
Geteilte Erfahrung, dass Jetty auch heute noch mit Freude für Backend-Web-Apps genutzt wird
Eindruck, dass der Tomcat/Jakarta-EE/JSP-Stack überraschend robust ist
Man kann wie bei PHP HTML und Code vermischen, aber auch reine Java-Routen verwenden
Unterstützung für WebSockets, und durch das Single-Process-/Multithread-Modell auch stark für Echtzeitkommunikation
Bei Bedarf können Daten zwischen Requests geteilt werden; JSP-Code ist standardmäßig auf den Request-Scope beschränkt
Deployment ist wirklich einfach: Sobald neue Dateien in das Verzeichnis
webappshochgeladen werden, lädt Tomcat die neue App automatisch und entlädt die bestehendeEin Nachteil ist, dass Lecks im Classloader dazu führen können, dass Garbage Collection fehlschlägt, ein Schicksal des Single-Process-Modells
Erstellung des Visualisierungstools für Apache-Requests ibrahimdiallo.com/reqvis
Zweifel daran, dass man heute in immer komplexere Architekturen geht; möglicherweise könnte man mit guter Hardware auch bestehende Technik weiterhin ausreichend nutzen
Bei der Frage nach dem Design eines Systems, das Millionen Menschen in Echtzeit über Aktienkurse informiert, dachte man zunächst an komplexe Stream-Strukturen wie Kafka oder Pub/Sub, erwog am Ende aber auch die einfache Methode, statische Dateien auf dem Server abzulegen
Neugier auf die tatsächlichen Betriebskosten eines solchen Ansatzes
Hervorgehoben wird, dass es einer serverless Architektur ähnelt, aber viel einfacher und günstiger ist
Bedauern darüber, dass man statt einer Neubewertung solcher traditionellen Strukturen einfach nur das neue Paradigma der "serverless functions" geschaffen hat
Na gut, CGI ist das eine, aber die Reaktion auf JSP überrascht mich echt, haha.
Ist JSP etwa schon zu so einem antiken Relikt geworden?