[Technisch] Moodle-Sizing: Ressourcenteilung für Apache 2 (MPM Event), PHP-FPM und PostgreSQL auf Monolith-Servern

[Technisch] Moodle-Sizing: Ressourcenteilung für Apache 2 (MPM Event), PHP-FPM und PostgreSQL auf Monolith-Servern

by Serhat Saman -
Number of replies: 1

Hallo Moodle-Community,

wer kennt es nicht? Ein plötzlicher Burst (z. B. Klausurphase) und der Linux-Kernel schlägt gnadenlos zu: Out of Memory (OOM) Killer. Oft liegt das an einer statischen oder fehlerhaften Verteilung der RAM-Ressourcen zwischen dem Webserver (Apache/PHP-FPM) und der Datenbank (PostgreSQL).

Wir haben für unsere Moodle-Infrastrukturen via Ansible ein dynamisches Berechnungsmodell entwickelt, das sich rein an den tatsächlichen System-Facts orientiert. Unsere wichtigste Erkenntnis: Apache queued (puffert), PHP-FPM ist der Flaschenhals, und Postgres benötigt ein striktes, vom bereinigten Pool abgeleitetes Limit.

Das mathematische Grundprinzip (Monolith)

Wir teilen den RAM in festen Schritten auf, um Überlastungen zu vermeiden:

  1. Fixer Abzug: Betriebssystem (1 GB) + ClamAV (dynamisch je nach Systemgröße zwischen 500 MB und 4 GB).

  2. Sicherheitsreserve: 10 % des Gesamt-RAMs bleiben unangetastet.

  3. Der verbleibende Pool: Wird zu 48 % für PostgreSQL und zu 52 % für den Webstack aufgeteilt.

[ Gesamt-RAM ]
   │
   ├───> [ -1 GB OS ] ───> [ -ClamAV (0.5-4GB) ] ───> [ -10% Sicherheitsreserve ]
   │
   └───> = [ Verbleibender RAM-Pool ]
              │
              ├── (48%) ──> PostgreSQL (Shared Buffers, Work Mem)
              └── (52%) ──> Web-Ressourcen (PHP-FPM Worker & Apache Threads)

1. PHP-FPM & Apache MPM Event richtig dimensionieren

Ein häufiger Fehler ist, Apache und PHP-FPM als getrennte, gleich große RAM-Fresser zu betrachten. Das stimmt beim modernen mpm_event nicht mehr. Apache-Threads sind extrem leichtgewichtig (~256 KB bis max. 1 MB pro Thread). PHP-FPM-Worker hingegen schlagen mit echten PHP-Prozessen (ca. 40 MB im Moodle-Webbetrieb) zu Buche.

Die Formel für PHP-FPM (pm.max_children):

Wir limitieren FPM doppelt – über den RAM und über die CPU:

  • RAM-Limit: 70 % des verfügbaren Web-RAMs geteilt durch 40 MB pro Worker.

  • CPU-Limit: Anzahl der vCPUs × 3 (um die CPU bei I/O-Waits optimal auszulasten).

  • Es gilt das Minimum aus beiden Werten.

Die Formel für Apache (MaxRequestWorkers):

Da Apache nur puffert und Keep-Alive-Verbindungen hält, setzen wir hier großzügig an: max_children × 5 (mindestens jedoch 100). Dadurch liefert Apache bei Bursts nicht sofort einen 503 Service Unavailable, sondern hält die Verbindung in der Queue, bis ein FPM-Worker frei wird.

2. PostgreSQL Tuning (abgeleitet von FPM)

Die maximale Anzahl an DB-Verbindungen (max_connections) leitet sich direkt aus den PHP-FPM-Workern ab, da jeder Worker eine Verbindung öffnen könnte, plus einer Reserve für Cron, CLI und Admins (etc.):

max_connections = maxchilds_fpm + 50

Gleichzeitig begrenzen wir das risikoreiche work_mem (Speicher pro Sortieroperation) gestuft nach verfügbarem DB-RAM (von 8 MB auf kleinen Systemen bis zu 64 MB auf Systemen >16 GB DB-RAM).

-----

Wie kamen wir auf die 40 MB beim FPM (und wie misst man das mit Cron/Bash)?

Die 40 MB sind ein realistischer Durchschnittswert für einen Moodle-Web-Worker unter normaler Last. Schwere Prozesse (wie der Moodle-Cron, Backups oder PDF-Konvertierungen via unoconv) können jedoch auf 80–120 MB oder mehr ansteigen.

Um den realen Speicherbedarf eurer Moodle-Worker zur Laufzeit (z.B. während der Cron läuft) zu ermitteln, nutzt man folgenden Bash-Befehl:

smem -c "pid user pss uss" -P php-fpm | grep -E "www|php" | grep -v root | awk 'BEGIN {print "PID\tUSER\t\tECHTER ANTEIL (PSS)\tREIN EXKLUSIV (USS)"} {printf "%s\t%s\t%.2f MB\t\t%.2f MB\n", $1, $2, $3/1024, $4/1024}'
PID     USER            ECHTER ANTEIL (PSS)     REIN EXKLUSIV (USS)
89449   www-data        29,40 MB                10,30 MB
89453   www-data        29,71 MB                8,80 MB
89451   www-data        29,92 MB                9,86 MB

 

Wie berechnet man die Anzahl an Children pro CPU?

Warum nutzen wir den Faktor vCPUs × 3?

PHP arbeitet synchron und blockierend. Wenn ein PHP-Worker auf die Datenbank oder das Filesystem (Moodle-Data) wartet, befindet sich der Prozess im Zustand I/O Wait und blockiert die CPU nicht. Würdest du nur exakt 1 Worker pro CPU-Kern erlauben (vCPUs × 1), würde die CPU während dieser Wartezeiten Leerlauf haben. Ein Faktor von 2 bis 4 ist der Best-Practice-Standard, um die CPU-Kerne trotz I/O-Wartezeiten voll auszulasten, ohne dass das System durch zu viele Kontextwechsel (Context Switching) kollabiert.

Spezifikation zu Apache2 MaxRequestWorkers Ressourcen (in KB)

Im Gegensatz zum alten mpm_prefork (wo jeder Apache-Prozess ein volles PHP-Modul geladen hatte und 40 MB wog), nutzt mpm_event Threads.

  • Ein Apache-Event-Thread verbraucht im Schnitt nur ca. 256 KB bis 512 KB RAM.

  • Das bedeutet: 100 Apache-Worker verbrauchen gerade einmal 25 bis 50 MB RAM!

  • Deshalb zieht Apache kaum Ressourcen und kann bedenkenlos auf den Faktor 5 von FPM gesetzt werden. Apache hält die TCP-Verbindung offen, liest statische Assets (Bilder, CSS) blitzschnell direkt ein und reicht die schweren PHP-Anfragen kontrolliert an FPM weiter.

Ermittlung:

smem -c "pid user pss uss" -P apache2 | grep www-data | awk 'BEGIN {print "PID\tUSER\t\tPROZESS-PSS\tTHREADS\tWORKER-THREAD-PSS"} { pids_cmd="cat /proc/" $1 "/status | grep Threads | awk '"'{print \$2}'"'" ; pids_cmd | getline t; close(pids_cmd); if (!t || t==0) t=1; pss_mb=$3/1024; thread_kb=$3/t; printf "%s\t%s\t%.2f MB\t\t%d\t%.2f KB\n", $1, $2, pss_mb, t, thread_kb}'
PID     USER            PROZESS-PSS     THREADS WORKER-THREAD-PSS
1130896 www-data        1,82 MB         1       1864,00 KB # (Listener/Control/KeepAlive-Thread)
1130898 www-data        4,32 MB         17      260,24 KB
1130897 www-data        4,63 MB         17      279,06 KB

-----------------

Diese Berechnungen und die dynamische Aufteilung liefern in unserer täglichen Praxis hervorragende, stabile Ergebnisse. Gleichzeitig sind wir uns bewusst: Jedes Moodle-Setup ist ein Unikat. Auch wenn sich diese mathematische Logik hervorragend auf andere PHP-Applikationen übertragen lässt, ist die Suche nach den perfekten Werten oft ein Blick in die Glaskugel. Die Realität zeigt einfach, dass die perfekte Konfiguration eine Illusion ist, weil sich Nutzerverhalten und Plugin-Lasten stündlich ändern können.

Ich möchte diesen Beitrag und mein BarCamp auf der MoodleDACH 2026 als Startschuss nutzen, um mich mit anderen Admins, DevOps-Begeisterten und Infrastruktur-Köpfen auszutauschen. Welche Hebel habt ihr in euren Setups identifiziert und gibt es alternative Ansätze, die wir vielleicht völlig übersehen haben?

Ich freue mich auf euer Feedback hier im Forum und auf den direkten Erfahrungsaustausch beim BarCamp!

In reply to Serhat Saman

Re: [Technisch] Moodle-Sizing: Ressourcenteilung für Apache 2 (MPM Event), PHP-FPM und PostgreSQL auf Monolith-Servern

by Martin Vögeli -
Wow, danke! Gäbe es einen Knopf dafür, würde ich deinen Beitrag liken 🤓