Ich zeige in zwei Sätzen, warum die Wahl des Apache MPM den Durchsatz, die Latenz und die Stabilität unter hoher Last sichtbar beeinflusst. Dabei vergleiche ich Event MPM und Worker MPM konkret für lange Keep-Alive-Verbindungen, HTTP/2 und hohe Parallelität und leite daraus klare Tuning-Empfehlungen ab.
Zentrale Punkte
Damit du sofort die wichtigsten Aussagen greifst, fasse ich die Kernaussagen kurz zusammen und markiere entscheidende Stichworte fett. Aus diesen Punkten leite ich weiter unten konkrete Handgriffe und Konfigurationen ab, die ich praxistauglich erkläre. Ich bewerte beide MPMs konsequent unter realistischen Lastprofilen mit vielen Verbindungen. So erkennst du ohne Umwege, welches Modul in deinem Stack glänzt. Die Liste liefert die Abkürzung zu fundierten Entscheidungen im täglichen Betrieb.
- Event entkoppelt Idle-Keep-Alive von Request-Threads und skaliert bei vielen Verbindungen.
- Worker punktet bei kurzen Requests, bindet aber Threads bei langem Keep-Alive.
- HTTP/2 profitiert messbar von Event dank effizientem Multiplexing-Handling.
- Ressourcen: Event hält RAM/CPU pro aktiver Anfrage niedriger.
- Kompatibilität: Thread-sichere Module sind Pflicht, mod_php bleibt Prefork-Terrain.
Warum Worker und Event das Rennen machen
Ich setze im modernen Betrieb klar auf Threads, weil sie pro Verbindung weniger RAM belegen als Prozesse. Prefork bot früher Sicherheit mit nicht thread-sicheren Modulen, doch es skaliert bei vielen Verbindungen nur schwer. Heute dominieren Worker und Event, weil sie viele gleichzeitige Nutzer sauber handhaben. Das zahlt sich vor allem mit aktivem Keep-Alive und HTTP/2 aus, wo Verbindungen lange offen bleiben. Genau dort zeigt Event seine Stärken, da es Leerlaufverbindungen nicht auf wertvolle Request-Threads klemmt.
Apache Worker MPM: Architektur und Grenzen
Ich erkläre Worker als Hybrid aus Prozessen und Threads, bei dem jeder Kindprozess einen Listener-Thread und viele Server-Threads besitzt. Ein Request landet auf einem Thread, wird beantwortet und gibt danach den Thread wieder frei. Bleibt die Verbindung offen, bleibt derselbe Thread an diese Verbindung gebunden. Das verursacht Leerlauf, wenn viele Clients länger warten oder nur sporadisch kleine Requests senden. Wer Worker einsetzt, sollte daher Thread-Pools und Limits bewusst dimensionieren und kann dazu meine kurze Thread-Pool-Optimierung als Startpunkt heranziehen.
Apache Event MPM: Event-Loop erklärt
Ich beschreibe Event als Worker-plus-Event-Loop, also Listener-Threads, die Idle-Verbindungen parken. Der Listener nimmt neue Verbindungen an, übergibt aktive Anfragen an freie Worker-Threads und holt die Verbindung danach zurück. Auf diese Weise arbeiten Request-Threads nur, wenn Daten fließen. Hunderte oder tausende Clients dürfen deshalb offen bleiben, ohne die Threads zu blockieren. Genau dieses Parken macht Event bei typischen HTTP/1.1- und HTTP/2-Workloads so effizient.
Event vs. Worker: Unterschiede unter Last
Ich bewerte beide MPMs immer unter realer Last mit langen Keep-Alive-Zeiten. Worker erreicht schnell das Limit, weil Idle-Verbindungen Threads besetzen, die dann für neue Requests fehlen. Event hält die Thread-Pools frei und schiebt Idle-Verbindungen in den Event-Loop. So steigt die Zahl gleichzeitig bedienbarer Nutzer deutlich, während Latenzen stabil bleiben. Wer Entscheidungsgrundlagen braucht, vergleicht am besten konkrete eventgetriebene Servermodelle mit Thread-Pools in Lasttests.
Kompatibilität: Module und typische Setups
Ich prüfe zuerst die Module, denn Worker und Event verlangen Thread-Sicherheit. Klassische mod_php-Stacks passen nicht, weshalb Prefork hier weiterhin Sinn ergibt. Läuft PHP hingegen über PHP-FPM oder FastCGI, entscheide ich mich klar für Event. Das gilt ebenso für Reverse-Proxies zu App-Servern, Microservices oder Go/Node-Backends. In solchen Setups zeigen Worker und vor allem Event ihre Stärke ohne Kompromisse bei der Kompatibilität.
Konfiguration: Die wichtigsten Direktiven
Ich stelle die Schlüssel-Direktiven kompakt vor, damit du sie sicher einordnest und anpasst. MaxRequestWorkers limitiert die Zahl gleichzeitig bearbeiteter Anfragen; bei Event kannst du oft höher gehen, weil Idle-Verbindungen nicht blockieren. ThreadsPerChild definiert die Anzahl Threads pro Prozess; zu wenig senkt Durchsatz, zu viel stresst die CPU. ServerLimit setzt die Grenze für Prozesse und damit die Obergrenze für parallele Requests im Verbund. Mit KeepAliveTimeout steuerst du, wie lange Verbindungen offen bleiben; je höher der Wert, desto stärker profitiert Event.
Tabellarischer Vergleich: Worker vs. Event
Ich fasse die wichtigsten Eigenschaften in einer kompakten Tabelle zusammen, damit du Unterschiede sofort siehst. Sie ersetzt keinen Lasttest, aber sie strukturiert deinen Blick auf die zentralen Merkmale. Lies die Punkte von links nach rechts und ordne sie deinem Traffic-Profil zu. So findest du zügig das passende MPM für deine Architektur. Der Fokus liegt klar auf Skalierung, Ressourcenbedarf und Verhalten mit Keep-Alive.
| Kriterium | Worker MPM | Event MPM | Auswirkung |
|---|---|---|---|
| Keep-Alive-Handling | Thread bleibt an Verbindung gebunden | Idle-Verbindungen parkt der Event-Loop | Event hält Request-Threads frei |
| Ressourceneinsatz | Mehr gebundene Threads im Leerlauf | Weniger gebundene Threads bei Idle | Geringerer RAM/CPU pro aktiver Anfrage |
| Latenz unter Last | Steigt früher an | Bleibt länger stabil | Besseres Ansprechverhalten |
| HTTP/2-Tauglichkeit | Ordentlich | Sehr effizient | Vorteile bei Multiplexing |
| Konfiguration | MaxRequestWorkers, ThreadsPerChild, ServerLimit | Gleich, plus Event-Loop-Optimierung | Event erlaubt höhere Auslastung |
| Kompatibilität | Thread-sichere Module nötig | Ebenso, bevorzugt mit PHP-FPM | Prefork bleibt mod_php-Option |
Praxis: Tuning-Workflow und Messung
Ich starte immer mit einer sauberen Baseline aus Monitoring und Logdaten. Danach variiere ich MaxRequestWorkers und ThreadsPerChild schrittweise und messe Latenz, Fehlerquote und CPU-Load. KeepAliveTimeout teste ich in Stufen, weil die ideale Zeit stark vom Client-Verhalten abhängt. Ab hier lohnt ein Vergleich Event vs. Worker mit Tools wie ab, wrk oder JMeter. Erst wenn die Metriken sauber aussehen, fixiere ich die Profile und dokumentiere die Kennzahlen.
Wann Prefork sinnvoll bleibt
Ich greife zu Prefork, wenn zwingend nicht thread-sichere Module laufen müssen. Dann zählt Isolation pro Prozess mehr als Skalierung. Im Gegenzug akzeptiere ich einen deutlich höheren RAM-Bedarf pro Verbindung. Für Legacy-Anwendungen ohne Anpassungsmöglichkeit bleibt das oft der realistische Weg. Sobald ich aber PHP-FPM oder andere externe App-Server nutze, ziehe ich Event klar vor.
Webhosting-Kontext und Anbieterwahl
Ich achte im Hosting-Umfeld auf MPM-Profile, weil auf einer Maschine oft viele virtuelle Hosts laufen. Event bietet hier die effizienteste Ausnutzung der Ressourcen, besonders mit HTTP/2 und TLS. Verlangt mein Stack PHP-FPM, setze ich Event als Standard. Für Einordnung und Technik-Check hilft ein kurzer Vergleich Prefork, Worker, Event vor der finalen Wahl. Wer diese Hausaufgaben erledigt, erzielt spürbar bessere Antwortzeiten pro Euro.
Best Practices kompakt
Ich nutze konsequent PHP-FPM oder andere externe App-Server, damit Event sein Potenzial ausspielt. Danach stimme ich MaxRequestWorkers und ThreadsPerChild auf CPU-Kerne und RAM ab und prüfe harte Limits des Systems. Bei vielen Idle-Clients wähle ich Event, setze KeepAliveTimeout bewusst höher und überwache dabei Latenzen. Für Workloads mit sehr kurzen Requests und moderatem Keep-Alive reicht Worker, wenn Module thread-sicher bleiben. Ohne kontinuierliches Monitoring von Thread-Auslastung, Fehlern und Latenzen treffe ich keine finalen Entscheidungen.
Konkrete Konfigurationsbeispiele für Event und Worker
Ich liefere zwei minimalistische Profile, die ich als Ausgangspunkt nutze und dann anhand von Messwerten schärfe. Entscheidend: MaxRequestWorkers = ServerLimit × ThreadsPerChild. Ich rechne rückwärts vom RAM-Budget und dem Bedarf je Thread (inkl. Modulen, TLS, Buffern) und erhöhe schrittweise.
# Beispiel: Event MPM (HTTP/2, PHP-FPM)
ServerLimit 16
ThreadLimit 256
ThreadsPerChild 64
MaxRequestWorkers 1024
StartServers 4
MaxConnectionsPerChild 10000
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 15
# Optional und nur nach Messung anpassen:
# ListenBacklog 1024
# ThreadStackSize 1048576 # 1 MB, nur wenn Module es zulassen
# AsyncRequestWorkerFactor 2 # Event-Loop-Feintuning, meist Default belassen
# HTTP/2
Protocols h2 http/1.1
# H2MaxSessionStreams 100-200 # je nach Backend-Kapazität feinjustieren
# Beispiel: Worker MPM (kurze Requests, moderates Keep-Alive)
ServerLimit 8
ThreadLimit 256
ThreadsPerChild 50
MaxRequestWorkers 400
StartServers 4
MaxConnectionsPerChild 5000
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 3
Protocols http/1.1
Ich halte MaxConnectionsPerChild (Alias: MaxRequestsPerChild) ungleich 0, um schleichende Leaks einzufangen. KeepAliveTimeout setze ich bei Event bewusst höher, weil Idle-Verbindungen preiswert sind; bei Worker halte ich ihn niedrig, um Threads nicht zu blockieren.
HTTP/2-Feintuning mit Event
Ich berücksichtige bei HTTP/2, dass Browser wenige Verbindungen öffnen und viele Streams multiplexen. Dadurch verschiebt sich der Engpass weg von Verbindungsanzahl hin zu fairer Thread-Zuteilung und Backend-Kapazität. Mit Event bleiben Threads frei, solange ein Stream wartet; das glättet Latenzspitzen. Praktische Stellhebel:
- H2MaxSessionStreams: Ich bewege mich typischerweise im Bereich 50–200. Zu hoch erzeugt Head-of-Line-Effekte im Backend, zu niedrig verschenkt Parallelität.
- MaxRequestWorkers: Mit Event darf ich höher gehen, sofern RAM und CPU es tragen. Ich beobachte die 95./99.-Perzentile der Latenz bei steigendem Parallelismus.
- TLS: Mit ALPN und modernen Cipher-Suites senke ich Handshake-Kosten; Event profitiert zusätzlich, weil Idle-Phasen zwischen Stream-Bursts effizient geparkt werden.
Betriebssystem-Limits und Socket-Backlogs
Ich prüfe vor jedem Lasttest die Systemgrenzen, sonst limitiert nicht das MPM, sondern der Kernel. Für hohe Verbindungszahlen skaliere ich insbesondere:
- Dateideskriptoren: ulimit -n und systemd
LimitNOFILEerhöhe ich z. B. auf 65536 oder höher; Apache braucht FD pro Socket, Log, Pipe. - Backlog:
net.core.somaxconnundtcp_max_syn_backlogsetze ich passend (z. B. 1024–4096), damit die Accept-Queue nicht überläuft. - Portbereich (bei Reverse-Proxy):
ip_local_port_rangeweite ich (z. B. 10000–65000), wenn viele gleichzeitige Outbound-Verbindungen zu Backends bestehen. - FIN/Timeouts: Vorsicht bei
tcp_fin_timeout: zu aggressiv kann Verbindungsabbrüche verursachen; ich ändere nur mit Messgrundlage.
Ich dokumentiere jeden Kernel-Tweak samt Begründung und verifiziere ihn mit erneuter Lastmessung. Ohne Nachweis bleibt der Default meist der richtige.
Monitoring und Fehlersuche im Alltag
Ich aktiviere ExtendedStatus und nutze server-status, um die Scoreboard-Zustände zu lesen. Bei Event sehe ich viele Idle-/Keep-Alive-Sockets, ohne dass die Worker-Threads ausschöpft sind. Tritt im Error-Log „server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting“ auf, reagiert der Server bereits am Limit; ich erhöhe vorsichtig und beobachte RAM/CPU sowie die Fehlerquote.
- Messfelder: In den Access-Logs erfasse ich Antwortzeiten (z. B. %D/%T), status codes, Bytes; ich korreliere Peaks mit CPU/IO.
- Symptome bei Worker: Viele Keep-Alive-Verbindungen inaktiv, Threads bei 100 % belegt, steigende Latenz, 503/504 – Indiz für gebundene Threads.
- Symptome bei Event: Listener-Threads hoch ausgelastet, aber Worker-Threads frei – meist Netzwerk- oder Backendgrenze, nicht MPM.
- Graceful-Reload: Ich rolle Änderungen mit
apachectl -k gracefulaus, damit bestehende Verbindungen sauber abfließen.
Kapazitätsplanung: Von Kernen und RAM zu MaxRequestWorkers
Ich rechne pragmatisch: Wieviel RAM pro Thread plus Puffer will ich zulassen? Bei TLS, Filtern und gängigen Modulen kalkuliere ich konservativ mit einigen MB pro Thread. Dann setze ich MaxRequestWorkers so, dass Spitzenlast in den 95./99.-Perzentilen ohne Swap abgefahren wird. Auf CPU-Ebene gilt: Threads über Kernanzahl hinaus helfen nur, solange sie nicht dauernd laufzeitintensiv sind. Mit Event wage ich höhere Werte, weil Idle-Phasen kaum kosten.
- Daumenregeln: Start mit 32–64 Threads pro Prozess, 4–16 Prozesse; danach Messung und Anpassung.
- ThreadStackSize: Ist RAM knapp und Module lassen es zu, reduziere ich die Stackgröße (vorsichtig, mit Stresstest).
- MaxKeepAliveRequests: Ich belasse meist den Default; bei Chatty-Clients kann ein höherer Wert Overhead reduzieren.
Reverse-Proxy-Szenarien und Backend-Verbindungen
Ich setze Event besonders gern vor App-Backends ein, weil es Frontendsockets effizient parkt, während die eigentliche Arbeit im Backend stattfindet. Entscheidend ist dann das Pooling der Backendverbindungen (mod_proxy):
- Keep-Alive zum Backend: Aktiv belassen, um Handshakes zu sparen; Größe der Pools (max je Ziel) passend zur Backend-Kapazität wählen.
- Proxy-Timeouts: Zeitouts klar definieren, damit hängende Backends keine Frontend-Threads binden.
- HTTP/2 zum Backend: Wo möglich, nutze ich H2 (z. B. intern h2c) für weniger Verbindungen bei mehr Streams – Event harmoniert damit gut.
Ich beobachte gezielt Latenzanteile Frontend vs. Backend; steigt nur die Backendzeit, hilft MPM-Tuning allein nicht – dann muss ich Poolgrößen, Timeouts oder Backend-Ressourcen anpassen.
Rollout-Strategie und Migration von Worker zu Event
Ich migriere in klaren Schritten: Zuerst prüfe ich die Modulliste (apachectl -M) auf Thread-Sicherheit. Alles, was nicht thread-safe ist (klassisch mod_php), muss raus oder isoliert werden. Danach aktiviere ich Event, setze konservative Startwerte und fahre Lasttests auf Staging. Im Rollout beginne ich mit einer Teilmenge des Traffics (Canary), vergleiche Metriken und rolle erst dann breit aus.
- Kommandos: Distributionstypisch MPM-Module umschalten (z. B. a2dismod/a2enmod) und sauber neustarten.
- Fallback-Plan: Ich halte ein Worker-Profil bereit, falls sich ein Modul unter Event doch auffällig verhält.
- Dokumentation: Jede Änderung an Limits, HTTP/2-Parametern und Kernelwerten dokumentiere ich mit Vorher/Nachher-Messungen.
Sicherheit und TLS-Performance im Blick
Ich beachte bei TLS, dass Handshakes CPU-intensiv sind und unter Last die Latenz treiben können. Mit Session-Resumption und moderner Cipher-Auswahl reduziere ich die Kosten, während Event Leerlaufphasen effizient parkt. In Kombination mit HTTP/2 und ALPN vermeide ich zusätzliche Roundtrips. Wichtig: TLS-Puffer und OpenSSL-Parameter zählen zum RAM-Fußabdruck je Thread – ich berücksichtige sie in der Kapazitätsplanung.
Fehlertoleranz und Graceful Degradation
Ich plane für Überlast: Ist die CPU saturiert oder erreicht Apache MaxRequestWorkers, will ich keine Lawine an Retries. Ich setze klare Timeouts, aussagekräftige Fehlerseiten und Rate-Limits an vorgelagerten Proxies. Mit Event bleiben unter Druck mehr Threads für echte Arbeit frei, während Idle-Verbindungen geparkt sind – genau diese Reserve hält das System länger bedienbar, bis die Last wieder sinkt oder automatisches Scaling greift.
Kurz zusammengefasst
Ich setze im heutigen Betrieb auf Event, sobald mein Stack thread-sichere Module und PHP-FPM nutzt. Dieser Ansatz reduziert gebundene Threads bei Idle-Verbindungen, hält die Reaktionszeit stabil und steigert die Zahl parallel bedienter Nutzer. Worker bleibt eine solide Option für kurze Requests mit moderatem Keep-Alive, wenn Event aus organisatorischen Gründen nicht passt. Prefork reserviere ich für Setups mit nicht thread-sicheren Modulen oder altem Code. Mit klaren Lasttests, sauberem Tuning der Direktiven und sichtbarem Monitoring bringe ich Apache reproduzierbar auf Turbo-Drehzahl.


