Apache Prozesse - Zusammenhang

Ahoi

New Member
Hi,

ich lese mich gerade in das Thema Apache Tuning ein.
So richtig schlau werde ich nicht zum Thema Prozesse.

Wie verhält sich den das Ganze mit Prozessen und Threads und maxClients?

Wenn maxClients auf 100 eingestellt ist und ich 400 Leute bedienen will muss ich dann 4 Prozesse starten? Oder wie muss ich mir das vorstellen?

Viele Grüße

Ahoi.
 
Zunächst einmal musst Du Dir grundsätzlich klar machen, wie Apache Prozesse und Threads verwaltet. Ich gehe hier mal von mpm_worker aus, das mittlerweile wohl am häufigsten eingesetzt wird.

mpm_worker arbeitet so, dass ein Pool an Prozessen bereit gestellt wird. In jedem Prozess gibt es wiederum einen Pool an Worker-Threads. Jeder Worker-Thread kann einen Client zur Zeit handlen.

Konfigurierbar ist an dieser Konstellation (fast) so gut wie alles - wie groß der Thread-Pool mindestens sein muss, wie groß er maximal werden darf, wie er sich auf die Prozesse verteilen soll, ob Threads nach einer bestimmten Anzahl bedienter Anfragen abgeknipst und neu erzeugt werden sollen (z. B. um die Auswirkungen von Memory Leaks zu limitieren), nach welcher Strategie der Thread-Pool zu Stoßzeiten vergrößert oder in lastschwachen Zeiten wieder verkleinert wird, etc.

Genau beschrieben (v. a. die genaue Konfigurationssyntax) ist das in der Apache-Dokumentation, sogar auf deutsch: http://httpd.apache.org/docs/2.2/de/mod/worker.html
 
Danke für die Antwort.
Die Doku hatte ich vor der Frage gelesen. Und nicht ganz verstanden.

Ich kapier jetzt nicht was besser ist:
1 Prozess mit 400 maxClients oder
4 Prozesse mit 100 max Clients.


Ich habe die Doku jetzt so verstanden:
Beim Start des Apache wird ein Eltern Prozess gestartet.
Dieser Startet dann Kindprozesse. (StartServers).
Jeder dieser Prozesse kann z.b. 100 MaxClients bedienen.
Passt das soweit?
 
Last edited by a moderator:
Ich kapier jetzt nicht was besser ist:
1 Prozess mit 400 maxClients oder
4 Prozesse mit 100 max Clients.
http://httpd.apache.org/docs/2.2/de/mod/worker.html said:
Die maximale Anzahl Clients, die gleichzeitig bedient werden kann (d.h. die maximale Gesamtzahl der Threads in allen Prozessen), wird mit der Direktive MaxClients festgelegt.
In beiden Fällen wäre also MaxClients 400 die entsprechende Direktive. 400 Threads in einem Prozess halte ich aber nicht für wirklich sinnvoll.

Zur Begründung muss man sich anschauen, wie Threads sich von Prozessen unterscheiden. Threads sind immer Bestandteil eines Prozesses. Während unterschiedliche Prozesse auch unterschiedliche Prozessräume (DATA, TEXT, etc. Speichersegmente) haben, teilen sich alle Threads innerhalb eines Prozesses diese Ressourcen.

Daher bedeutet es zunächst mehr Overhead, wenn mehr Prozesse existieren - für jeden muss ja ein eigener Prozessraum erzeugt werden; bei einem Kontext-Wechsel auf der CPU müssen auch die zugehörigen Speichersegmente de-/aktiviert werden. Ein Thread-Kontextwechsel innerhalb eines Prozesses unterliegt dieser Einschränkung nicht, hier wird (in der Theorie) nur der Instruction Pointer neu gesetzt, und weiter geht's.

In der Praxis gibt es aber deutliche Einschränkungen. Da Threads innerhalb eines Prozesses dieselben Speicherbereiche benutzen, müssen kritische Operationen wie z. B. das Reservieren von Speicherblöcken auf diesem gemeinsam genutzten Speicherbereich durch geeignete Mechanismen geschützt werden (bei Threads geschieht dies i. d. R. durch einen Mutex). Im Klartext: während ein Thread eines Prozesses eine kritische Operation durchführt, darf kein anderer Thread desselben Prozesses (der ev. auf einem anderen CPU-Kern läuft) dieselbe kritische Operation ausführen.

Je mehr Threads es innerhalb eines Prozesses gibt, desto höher ist also die Wahrscheinlichkeit, dass ein Thread einen anderen in der Ausführung behindert. Es kommt zwar dadurch bei sauberer Programmierung nicht zu einem Deadlock, aber es bildet sich ein Stau.

Eine genaue Grenze lässt sich da nicht ziehen, da diese stark von der Nutzung gemeinsamer Ressourcen abhängt. Ein häufig genannter Daumenwert für Linux-Systeme lautet 32: Bei dieser Anzahl Threads innerhalb eines Prozesses kommt es noch nicht zu so gravierenden Behinderungen, dass die Leistung der Threads in Summe wieder abnimmt.

Ich würde daher empfehlen, MaxClients insgesamt erst mal deutlich zu limitieren - 400 Threads können auf handelsüblichen Systemen sowieso nicht parallel laufen. Wenn so viele Threads im Status Wait hängen, dauert es ohnehin ziemlich lange, bis ein bestimmter Thread wieder vom Scheduler drangenommen wird. Irgendwo ist der Punkt dann erreicht, wo der anfragende Client besser in der Queue gehalten wird (steuerbar über ListenBackLog), bis ein Thread "frei" ist und sich des Clients annehmen kann.

Ich würde mit den Standard-Werten oder sogar leicht darunter starten (MaxClients <= 100) und dann dem Server ordentlich Feuer machen (ab, Siege & Co.). Dann langsam in den Bereich reinsteigern, wo Du tatsächlich Deine Spitzen-Hitrate erwartest. Dabei würde ich darauf achten, dass pro Child nicht mehr als 24 Threads gestartet werden (25 ist bei mpm_worker default und auch sinnvoll).

Bei den Versuchen dann immer die durchschnittliche Antwortzeit und den Ressourcenverbrauch (Anzahl Prozesse und Threads) im Auge behalten - so kann man sich mal schnell eine Load im 3stelligen Bereich erzeugen und/oder sogar den OOM-Killer auf den Plan rufen. Genau das ist ja aber die Situation, die man beim Tuning vermeiden will - es muss also immer Luft im System sein. Wenn damit die Antwortzeiten bei den erwarteten Hitrates immer noch in einem unbefriedigenden Bereich liegen, hilft eigentlich nur noch horizontale Skalierung.

Ich habe die Doku jetzt so verstanden:
Beim Start des Apache wird ein Eltern Prozess gestartet.
Dieser Startet dann Kindprozesse. (StartServers).
Soweit korrekt.
Jeder dieser Prozesse kann z.b. 100 MaxClients bedienen.
Passt das soweit?
Nein. Alle Kind-Prozesse zusammen können 100 MaxClients bedienen. Jeder einzelne Kindprozess bedient maximal ThreadsPerChild Clients. Wenn jetzt die maximale Anzahl an Kindprozessen durch ServerLimit ebenfalls limitiert ist, muss man sehr genau darauf achten, dass sich die gesetzten Limits nicht gegenseitig widersprechen.

Beispiel:
Code:
MaxClients 600
ServerLimit 10
ServerThreads 10
ist natürlich Blödsinn - 10 Prozesse mit je 10 Threads können eben maximal 100 Clients gleichzeitig verarzten, und nicht 600.

Sorry, ist ein bisschen länger geworden - hoffe, es ist trotzdem noch einigermaßen verständlich. Wenn nicht, einfach weiterbohren :)
 
Vielen Dank das Du Dir Zeit genommen hast und das sehr gut erklärt hast.
Jetzt kommt mir ein wenig Licht ins Dunkel.

Gruß
Ahoi
 
Hi daemotron,

auch von meiner Seite aus ein dickes Danke für die ausführlichen und guten Hintergrundinfos!

Vielleicht kann ich auch noch ein Frage bzgl. der Nomenklatur hier ranhängen:

Code:
<IfModule worker.c>
  StartServers          n 
  MaxClients            n
  MinSpareThreads    n
  MaxSpareThreads   n  
  ThreadsPerChild       n
  MaxRequestsPerChild   n
</IfModule>

StartServers sind also Prozesse und Childs sind ebenfalls im Sinne von Prozessen gemeint?

1) Könnte man das auch so sinngemäß schreiben (nur zum Verständnis)?

Code:
<IfModule worker.c>
  Start[B]Processes[/B] n 
  MaxClients            n
  MinSpareThreads     n
  MaxSpareThreads     n  
  ThreadsPer[B]Process[/B]       n
  MaxRequestsPer[B]Process[/B]   n
</IfModule>

Ausserdem gehe ich davon aus, dass sich die erzeugte Prozessanzahl (Servers/Childs) durch die Anzahl der ThreadsPerChild und MaxClients indirekt regelt?

Code:
MaxClients 200
ThreadsPerChild 25

Ergibt 8 Apache-Prozesse (ohne Elternprozess), die die Requests abarbeiten (die Direktive StartServers ist ja nur für den 1. Start wichtig, damit nicht gleich bei 100 gleichzeitigen Request die neuen Prozesse gespawnt werden müssen, sondern nach einem Apache-Neustart schon da sind). Richtig?
 
Last edited by a moderator:
StartServers sind also Prozesse und Childs sind ebenfalls im Sinne von Prozessen gemeint?
Korrekt. Die Nomenklatur in der Apache-Konfiguration ist hier leider nicht durchgängig.

1) Könnte man das auch so sinngemäß schreiben (nur zum Verständnis)?

Code:
<IfModule worker.c>
  Start[B]Processes[/B] n 
  MaxClients            n
  MinSpareThreads     n
  MaxSpareThreads     n  
  ThreadsPer[B]Process[/B]       n
  MaxRequestsPer[B]Process[/B]   n
</IfModule>
Sinngemäß ja. Wobei die Bezeichnung Child durchaus korrekt ist, da sich die Werte immer nur auf die Kindprozesse beziehen.

Ausserdem gehe ich davon aus, dass sich die erzeugte Prozessanzahl (Servers/Childs) durch die Anzahl der ThreadsPerChild und MaxClients indirekt regelt?
Indirekt ja. Die Anzahl laufender Prozesse kann aber auch größer werden als MaxClients / ThreadsPerChild - das hängt mit dem Lebenszyklus von Prozessen zusammen und damit, ob Apache mit MaxSpareThreads und/oder MaxRequestsPerChild dazu angehalten wird, regelmäßig überzählige Prozesse zu recyclen.
 
Danke.

Ich halte mich ja nicht für doof, allerdings empfinde ich diese Zusammenhänge als äusserst sperrig zu verstehen. Trotz lesen der Dokumentation und lesen von Fachliteratur. Wobei man da manchmal den Eindruck hat, dass die Autoren auch bloss die Apache-Dokumentation widerkäuen und die tiefergehenden Zusammenhänge selbst nicht auf dem Schirm haben.
 
Ich halte mich ja nicht für doof
Ich auch nicht :)

Wobei man da manchmal den Eindruck hat, dass die Autoren auch bloss die Apache-Dokumentation widerkäuen und die tiefergehenden Zusammenhänge selbst nicht auf dem Schirm haben.
Meine Behauptung: richtig klar werden diese Zusammenhänge erst, wenn man selbst mal einen netzwerkenden, multiplexenden Daemon zusammengecoded hat - und zwar nicht in Perl o. ä, sondern schön "zu Fuß" in C, mit den Standard-Funktionen der libc. Zumindest bei mir war's so.

<rant>
Dabei fällt einem dann auch auf, dass die Apache-Entwickler einige schwierige Entscheidungen aus dem Code weg und in die Konfiguration delegiert haben - und damit vom Entwickler an den Admin. Besonders krasses Beispiel hierfür ist der Konfigurationsparameter ListenBackLog, genauso aber auch ThreadStackSize oder SendBufferSize. Diese Parameter können eigentlich nur sinnvoll gesetzt werden, wenn man den Code von Apache zumindest in den betreffenden Bereichen gut kennt.

Bei solchen Parametern gibt es für den Entwickler grundsätzlich drei Möglichkeiten:
  • Er macht sich selbst Gedanken und wählt einen sinnvollen, für die meisten Einsatzzwecke tauglichen Wert
  • Er konstruiert Algorithmen, die aufgrund der Betriebsumgebung, Konfiguration etc. oder sogar im laufenden Betrieb optimierte Werte für diese Parameter ermitteln
  • Er überlässt es dem Admin, geeignete Werte zu ermitteln und festzulegen
a) ist die am häufigsten gewählte Lösung, die für die breite Masse meist gut funktioniert, Extremanwendungsfälle an beiden Rändern aber außen vor läßt. b) ist der optimale Ansatz, der aber hohe Anforderungen an den Programmierer stellt und oft genug trotzdem schief geht. c) macht dem Programmierer am wenigsten Mühe, stellt den Admin aber vor große Herausforderungen.

Bei Apache ist es eine Kombination aus a) und c) - der Durchschnittsadmin sollte die Finger von diesen Parametern lassen und die Defaults verwenden. "Extremsportler" müssen sich einiges anlesen, können dann aber wirklich das Optimum aus der Software herausholen.

Ich persönlich bezweifle allerdings, dass man mit diesen Parametern gegenüber gut gewählten Defaults wirklich noch messbar Performance herausholen kann - in den meisten Fällen spielt man sich dabei mehr kaputt, als man gewinnen kann...
</rant>
 
Da könnte auf jeden Fall was dran sein :)

Ich habe mir mal einen Daemon angesehen, der in Ruby geschrieben war (TCP Socket). Aber da ist man ja auch nur wenig im Dreck wühlen...

Vielleicht noch eine letzte Frage: Die Direktive MaxRequestsPerChild in Verbindung mit FastCGI-Backends - die FastCGI-Prozesse haben ja einen eigenen Lebenszyklus.

Wenn ein Apache-Childprozess einen solchen initiiert (also fcgi) - werden diese dann auch nach der Anzahl der Requests terminiert oder läuft das völlig unabhängig voneinander?

Also Request -> Apache-Child -> Thread -> fcgi und dann nach n MaxRequestsPerChild werden Apache-child und fcgi terminiert?
 
Die FCGI-Prozesse sind unabhängig von den Apache-Workern. Apache startet einen Prozeß, der die Steuerung der FCGI-Prozesse übernimmt und quasi parallel dazu die Worker-Prozesse für die HTTP-Anfragen. Das Beenden eines Worker-Prozesses hat damit keine Auswirkungen auf die FCGI-Prozesse. Das kann man mit pstree schön sehen:

Code:
% pstree
init─┬─apache2─┬─apache2───php5-cgi
     │         ├─6*[apache2───26*[{apache2}]]
     │         └─cronolog
     ...

Der Apache-Master hat den Steuerprozeß mit einem als FCGI laufenden PHP5-Prozeß. Daneben sind 6 Prozesse mit jeweils 26 Threads (25 Worker + 1 Dispatcher) und ebenfalls der fürs Logfile zuständige cronolog.
 
Alles klar. Sieht bei mir ähnlich aus.

Dann steuere ich mit der http.conf Direktive nur die http-Request-Threads (Worker)?!

Wann beendet sich denn der fcgi-Steuerprozess? Die fcgi-Threads gehen irgendwann kaputt bzw. werden respawnt. Bleibt der Steuerprozess bis zum nächsten Apache-Restart am Leben? Oder werden auch die Requests gezählt, die an das fcgi-Backend durchgereicht werden?
 
fcgid-Prozesse werden nur Teilweise über Apache gesteuert. Nämlich nur dann wenn sich der Apache-Child beendet.
Ansonsten hat fcgid seinen eigene Scheduler zum Beenden von Idle-Threads bzw. wenn ausreichend Request (MaxRequestsPerProcess) verarbeitet sind.
Ein Problem dieser Art von IPC ist, dass es ggf. fcgid-Zombies entstehen können, die eine Zeit lang im Speicher bleiben. Und auch das Worker-Childs ggf. sich nicht beenden weil ein Thread noch mit einem fcgid verbunden ist.
Diese Dinge sind "natürlich" und erledigen sich mit der Zeit jeweils selbst.
Nachteil ist eben, dass in dieser Zeit Speicher nicht frei gegeben wird.
Aber damit kann man heutzutage wohl leben. ;)

huschi.
 
Schon mal über die Bedeutung von MaxRequestsPerChild nachgedacht? ;)

Ja, weiter oben in Posting Nr. 10 in diesem Thread:

Also werden die Requests, die an das fcgi-Backend durchgereicht werden, von dem fcgi-"Eltern"prozess mitgezählt und dann greift diese Direktive auch hier? Also nicht nur für die statischen Request wie Bilder, CSS-Dateien etc.?
 
Nein, fcgid ist ein eigener Daemon mit einem eigenen Speicher-Modell (welches dem Apache-Prefork-MPM ähnlich ist). Allerdings setzt man i.d.R. beim fcgid die Anzahl der zu forkenden Prozesse auf 0 damit er sich nicht forkt sondern nur einen Prozess aufbaut. Denn ein geforkter fcgid hat deutlich mehr (hässliche) Nebenwirkungen. Aber das führt hier gerade zu weit. Wichtig ist zu verstehen, dass der fcgid wiederum ein Server-Prozess ist.

Und dieser Daemon hat seine eigenen Einstellungen, anhand deren er selbstständig regelt wie lange er lebt: IdleTimeout, ProcessLifeTime, MaxProcessCount.

Zwischen einem Apache-Thread (Thread im Worker-Modell) und dem fcgid besteht eine Verbindung (meist über einen Unix-Socket) über die die Verbindung gehalten wird.

Wir haben also auf beiden Seiten der Verbindung jeweils einen Server-Prozess der beendet werden kann bzw. sich selbst beendet.

Das gilt natürlich nur für PHP-Scripte. Statische Inhalte gehen natürlich nicht durch den fcgid.

huschi.
 
Huschi, das ist mir alles klar, also wie die Kommunikation grundsätzlich läuft und dass man das fcgi-Backend (den Daemon) separat konfiguriert. Mir geht es aber um die apacheseitige Prozesskontrolle der Kind-Prozesse, die den fcgid aktivieren.

Wir haben also auf beiden Seiten der Verbindung jeweils einen Server-Prozess der beendet werden kann bzw. sich selbst beendet.

Ich habe das gerade mal ausprobiert für eine fcgi/php-Seite: MaxRequestsPerChild auf 1 gesetzt.

Wenn ich das Log taile, sehe ich zwar, dass der php-starter gestartet wird, aber nach einigen Requests nicht wieder heruntergefahren bzw. neu gestartet wird. Es scheint also, dass man in der Apachekonfig über MaxRequestsPerChild nichts erreichen kann.

Wenn ich jedoch die fcgid.conf anfasse und den IdleTimeout auf 5 Sekunden stelle, kann man das Sterben des Prozesse gut beochbachten:

Code:
[Fri Oct 15 16:54:57 2010] [notice] mod_fcgid: call /var/www/test/htdocs/index.php with wrapper /var/www/test/conf/php-fcgi-starter
[Fri Oct 15 16:55:00 2010] [notice] mod_fcgid: process /var/www/test/htdocs/index.php(554) exit([B]idle timeout[/B]), terminated by calling exit(), return code: 0

Bleibt also die Frage: Was bzw. wie kontrolliert die Apache-Prozesse, die mit dem fcgi-Backend kommunizieren? MaxRequestsPerChild ist es offensichtlich nicht.
 
Last edited by a moderator:
Mir geht es aber um die apacheseitige Prozesskontrolle der Kind-Prozesse, die den fcgid aktivieren.
Dann lass bei der Frage doch den fcgid weg. Schließlich zählt der Child jeden Request, egal was es ist. (Einschränkung: nachfolgende Request in der selben Connection (Stichwort "Keep-Alive") werden nicht als neuer Request gezählt.)

Ich habe das gerade mal ausprobiert für eine fcgi/php-Seite: MaxRequestsPerChild auf 1 gesetzt.
Darf ich fragen, wie Du diesen rein global zu setzenden Parameter auf nur eine Seite beschränkt hast?

Wenn ich jedoch die fcgid.conf anfasse und den IdleTimeout auf 5 Sekunden stelle, kann man das Sterben des Prozesse gut beochbachten:
Ich sehe in dem Logfile-Ausschnitt lediglich die fcgid-Prozesse sterben.

Irgendwie wird mir ist nicht ganz klar, was Du überhaupt wissen willst und was das Experiment bewirken sollte. Deine abschließende Frage bringt ebenfalls keine Erleuchtung.
Aber evtl. stolperst Du genau über den o.g. Zombie-Effekt. Dadurch das eine IPC-Verbindung per Unix-Socket zwischen zwei Server-Prozessen besteht, kann der Eine nicht ohne den Anderen sterben. Erst wenn beide Sterben wollen (z.B. wegen Timeout, o.ä.) tun sie es auch.

huschi.
 
Back
Top