„Gespräch:M4 Geschwindigkeit und Lua-Skripte“ – Versionsunterschied
KKeine Bearbeitungszusammenfassung |
KKeine Bearbeitungszusammenfassung |
||
| Zeile 1.327: | Zeile 1.327: | ||
Das bedeutet sachlich: | Das bedeutet sachlich: | ||
* Auf der Seite oder in einer transkludierten Seite gibt es einen '''Sprachlink''' wie <code><nowiki>[[de:…]]</nowiki></code>, <code><nowiki>[[en:…]]</nowiki></code> usw. | * Auf der Seite oder in einer transkludierten Seite gibt es einen '''Sprachlink''' wie <code><nowiki>\[\[de:…]]</nowiki></code>, <code><nowiki>\[\[en:…]]</nowiki></code> usw. | ||
* MediaWiki erkennt ihn als Interlanguage-Link. | * MediaWiki erkennt ihn als Interlanguage-Link. | ||
* Wikibase Client will ihn in sein Site-/Sitelink-Modell übersetzen. | * Wikibase Client will ihn in sein Site-/Sitelink-Modell übersetzen. | ||
| Zeile 1.359: | Zeile 1.359: | ||
Mit anderen Worten: | Mit anderen Worten: | ||
Wenn du z. B. irgendwo <code><nowiki>[[de:Beispiel]]</nowiki></code> verwendest, dann brauchst du in der Sites-Konfiguration einen Site-Eintrag, dessen | Wenn du z. B. irgendwo <code><nowiki>\[\[de:Beispiel]]</nowiki></code> verwendest, dann brauchst du in der Sites-Konfiguration einen Site-Eintrag, dessen | ||
* globale Site-ID z. B. <code>dewiki</code> oder etwas Eigenes ist, | * globale Site-ID z. B. <code>dewiki</code> oder etwas Eigenes ist, | ||
| Zeile 1.380: | Zeile 1.380: | ||
===== Variante B — den Sprachlink nicht als Interlanguage-Link verwenden ===== | ===== Variante B — den Sprachlink nicht als Interlanguage-Link verwenden ===== | ||
Wenn diese Links auf deiner Seite '''gar nicht''' als echte Sprachlinks/Sitelinks gedacht sind, sondern nur normale Verweise sein sollen, dann solltest du nicht die Sprachlink-Syntax <code><nowiki>[[de:...]]</nowiki></code> verwenden. MediaWiki behandelt solche Präfixe als Interlanguage-Links. Seit MW 1.44 gibt es dafür auch die Parserfunktion <code>#interlanguagelink</code>, was denselben Charakter noch einmal bestätigt. | Wenn diese Links auf deiner Seite '''gar nicht''' als echte Sprachlinks/Sitelinks gedacht sind, sondern nur normale Verweise sein sollen, dann solltest du nicht die Sprachlink-Syntax <code><nowiki>\[\[de:...]]</nowiki></code> verwenden. MediaWiki behandelt solche Präfixe als Interlanguage-Links. Seit MW 1.44 gibt es dafür auch die Parserfunktion <code>#interlanguagelink</code>, was denselben Charakter noch einmal bestätigt. | ||
Dann wäre die Korrektur eher: | Dann wäre die Korrektur eher: | ||
| Zeile 1.390: | Zeile 1.390: | ||
Bei deinem Wiki ist am wahrscheinlichsten: | Bei deinem Wiki ist am wahrscheinlichsten: | ||
* Du hast in Wikitext oder importierten Seiten Sprachlinks wie <code><nowiki>[[de:...]]</nowiki></code>. | * Du hast in Wikitext oder importierten Seiten Sprachlinks wie <code><nowiki>\[\[de:...]]</nowiki></code>. | ||
* Diese Präfixe existieren zwar im '''Interwiki-/Sprachlink-Sinn''', aber nicht sauber in deiner '''Sites'''-Konfiguration von Wikibase. | * Diese Präfixe existieren zwar im '''Interwiki-/Sprachlink-Sinn''', aber nicht sauber in deiner '''Sites'''-Konfiguration von Wikibase. | ||
* Deshalb scheitert genau die Abbildung <code>interlanguage prefix -> global site ID</code>. | * Deshalb scheitert genau die Abbildung <code>interlanguage prefix -> global site ID</code>. | ||
| Zeile 1.398: | Zeile 1.398: | ||
==== Im Wikitext ==== | ==== Im Wikitext ==== | ||
Suche auf der betroffenen Seite und in transkludierten Vorlagen nach:<syntaxhighlight lang="wikitext"> | Suche auf der betroffenen Seite und in transkludierten Vorlagen nach:<syntaxhighlight lang="wikitext"> | ||
[[de: | \[\[de: | ||
[[en: | \[\[en: | ||
[[fr: | \[\[fr: | ||
[[zh: | \[\[zh: | ||
</syntaxhighlight>und ähnlichen Präfixen. | </syntaxhighlight>und ähnlichen Präfixen. | ||
Aktuelle Version vom 11. August 2026, 15:21 Uhr
Über diese Seite Gespräch
-
Liste Werke von Klaus Feldmann
ChatGPT Sophos GPT-5.4 Thinking
31. März 2026 n. Chr. erst.
11:30 Uhr Ausgabe 02
Ausgabe 02
unüberarbeitet ungeprüft
Hallo Sophos,
ich brauche deine Untersützung als Systemspezialist für MediaWiki.
Ich habe folgende Fehlermeldung beim speichern einer Seite, die eine vielzahl an Lua-Skripten ausführt. Ich habe vor wenigen Tagen ein Update von MediaWIki 1.45.0-beta auf 1.45.1 ausgeführt. Der Fehler muß nichts mit dem updater zu tun haben, aber es könnten noch Fehler von 1.44 aufgrund viler Änderungen verschleppt sein.
Warning: require_once(/Library/WebServer/Documents/mediawiki/includes/libs/rdbms/exception/DBConnectionError.php): Failed to open stream: Too many open files in /Library/WebServer/Documents/mediawiki/includes/AutoLoader.php on line 273
MediaWiki internal error.
Original exception: [f681db4ae8db616bbfec3b55] /mediawiki/index.php?title=I_Ging&action=submit RuntimeException: Could not open '/var/tmp//mw-GlobalIdGenerator70-UUID-128'.
Zeile 437 von GlobalIdGenerator.php ist die Zeile
"$handle = fopen( $this->$lockFile, 'cb+' );"
in folgendem Block:
protected function getTimeAndDelay( $lockFile, $clockSeqSize, $counterSize, $offsetSize ) {
// Get the UID lock file handle
if ( isset( $this->fileHandles[$this->$lockFile] ) ) {
$handle = $this->fileHandles[$this->$lockFile];
} else {
$handle = fopen( $this->$lockFile, 'cb+' );
$this->fileHandles[$this->$lockFile] = $handle ?: null;
}
// Acquire the UID lock file
if ( $handle === false ) {
throw new RuntimeException( "Could not open '{$this->$lockFile}'." );
}
if ( !flock( $handle, LOCK_EX ) ) {
fclose( $handle );
throw new RuntimeException( "Could not acquire '{$this->$lockFile}'." );
}
// The formatters that use this method expect a timestamp with millisecond
// precision and a counter upto a certain size. When more IDs than the counter
// size are generated during the same timestamp, an exception is thrown as we
// cannot increment further, because the formatted ID would not have enough
// bits to fit the counter.
//
// To orchestrate this between independent PHP processes on the same host,
// we must have a common sense of time so that we only have to maintain
// a single counter in a single lock file.
//
// Given that:
// * The system clock can be observed via time(), without milliseconds.
// * Some other clock can be observed via microtime(), which also offers
// millisecond precision.
// * microtime() drifts in-process further and further away from the system
// clock the longer a process runs for.
// For example, on 2018-10-03 an HHVM 3.18 JobQueue process at WMF,
// that ran for 9 min 55 sec, microtime drifted by 7 seconds.
// time() does not have this problem. See https://bugs.php.net/bug.php?id=42659.
//
// We have two choices:
//
// 1. Use microtime() with the following caveats:
// - The last stored time may be in the future, or our current time may be in the
// past, in which case we'll frequently enter the slow timeWaitUntil() method to
// try and "sync" the current process with the previous process.
// We mustn't block for long though, max 10ms?
// - For any drift above 10ms, we pretend that the clock went backwards, and treat
// it the same way as after an NTP sync, by incrementing clock sequence instead.
// Given the sequence rolls over automatically, and silently, and is meant to be
// rare, this essentially sacrifices a reasonable guarantee of uniqueness.
// - For long running processes (e.g. longer than a few seconds) the drift can
// easily be more than 2 seconds. Because we only have a single lock file
// and don't want to keep too many counters and deal with clearing those,
// we fatal the user and refuse to make an ID. (T94522)
// - This offers terrible service availability.
// 2. Use time() instead, and expand the counter size by 1000x and use its
// digits as if they were the millisecond fraction of our timestamp.
// Known caveats or perf impact: None. We still need to read-write our
// lock file on each generation, so might as well make the most of it.
//
// We choose the latter.
$msecCounterSize = $counterSize * 1000;
rewind( $handle );
// Format of lock file contents:
// "<clk seq> <sec> <msec counter> <rand offset>"
$data = explode( ' ', fgets( $handle ) );
if ( count( $data ) === 4 ) {
// The UID lock file was already initialized
$clkSeq = (int)$data[0] % $clockSeqSize;
$prevSec = (int)$data[1];
$prevMsecCounter = (int)$data[2] % $msecCounterSize;
$randOffset = (int)$data[3] % $counterSize;
// If the system clock moved back or inter-process clock drift caused the last
// writer process to record a higher time than the current process time, then
// briefly wait for the current process clock to catch up.
$sec = $this->timeWaitUntil( $prevSec );
if ( $sec === false ) {
// There was too much clock drift to wait. Bump the clock sequence number to
// avoid collisions between new and already-generated IDs with the same time.
$clkSeq = ( $clkSeq + 1 ) % $clockSeqSize;
$sec = time();
$msecCounter = 0;
$randOffset = random_int( 0, $offsetSize - 1 );
trigger_error( "Clock was set back; sequence number incremented." );
} elseif ( $sec === $prevSec ) {
// The time matches the last ID. Bump the tie-breaking counter.
$msecCounter = $prevMsecCounter + 1;
if ( $msecCounter >= $msecCounterSize ) {
// More IDs generated with the same time than counterSize can accommodate
flock( $handle, LOCK_UN );
throw new RuntimeException( "Counter overflow for timestamp value." );
}
} else {
// The time is higher than the last ID. Reset the tie-breaking counter.
$msecCounter = 0;
}
} else {
// Initialize UID lock file information
$clkSeq = random_int( 0, $clockSeqSize - 1 );
$sec = time();
$msecCounter = 0;
$randOffset = random_int( 0, $offsetSize - 1 );
}
// Update and release the UID lock file
ftruncate( $handle, 0 );
rewind( $handle );
fwrite( $handle, "{$clkSeq} {$sec} {$msecCounter} {$randOffset}" );
fflush( $handle );
flock( $handle, LOCK_UN );
// Split msecCounter back into msec and counter
$msec = (int)( $msecCounter / 1000 );
$counter = $msecCounter % 1000;
return [
self:\:CLOCK_TIME => [ $sec, $msec ],
self:\:CLOCK_COUNTER => $counter,
self:\:CLOCK_SEQUENCE => $clkSeq,
self:\:CLOCK_OFFSET => $randOffset,
self:\:CLOCK_OFFSET_COUNTER => $counter + $randOffset,
];
}
Wenn ich die Vorlage "{{Navigationsleiste Fünf Klassiker}}" rausnehme, speichert die Seite ohne Fehler. Was könnte die Ursache sein?
Prüfung
Ich prüfe zuerst, ob das ein bekannter Effekt in 1.45.x oder eher ein Ressourcenproblem ist, das nur durch diese Vorlage ausgelöst wird. Danach ordne ich ein, ob die Ursache eher bei Lua/Parserlast, Dateihandle-Leak, oder einer tieferen Inkompatibilität aus 1.44 liegt.
Ich habe den ersten wichtigen Punkt: Die Meldung ist hier nicht primär „Datei fehlt“ oder „tmp-Verzeichnis falsch“, sondern EMFILE / Too many open files. Das heißt: Der PHP-Prozess hat bereits so viele offene Dateideskriptoren, dass selbst das kleine Lockfile für die UUID-Erzeugung nicht mehr geöffnet werden kann.
Ja. Die eigentliche Ursache ist mit hoher Wahrscheinlichkeit nicht die Zeile selbst in GlobalIdGenerator.php, sondern dass der PHP/Apache-Prozess beim Speichern bereits am Dateideskriptor-Limit angekommen ist. Das zeigt die erste Meldung eindeutig:
Failed to open stream: Too many open files
Danach scheitert folgerichtig auch noch require_once(...), also sogar das Nachladen einer ganz normalen PHP-Datei. Der UUID-Lockfile-Zugriff ist hier nur der erste sichtbare Aufprallpunkt; der Prozess ist da schon „voll“. MediaWiki verwendet für diese ID-Erzeugung tatsächlich Lockfiles im Temp-Verzeichnis wie mw-GlobalIdGenerator…-UUID-128. Das ist normales Core-Verhalten.
Dass die Seite ohne {{Navigationsleiste Fünf Klassiker}} speichert, spricht daher stark für diese Kausalkette:
- Die Vorlage triggert zusätzliche Transklusionen /
#invoke-Aufrufe / Parserarbeit. - Dabei werden sehr viele Dateien, Pipes, Streams oder andere Handles geöffnet.
- Irgendwo werden davon zu viele gleichzeitig offengehalten oder zu spät freigegeben.
- Erst am Ende scheitert dann
GlobalIdGeneratorbeim Öffnen seines Lockfiles.
Der UUID-Fehler ist also sehr wahrscheinlich Folgefehler, nicht Primärursache.
Was daraus folgt
1) Das Problem ist eher Ressourcenerschöpfung als „kaputte UUID-Datei“
Die bekannten MediaWiki-Fälle zu mw-GlobalIdGenerator… betreffen meist ein falsches oder nicht beschreibbares Temp-Verzeichnis. Dann lautet das Muster aber eher: Datei/Pfad existiert nicht oder ist nicht beschreibbar. Bei dir steht ausdrücklich Too many open files. Das ist ein anderer Fehlertyp.
2) Die Vorlage ist sehr wahrscheinlich nur der Auslöser
Wenn eine einzige Navigationsvorlage den Unterschied macht, ist das oft ein Zeichen für:
- zusätzliche Modulketten,
- rekursive Vorlagen-/Modulbezüge,
- massenhaft
mw.title...:getContent()/ Seitennachladungen, - sehr viele
#invoke-Umgebungen, - oder einen alten Inkompatibilitätsrest, der in einer selten betretenen Codebahn nun Handles offenlässt.
Gerade bei Scribunto ist wichtig: jeder {{#invoke:...}} läuft in einer eigenen Umgebung. Viele Invokes erhöhen also die Last sichtbar.
Die wahrscheinlichsten Ursachen – nach Plausibilität geordnet
A. Prozesslimit des Webservers/PHP ist zu niedrig
Auf macOS/Homebrew/Apache ist ein zu niedriges ulimit -n bzw. maxfiles sehr gut denkbar. Dann reicht eine schwere Seite aus, um das Limit zu reißen.
Dafür spricht:
Too many open filesist ein OS-/Prozesslimit-Fehler.- Nicht nur das Lockfile, sondern auch
require_oncescheitert. - Der Fehler tritt bei einer besonders schweren Seite bzw. mit einer bestimmten Vorlage auf.
B. Transklusions- oder Modulschleife / explosionsartige Erweiterung
Die betreffende Navigationsleiste könnte direkt oder indirekt wieder Seiten/Module/Vorlagen laden, die erneut in denselben Bereich zurückführen oder jedenfalls massiv vervielfachen.
Dafür spricht:
- „Mit Vorlage kaputt, ohne Vorlage nicht.“
- Solche Fehler werden oft nicht als klassische Rekursion gemeldet, wenn zuerst ein Ressourcenlimit getroffen wird.
C. Ein Modul oder Altbestand aus 1.44 hält Handles offen
Das ist eine Mutmaßung, aber keine fernliegende: Wenn alte angepasste Erweiterungen/Module/Brücken im Spiel sind, kann eine bestimmte Ausführungskette mehr Streams/Subprozesse/Dateien offenhalten als vorgesehen.
Dafür spricht:
- Du hast ein Upgrade hinter dir.
- Bei dir laufen viele eigene Lua-/Wikibase-/SMW-Anpassungen.
- Der Fehler erscheint auf einer stark skriptlastigen Seite.
Was ich zuerst prüfen würde
1) Offene Dateien des Apache-/PHP-Prozesses zählen
Auf macOS sehr aufschlussreich:
ulimit -n
launchctl limit maxfiles
ps aux | grep httpd
lsof -p <PID_des_httpd_Kindprozesses> | wc -l
lsof -p <PID_des_httpd_Kindprozesses> | head -200
Wichtig ist der Kindprozess, der die Anfrage wirklich bearbeitet, nicht nur der Masterprozess.
Wenn du beim problematischen Speichern parallel lsof beobachtest und die Zahl stark ansteigt, ist die Sache fast entschieden.
2) Vorlage auf Rekursion / Kettenwirkung prüfen
Prüfe:
- Welche Vorlagen bindet
Navigationsleiste Fünf Klassikerein? - Welche Module ruft sie auf?
- Gibt es darin
mw.title.new(...):getContent(),expandTemplate,frame:preprocess, Datenseiten, Untervorlagen mit denselben Navboxen? - Gibt es Selbstbezüge oder Rundbezüge?
Schon ein unauffälliger Kreis kann die Handle-Zahl hochtreiben.
3) Vorlagenstückweise zerlegen
Sehr praktisch zur Eingrenzung:
- Seite mit Vorlage speichern → Fehler.
- Dann in der Vorlage einzelne Teilblöcke auskommentieren.
- Besonders verdächtig: Untervorlagen, die selbst Navboxen, Listen oder Datenmodule nachladen.
- Ebenso verdächtig: Module, die seitenweise Inhalte nachladen statt nur Argumente auszuwerten.
4) PHP-/Apache-Logs parallel lesen
Nicht nur MediaWiki-Fehlerausgabe, sondern auch:
- Apache error log
- PHP error log
Oft steht dort vor dem UUID-Fehler noch die eigentliche erste Auffälligkeit.
Was du nicht überbewerten solltest
- Der doppelte Slash in
/var/tmp//...ist hier praktisch belanglos. GlobalIdGenerator.phpist sehr wahrscheinlich nicht defekt.- Das Update auf 1.45.1 kann der Anlass sein, muss aber nicht die eigentliche Ursache sein. Es kann auch sein, dass die neue Version nur etwas mehr Last erzeugt und damit eine alte Schwäche jetzt sichtbar wird.
Mein vorläufiges Urteil
Am wahrscheinlichsten ist:
Die Navigationsvorlage stößt eine zu große oder zirkuläre Parser-/Lua-Arbeitsmenge an, und dein Apache/PHP-Prozess läuft dabei in das Open-File-Limit.
GlobalIdGenerator ist dabei nur der erste Core-Bereich, der beim anschließenden Öffnen einer weiteren Datei scheitert.
Also in Kurzform:
- Primärursache: zu viele gleichzeitig offene Dateien/Streams im Request.
- Auslöser: die fragliche Navigationsvorlage oder eines ihrer Module/Untervorlagen.
- Nicht Primärursache:
GlobalIdGeneratorselbst.
Wenn du mir jetzt den Wikitext von Vorlage:Navigationsleiste Fünf Klassiker sowie die dabei aufgerufenen Module/Vorlagen gibst, kann ich dir die wahrscheinliche Engstelle gezielt herausarbeiten.
Hier die Abfragen:
Hier die Abfragen
1) ulimit -n
2560
2) launchctl limit maxfiles
maxfiles 256 unlimited
3) ps aux | grep httpd
_www 5667 0.0 0.1 412010960 16368 ?? S Sat04PM 1:52.21 /opt/homebrew/opt/httpd/bin/httpd -D FOREGROUND
_www 402 0.0 0.0 412010960 12304 ?? S Sat04PM 0:07.69 /opt/homebrew/opt/httpd/bin/httpd -D FOREGROUND
_www 401 0.0 0.0 411879888 11888 ?? S Sat04PM 0:02.42 /opt/homebrew/opt/httpd/bin/httpd -D FOREGROUND
_www 400 0.0 0.1 412010960 15584 ?? S Sat04PM 1:23.91 /opt/homebrew/opt/httpd/bin/httpd -D FOREGROUND
root 318 0.0 0.0 410938656 688 ?? Ss Sat04PM 0:07.48 /opt/homebrew/opt/httpd/bin/httpd -D FOREGROUND
aewi 35286 0.0 0.0 410733616 1568 s000 S+ 9:06AM 0:00.00 grep httpd
httpd_Kindprozesses
Welches davon ist die pid des httpd_Kindprozesses?
Die Kindprozesse sind hier die httpd-Prozesse mit Benutzer _www.
Also konkret:
- PID 5667
- PID 402
- PID 401
- PID 400
Der Prozess mit Benutzer root und PID 318 ist der Parent-/Masterprozess.
Für dein Problem sind also die _www-Prozesse maßgeblich. Am ehesten interessant sind dabei die mit bereits etwas Laufzeit und Aktivität, also vor allem:
- 5667
- 400
Damit kannst du z. B. prüfen:
lsof -p 5667 | wc -l
lsof -p 400 | wc -l
und ausführlicher:
lsof -p 5667 | head -200
lsof -p 400 | head -200
Noch wichtiger ist der Test während du den problematischen Speichervorgang auslöst. Dann sieht man, welcher _www-Kindprozess die Anfrage tatsächlich bearbeitet und ob dessen offene Dateien stark ansteigen.
Ein guter nächster Schritt ist:
watch -n 0.5 'lsof -p 5667 | wc -l; echo "---"; lsof -p 400 | wc -l'
Falls watch auf deinem macOS nicht vorhanden ist, geht auch:
while true; do
date
echo "PID 5667: $(lsof -p 5667 | wc -l)"
echo "PID 400: $(lsof -p 400 | wc -l)"
echo "-----"
sleep 0.5
done
Wichtiger Befund schon jetzt:
launchctl limit maxfiles zeigt bei dir soft limit 256. Das ist sehr niedrig und passt gut zu Too many open files. ulimit -n = 2560 in deiner Shell hilft dem bereits laufenden Apache-Prozess nicht, wenn dessen Launch-Kontext nur 256 mitbekommen hat.
Der nächste sachliche Prüfpunkt ist daher zweigleisig:
- welcher
_www-Kindprozess läuft beim Speichern ins Limit, - warum Apache offenbar nur mit maxfiles 256 gestartet wurde.
Wenn du magst, gehen wir als Nächstes direkt an die Prüfung mit lsof und danach an die dauerhafte Anhebung des Limits für Homebrew-httpd.
Abfragen
Diese Abfragen geben entweder als Antwort 0 oder antworten nicht. Wie dem auch sei. ich habe zugleich ein Update von Mac mini M1 16GB RAM auf M4 24GB RAM ausgeführt und kann nun meine Ressources erhöhen, nur welche bietet sich dafür an? Ich habe auch das Hindernis, daß Seiten mit vielen Skripten langsam laden.
Ich ziehe dafür kurz die maßgeblichen Primärquellen heran und ordne dann ein, welche Ressourcen bei deiner Konstellation wirklich etwas bringen und welche nur Scheinwirkung hätten. Bei deinem Fehlerbild ist die offene-Dateien-Grenze fast sicher der erste Hebel, aber für langsame Seiten lohnt sich eine zweite Schicht aus PHP- und MediaWiki-Tuning.
Befund
Ja — aber man sollte nicht blind „mehr RAM“ geben, sondern die Engstelle in der richtigen Reihenfolge angehen.
Bei deinem Befund ist der erste Hebel nicht RAM, sondern das Dateideskriptor-Limit des per launchd gestarteten Apache. Deine Shell zeigt zwar ulimit -n 2560, aber launchctl limit maxfiles zeigt für den gestarteten Dienst soft 256. Genau das passt zu Too many open files. Solange Apache mit diesem Limit läuft, kann schon eine einzelne schwere Parser-/Lua-Anfrage scheitern — und mehr RAM ändert daran nichts. Apache selbst weist zudem darauf hin, dass Worker-Zahlen nur so weit erhöht werden sollen, wie die physischen Ressourcen reichen; beim event-MPM ist der Sinn gerade, Verbindungen effizienter zu bedienen.
Meine Priorisierung für dein System
1) Zuerst: maxfiles für den Apache-Dienst anheben
Für deinen Fall würde ich zuerst das Soft-Limit des Dienstes deutlich erhöhen.
Praktischer Zielbereich:
- soft 4096 als erste sinnvolle Stufe
- hard 65536 oder höher als Reserve
Für dein Wiki mit vielen Transklusionen und Lua-Modulen ist 4096 ein vernünftiger Anfang.
8192 ist ebenfalls vertretbar, wenn du häufiger schwere Seiten bearbeitest.
Nicht zuerst erhöhen:
MaxRequestWorkers- Apache-Prozesszahl
- irgendwelche MediaWiki-Zeitlimits
Denn mit maxfiles=256 verschlimmert mehr Parallelität die Lage eher, statt sie zu lösen.
2) Dann: PHP-Opcode-Cache sauber dimensionieren
Für langsame Seiten ist der nächste große Hebel OPcache. PHP empfiehlt als gute Ausgangswerte u. a.:
opcache.memory_consumption=128opcache.interned_strings_buffer=8opcache.max_accelerated_files=4000opcache.revalidate_freq=60
Für dein Setup mit MediaWiki + vielen Erweiterungen + vielen Lua-/PHP-Dateien würde ich aber nicht bei den Minimalempfehlungen stehen bleiben, sondern eher so starten:
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.revalidate_freq=60
Warum gerade diese Richtung?
MediaWiki mit etlichen Extensions, Skins und eigenen Anpassungen hat deutlich mehr PHP-Dateien als ein kleines Standardprojekt. max_accelerated_files=4000 ist oft nur ein Unterboden; bei dir ist 20000 als Startwert realistischer. Das ist eine praxisbezogene Empfehlung, nicht der PHP-Default. Die PHP-Dokumentation zeigt, dass diese Direktiven genau dafür gedacht sind.
3) MediaWiki-Caches nicht bei „nur Dateisystem/DB“ belassen
MediaWiki empfiehlt für Performance ausdrücklich:
- Bytecode cache für PHP,
- APCu als local object cache,
- Memcached als Hauptcache; für kleinere Installationen ist auch ein lokaler Cache-Stack bereits hilfreich.
Wenn du bisher noch keinen lokalen Objektcache nutzt, ist das ein echter Hebel. Für einen einzelnen leistungsstarken Mac ist als erste Ausbaustufe meist sinnvoll:
$wgMainCacheType = CACHE_ACCEL;
$wgParserCacheType = CACHE_ACCEL;
und dazu APCu in PHP aktivieren.
Das bringt besonders bei wiederholten Seitenaufrufen etwas. MediaWiki nutzt Parser- und Objektcaches an mehreren Stellen; der Parser-Cache puffert das erzeugte ParserOutput für Seitenansichten.
Wichtig zur Einordnung
Das hilft vor allem beim Lesen bereits berechneter Seiten.
Beim Speichern und Vorschau erzeugen muss eine schwere Seite oft dennoch neu geparst werden. Deshalb ist Caching wichtig, aber es beseitigt nicht allein die Last durch sehr teure Lua-/Vorlagenketten.
4) Erst danach: Apache-Arbeiterzahl vorsichtig anpassen
Wenn du noch mit einem eher prozesslastigen Setup arbeitest, würde ich konservativ bleiben.
Falls du auf prefork + mod_php sitzt:
Dann würde ich nicht aggressiv hochdrehen. Apache sagt selbst, MaxRequestWorkers soll nur so hoch sein, wie der RAM es zulässt. Bei schwerem PHP ist jeder Worker ein voller Prozess.
Praktischer Startbereich:
MaxRequestWorkers 10bis25
Nicht höher, bevor du geprüft hast:
- RAM pro httpd/PHP-Prozess,
- Zahl offener Dateien,
- reale Parallelität deines Wikis.
Falls du später auf php-fpm umstellst:
Dann wird ein event-basiertes Modell attraktiver, weil Apache Keep-Alive-Verbindungen effizienter behandelt. Das ist eher der zweite Architektur-Schritt, nicht die erste Reparatur. Apache beschreibt genau diesen Vorteil des event-MPM.
5) Für langsame Seiten: die eigentliche Lastquelle bleibt meist Lua/Transklusion
Bei deinem konkreten Fall mit einer problematischen Navigationsvorlage ist sehr wahrscheinlich:
- viele
#invoke-Aufrufe, - viele Untervorlagen,
- evtl. Seitennachladungen aus Lua,
- oder eine Rundbeziehung.
Scribunto stellt den Lua-Rahmen bereit; jeder #invoke kostet Parserarbeit. Bei sehr skriptlastigen Seiten ist daher oft nicht der Server zu klein, sondern die Vorlagenarchitektur zu teuer.
Daher meine klare Empfehlung in dieser Reihenfolge
Sofort sinnvoll
- Apache-Dienstlimit
maxfilesauf 4096/65536 oder 8192/65536 anheben. - OPcache sauber dimensionieren (
memory_consumption=256,interned_strings_buffer=16,max_accelerated_files=20000als guter Start). - APCu als MediaWiki-Objekt-/Parsercache aktivieren, falls noch nicht geschehen.
Danach
- Apache-Worker nur vorsichtig erhöhen, nicht als ersten Schritt.
- Problemvorlage strukturell prüfen, besonders auf:
- tiefe Vorlagenketten,
- viele
#invoke, mw.title...getContent()oder ähnliche Seitennachladungen,- rekursive Navbox-Bezüge.
Konkrete Startwerte, die ich dir für den M4 geben würde
A. Dienst-/OS-Ebene
maxfiles soft = 4096maxfiles hard = 65536
B. PHP
memory_limit = 512M
realpath_cache_size = 256k
realpath_cache_ttl = 600
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.revalidate_freq=60
C. MediaWiki
$wgMainCacheType = CACHE_ACCEL;
$wgParserCacheType = CACHE_ACCEL;
D. Apache
- Bei
prefork:MaxRequestWorkerszunächst 10–20 - Nicht höher, bevor das
maxfiles-Problem weg ist
Mein Urteil in einem Satz
Für den Speicherfehler ist die beste Ressourcenerhöhung zunächst offene Dateien / maxfiles, nicht RAM.
Für die Langsamkeit sind danach OPcache + APCu + Vorlagen/Lua-Entlastung die wirksamsten Hebel.
Wenn du willst, gehe ich im nächsten Schritt mit dir konkret durch Homebrew-httpd auf macOS, damit wir die maxfiles-Anhebung für den gestarteten Dienst sauber und dauerhaft setzen.
Cache
Danke für die Hinweise.
Die Chache Einstelklungen in LocalSettings.php sind bereits so eingestellt und APCu hatten wir installiert und aktiviert.
Wir sind an sich auf fpm für php – ich weiß nichgt ob das eine Rolle spielt.
Die Ändernunegn an der php.ini brachten keine Verbessewrung.
Die fragliche Seite ruft ein Skript für Chinesisch sehr oft auf, da die Seite Tabellen mit vielen chinesischen Schriftzeichen enthält.
Ich habe die Seite nun in Unterseiten aufgeteilt und einzeln funktionieren die Abschnitte.
Wenn ich diese jedoch in die Hauptseite transkludiere habe ich wieder den Fehler.
Wie kann ich erreichen, daß die Skripte auf den Unterseiten gespeichert bleiben und nur in die Hauptseite ohne Ausührung der Skripte angezeigt wird?
Transkludieren
Ich prüfe gerade den entscheidenden Punkt: ob MediaWiki beim Transkludieren den bereits gerenderten Unterseiten-Output wiederverwenden kann, oder ob der Wikitext der Unterseiten in der Hauptseite erneut geparst wird. Davon hängt ab, ob dein Ziel direkt geht oder ob man auf einen anderen Mechanismus ausweichen muss.
Ja — aber nicht mit normaler Transklusion.
Der entscheidende Sachverhalt ist: In MediaWiki werden Vorlagen, Parserfunktionen, Variablen und damit auch #invoke/Lua sowohl bei der Substitution als auch als erster Schritt der Seitendarstellung expandiert. Außerdem braucht der Parser beim Parsen einer Seite rekursiv auch den Quelltext aller transkludierten Seiten und Lua-Module. Mit anderen Worten: Wenn du Unterseiten in die Hauptseite transkludierst, wird deren Wikitext nicht als schon fertig gerenderter Bestand übernommen, sondern im Kontext der Hauptseite wieder in den Expansions-/Parserprozess einbezogen.
Darum hilft dir die Aufteilung in Unterseiten zwar beim Einzelspeichern, aber nicht bei der zusammengesetzten Hauptseite: Durch die Transklusion holst du die teuren #invoke-Aufrufe wieder zurück in denselben Parse-Vorgang. Dass genau solche transkludierten und wiederholt verwendeten Konstruktionen die Komplexität hochtreiben, ist in den Template-Limits auch so beschrieben; besonders kritisch sind viele Wiederholungen derselben Vorlage/Funktion, etwa in Tabellenzeilen.
Was daraus folgt
Das von dir gewünschte Verhalten
„Unterseiten sollen ihre Skriptausführung behalten, aber in der Hauptseite nur noch als Anzeige ohne erneute Ausführung erscheinen“
geht mit gewöhnlichem {{:Unterseite}} in MediaWiki nicht direkt.
<onlyinclude>, <includeonly>, <noinclude> helfen nur dabei, welcher Wikitext transkludiert wird. Sie verhindern nicht, dass der transkludierte Wikitext beim Einbinden wieder expandiert wird. Die Expansionslogik greift trotzdem.
Was tatsächlich geht
Variante A — Ergebnis „einfrieren“ statt live transkludieren
Wenn das chinesische Skript am Ende bloß fertigen Text/Wikitext erzeugt, kannst du die teuren Aufrufe vorab substituieren und damit in statischen Wikitext verwandeln. MediaWiki dokumentiert, dass auch ParserfunktionenSubstitution verwenden können. Damit wird aus „bei jedem Parse ausführen“ eher „einmal auswerten und Ergebnis in die Seite schreiben“.
Praktisch heißt das:
- die teuren
{{#invoke:...}}bzw. Vorlagen nicht live stehen lassen, - sondern das erzeugte Ergebnis in einer statischen Unterseite festhalten,
- und diese statische Seite dann in die Hauptseite transkludieren.
Wichtig dabei:
Das ist dann kein automatisches Live-Update mehr. Wenn sich Daten oder Modul-Logik ändern, musst du die statische Unterseite neu erzeugen.
Variante B — Hauptseite gar nicht transkludieren lassen, sondern Ausgabe hineinkopieren
Also nicht:
{{:I_Ging/Abschnitt1}}
{{:I_Ging/Abschnitt2}}
sondern die fertig expandierten Ergebnisse direkt in die Hauptseite schreiben, also faktisch eine Art Build-/Generierungsschritt.
Das ist technisch das Robusteste, wenn du eine große „Leseseite“ brauchst, aber die Berechnung nicht jedes Mal neu anwerfen willst.
Variante C — das Lua-Modul selbst entlasten
Da du sagst, dass ein Chinesisch-Skript sehr oft aufgerufen wird, liegt hier wahrscheinlich der eigentliche Hebel.
Wenn dasselbe Modul für hunderte Zeichen einzeln aufgerufen wird, ist meist besser:
- ein Aufruf für eine ganze Liste/Tabelle statt viele Einzelaurufe,
- Memoisierung im Modul: gleiche Eingabe nur einmal berechnen,
- keine wiederholten teuren
mw.title...getContent()/expandTemplate()-Muster, - Daten möglichst aus einer vorbereiteten Lua-Tabelle statt aus wiederholten Seitennachladungen.
Das ändert zwar nichts an der Grundregel der Transklusion, kann aber die Last massiv senken.
Was ich dir konkret empfehlen würde
Für dein Ziel ist die sauberste Lösung meist eine Zwei-Seiten-Strategie:
1. Arbeits-Unterseiten
Dort darf die volle Logik laufen, z. B.:
I_Ging/Abschnitt1/QuelleI_Ging/Abschnitt2/Quelle
Diese Seiten enthalten die teuren #invoke-Aufrufe.
2. Statische Anzeige-Unterseiten
Zusätzlich erzeugst du:
I_Ging/Abschnitt1/AnzeigeI_Ging/Abschnitt2/Anzeige
Dort steht nur das bereits expandierte Ergebnis, keine teuren Lua-Aufrufe mehr.
Die Hauptseite transkludiert dann nur noch:
{{:I_Ging/Abschnitt1/Anzeige}}
{{:I_Ging/Abschnitt2/Anzeige}}
Damit bleibt die Hauptseite leicht, weil sie nur statischen Wikitext einbindet.
Die wichtige Grenze
Was nicht geht, ist dies:
Unterseite einmal rendern, und MediaWiki soll bei Transklusion automatisch genau dieses gerenderte HTML wiederverwenden, ohne den Quelltext erneut in die Expansion einzubeziehen.
Das ist mit normalem Wikitext-Transkludieren nicht das Standardmodell von MediaWiki. Die Parser-/Transklusionsarchitektur setzt gerade auf Quelltext-Expansion, nicht auf „gespeichertes Render-Fragment der anderen Seite einhängen“.
Mein Urteil für deinen Fall
Bei dir ist sehr wahrscheinlich nicht die Seitengröße als solche das Hauptproblem, sondern die vielen wiederholten Lua-Aufrufe für chinesische Zeichen.
Darum sind die beiden wirksamsten Wege:
- Ergebnis einfrieren und nur noch statische Anzeige-Unterseiten transkludieren.
- Das Chinesisch-Modul umbauen, damit es Listen blockweise verarbeitet oder wiederholte Ergebnisse intern cached.
Wenn du mir ein kleines Beispiel des betreffenden Wikitexts gibst — also ein paar Tabellenzeilen mit den chinesischen Aufrufen und den verwendeten #invoke-/Vorlagen — kann ich dir daraus eine konkrete statische Anzeige-Strategie oder eine Memoisierungs-/Batch-Variante für das Modul ableiten.
unpraktisch
Das mit der Unterseiten für Quelle und Anzeige ist unpraktisch. Ich habe nun einen Verweis und Link auf der Hauptseite zu den jeweiligen Unterseiten erstellt. Das genügt mir für die Arbeitsfähigkeit vorerst.
Ausweichlösung
Ja, das ist für den Moment eine vernünftige Ausweichlösung.
Damit entgehst du genau dem problematischen Punkt: Die Hauptseite zieht die teuren Transklusionen nicht mehr in einen gemeinsamen Parse-Vorgang hinein. Die Unterseiten bleiben jeweils für sich beherrschbar, und die Hauptseite dient nur noch der Gliederung und Navigation.
Für die weitere Einordnung heißt das nun auch:
- Die Aufteilung hat den Fehler nicht logisch behoben, aber den kritischen Lastpfad umgangen.
- Damit verdichtet sich die Vermutung weiter, dass die kombinierte Seite an Parser-/Lua-Komplexität bzw. an ein Ressourcenlimit stößt.
- Der wahrscheinlich wichtigste technische Restpunkt bleibt trotzdem das
maxfiles-Limit des gestarteten Apache-/php-fpm-Kontexts, weil dieses Problem bei anderen schweren Seiten wieder auftreten kann.
Für den Augenblick ist dein jetziger Stand arbeitsfähig. Später wären die zwei sinnvollsten nächsten Untersuchungen:
maxfilesfür die gestarteten Dienste dauerhaft anheben,- das häufig aufgerufene Chinesisch-Modul darauf prüfen, ob sich viele Einzelaufrufe zu weniger, gröberen Aufrufen zusammenfassen lassen.
So wie du es jetzt gelöst hast, ist es erst einmal stabil genug, um weiterzuarbeiten.
Flaschenhals
Dann rechcherchiere bitte professionell danach, wie ich den maxfile-"Flaschenhals" bei mir beheben kann.
launchd-Konfiguration
Ich habe den Kern schon bestätigt: Entscheidend ist auf macOS die launchd-Konfiguration der gestarteten Dienste, nicht die Shell-ulimit. Ich suche jetzt die belastbaren Quellen dafür und dann leite ich daraus eine Vorgehensweise speziell für Homebrew-httpd und php-fpm ab.
Ja. Bei dir liegt der Flaschenhals sehr wahrscheinlich in der launchd-verwalteten Dateideskriptor-Grenze der gestarteten Dienste, nicht in der Shell. Das passt exakt zu deinem Befund launchctl limit maxfiles → 256 unlimited und zu Too many open files. launchctl verwaltet diese Limits, und launchd.plist erlaubt dafür pro Dienst SoftResourceLimits und HardResourceLimits mit NumberOfFiles.
Der wichtigste praktische Punkt ist: Änderungen an der Shell-ulimit oder an php.ini beheben diesen Flaschenhals nicht, wenn httpd und php-fpm von launchd mit niedrigen Limits gestartet werden. Und bei Homebrew gibt es noch eine zweite Falle: Wenn du die von brew services erzeugte .plist direkt änderst, können diese Anpassungen bei brew services restart ... wieder überschrieben werden.
Was ich dir fachlich empfehle
1) Erst die tatsächlichen Service-Dateien ermitteln
Prüfe zuerst, welche launchd-Plists bei dir wirklich benutzt werden: brew services list
Bei Homebrew liegen diese typischerweise als homebrew.mxcl.httpd.plist und homebrew.mxcl.php.plist unter ~/Library/LaunchAgents/ oder /Library/LaunchDaemons/. Das sieht man auch in Homebrew-Fällen und brew services list-Ausgaben.
2) Nicht die generierten Homebrew-Plists direkt als Dauerlösung patchen
Das wäre nur eine kurzfristige Probe, aber keine saubere Dauerlösung, weil Homebrew-Service-Neustarts die generierten Dateien wieder auf Maintainer-Stand zurücksetzen können.
3) Saubere Dauerlösung: eigene launchd-Plists mit NumberOfFiles
Apple beschreibt genau dafür SoftResourceLimits und HardResourceLimits; der Schlüssel für offene Dateien heißt NumberOfFiles. Bei systemweiten Daemons beeinflusst das zusätzlich die zugehörigen Kernel-/per-process-Limits.
Für dich heißt das praktisch:
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>65536</integer>
</dict>
Du nimmst die aktuell verwendete httpd- und php-fpm-Service-Definition als Vorlage und ergänzt in deiner eigenen plist etwa:
4) Sinnvolle Zielwerte für deinen Fall
Für dein MediaWiki mit vielen Transklusionen, Lua-Aufrufen und php-fpm ist als erste saubere Stufe sinnvoll:
- Soft:
4096 - Hard:
65536
Das ist deutlich über deinem jetzigen 256, aber noch vernünftig. Dass 256 bei Lastanwendungen zu niedrig ist, wird auch in praktischen macOS-Fällen genau so sichtbar.
Ich würde nicht gleich auf riesige Fantasiewerte gehen. Erst 4096/65536 setzen, dann prüfen, ob der Fehler weg ist.
Der sachlich richtige Weg auf deinem System
Da du Homebrew-httpd und offenbar php-fpm nutzt, würde ich so vorgehen:
A. Aktive Plists feststellen
brew services list
ls -l ~/Library/LaunchAgents/homebrew.mxcl.*
ls -l /Library/LaunchDaemons/homebrew.mxcl.*
B. Eigene dauerhafte Plists anlegen
Lege eigene Dateien an, z. B.:
/Library/LaunchDaemons/local.httpd.plist/Library/LaunchDaemons/local.php-fpm.plist
und übernimm dort den bisherigen ProgramArguments-/KeepAlive-/RunAtLoad-Teil aus den Homebrew-Plists, ergänzt um die beiden NumberOfFiles-Blöcke.
C. Homebrew-Service für diese beiden stoppen
brew services stop httpd
brew services stop php
D. Eigene Plists laden
sudo launchctl bootstrap system /Library/LaunchDaemons/local.httpd.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/local.php-fpm.plist
Falls sie schon geladen waren, vorher bootout statt bootstrap.
E. Danach prüfen
launchctl limit maxfiles
ps aux | egrep 'httpd|php-fpm'
Und dann den Problemfall erneut auslösen.
Was du alternativ kurz zum Test tun kannst
launchctl hat einen limit-Befehl, mit dem man die maxfiles-Grenze direkt setzen kann. Das ist als temporärer Test nützlich, um die Diagnose zu bestätigen. Die Manpage beschreibt genau diesen Befehl.
Also testweise: sudo launchctl limit maxfiles 4096 65536
Dann httpd und php-fpm neu starten und die problematische Speicherung wiederholen.
Aber: Das ist für mich hier nur ein Bestätigungstest, nicht die bevorzugte Dauerlösung. Die saubere Dauerlösung ist die Einstellung in den Dienst-Plists. Apple dokumentiert gerade diesen plist-Weg mit SoftResourceLimits/HardResourceLimits.
Wovon ich dir abrate
- Nur
ulimit -nin der Shell ändern Das trifft deine interaktive Shell, aber nicht verlässlich den vonlaunchdgestarteten Dienst. - Die Homebrew-
homebrew.mxcl.*.plistdirekt dauerhaft editieren Das kann beibrew services restartwieder verloren gehen. - Gleich globale Extremwerte setzen Das ist unnötig und erschwert die Fehlersuche. Erst moderate, belegbare Schritte.
Mein Urteil für deinen Fall
Die professionelle, belastbare Lösung ist:
- Bestätigen, dass
httpd/php-fpmunterlaunchdmit zu niedrigemmaxfileslaufen. - Eigene
launchd-Plists fürhttpdundphp-fpmanlegen. - Dort
SoftResourceLimits/HardResourceLimits → NumberOfFilessetzen. - Mit 4096 / 65536 starten.
Wenn du mir den Output von brew services list und den Inhalt der beiden aktuellen Homebrew-Plists gibst, formuliere ich dir daraus die exakten fertigen .plist-Dateien für dein System, so dass du sie nur noch ablegen und laden musst.
LaunchAgent wie folgt geändert
Ich habe den LaunchAgent wie folgt geändert:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>Label</key>
<string>homebrew.mxcl.httpd</string>
<key>LimitLoadToSessionType</key>
<array>
<string>Aqua</string>
<string>Background</string>
<string>LoginWindow</string>
<string>StandardIO</string>
<string>System</string>
</array>
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>65536</integer>
</dict>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/httpd/bin/httpd</string>
<string>-D</string>
<string>FOREGROUND</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Ist das richtig so?
launchd.plist-Schlüssel
Ich prüfe jetzt die genaue Semantik der launchd.plist-Schlüssel und ob dein konkreter Aufbau für einen Homebrew-httpd-Agent stimmig ist. Wichtig ist hier nicht nur „gültiges XML“, sondern auch, ob es im richtigen Startkontext wirkt.
Im Grundsatz: ja, die beiden Resource-Limit-Blöcke sind syntaktisch richtig eingefügt. launchd unterstützt SoftResourceLimits und HardResourceLimits, und NumberOfFiles ist der passende Schlüssel für offene Dateien. Dein XML ist also in diesem Punkt stimmig.
Es gibt aber drei wichtige Vorbehalte:
Erstens: Ein LaunchAgent ist ein Benutzer-Job, kein System-Daemon. Apple beschreibt Agents als Prozesse, die an einen eingeloggten Benutzer gebunden sind und nur laufen, solange dieser Benutzer angemeldet ist. Für einen Webserver ist das oft nicht die beste Form, wenn er unabhängig von deinem Login zuverlässig laufen soll.
Zweitens: Wenn diese Datei von Homebrew verwaltet wird, kann brew services restart httpd deine Änderung später wieder überschreiben. Das ist der praktische Haken an direktem Editieren von homebrew.mxcl.httpd.plist.
Drittens: Dein Block
<key>LimitLoadToSessionType</key>
<array>
<string>Aqua</string>
<string>Background</string>
<string>LoginWindow</string>
<string>StandardIO</string>
<string>System</string>
</array>
ist für dein Ziel nicht der eigentliche Hebel. Entscheidend für den maxfiles-Flaschenhals sind die SoftResourceLimits/HardResourceLimits. Der Session-Type-Teil ist hier eher Begleitkonfiguration; er macht die Datei nicht automatisch „falscher“, aber er löst auch nicht dein Kernproblem. Die belastbare und relevante Änderung sind bei dir die NumberOfFiles-Werte.
Mein Urteil zu deiner Datei
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>65536</integer>
</dict>
Ja, der eingefügte Teil für NumberOfFiles ist richtig.
So würde ich ihn inhaltlich auch setzen:
Das ist fachlich passend zu deinem Fehlerbild Too many open files.
Was ich an deiner Stelle noch anpassen würde
1) Für Dauerbetrieb eher LaunchDaemon statt LaunchAgent
Wenn dein httpd dauerhaft und unabhängig vom Login laufen soll, ist ein LaunchDaemon in /Library/LaunchDaemons/meist die sachgerechtere Form; Agents sind an die Benutzersitzung gebunden. Apple unterscheidet genau so zwischen per-user agents und systemweiten Daemons.
2) Auch php-fpm prüfen
Wenn du tatsächlich auf php-fpm bist, dann kann nicht nur httpd, sondern auch php-fpm in dasselbe Limit laufen. Dann müsstest du die entsprechende php-fpm-plist analog anpassen, sonst verlagert sich der Engpass nur. Dass launchd die Resource Limits pro Job setzt, gilt allgemein für solche Jobs.
3) Änderungen wirklich neu laden
Eine geänderte plist wirkt erst sauber, wenn der Job entladen und neu geladen bzw. neu gebootstrapped wurde. Apple beschreibt launchd-Jobs genau als geladene Konfigurationen; Änderungen an der Datei allein genügen nicht.
4) Für Diagnose Logpfade ergänzen
Zum Prüfen, ob der Job mit deiner geänderten plist wirklich sauber startet, sind StandardOutPath und StandardErrorPath oft hilfreich. Apple zeigt das ausdrücklich als Debug-Hilfe für launchd-Jobs.
Zum Beispiel:
<key>StandardOutPath</key>
<string>/opt/homebrew/var/log/httpd-launchd.log</string>
<key>StandardErrorPath</key>
<string>/opt/homebrew/var/log/httpd-launchd-error.log</string>
Der entscheidende praktische Punkt
Wenn du nur diese eine Homebrew-LaunchAgent-Datei editiert hast, dann ist die Änderung zwar inhaltlich korrekt, aber noch nicht automatisch eine robuste Dauerlösung. Der richtige Prüfstein ist jetzt:
- Job sauber neu laden.
- Danach erneut
launchctl limit maxfilesim passenden Kontext prüfen. - Problemseite wieder testen.
- Dasselbe ggf. auch für
php-fpmsetzen.
Wenn du mir jetzt sagst, wo genau diese plist liegt — also ~/Library/LaunchAgents/... oder /Library/LaunchDaemons/... — und was brew services list für httpd und php zeigt, kann ich dir den nächsten Schritt präzise sagen: ob du auf diesem Weg bleiben kannst oder besser auf eine eigene Daemon-plist umstellst.
Änderungen nun für LaunchDemon und LaunchAgent eingetragen
Ich habe die Änderungen nun für LaunchDemon und LaunchAgent eingetragen, einen Neustartr durchgeführt und es bleibt bei der bisherigen einstellung von 256.
Offenbar greifen die homebrew.mxcl.httpd.plist einstellungen nicht, sie wurden nach dem Neustart auch nicht überschrieben.
Wie setze ich dasselbe auch für php-fpm?
Homebrew-Label
Ich prüfe gerade zwei Dinge zugleich: erstens, welcher Dienstname/Homebrew-Label für dein php-fpm tatsächlich aktiv ist, und zweitens, ob bei dir eher ein job-spezifisches Launchd-Limit oder ein eigener php-fpm-RLIMIT-Wertmaßgeblich ist. Das ist auf macOS/Homebrew nicht ganz dasselbe.
Ja — für php-fpm gibt es zwei Stellen, die du setzen solltest:
- im
launchd-Job von Homebrew-PHP, analog zuhttpd, - zusätzlich in
php-fpm.confüberrlimit_files, weil FPM dafür eine eigene Direktive hat. PHP dokumentiertrlimit_filessowohl für den FPM-Masterprozess als auch für Pool-Childprozesse; Standard ist jeweils nur der „system defined value“.
Der zweite Punkt ist bei dir besonders wichtig, weil du bereits gesehen hast, dass die reine plist-Änderung bei httpd nicht sichtbar durchschlägt.
Warum du weiter 256 siehst
launchctl limit maxfiles zeigt die Limits von launchd selbst an. Dass dort weiter 256 steht, bedeutet nicht zwingend, dass keineinzelner Job ein anderes Limit bekommen kann. Die plist-Schlüssel SoftResourceLimits / HardResourceLimits sind job-spezifische setrlimit(2)-Werte; NumberOfFiles ist genau der Schlüssel für offene Dateien. Für systemweite Daemons kann das zusätzlich kern.maxfiles bzw. kern.maxfilesperproc anheben.
Das erklärt auch, warum dein globaler Blick auf launchctl limit unverändert bleiben kann, obwohl ein Job theoretisch eigene Limits hätte.
So setzt du es für php-fpm
A. Zuerst die richtige Homebrew-plist finden
Bei Homebrew läuft PHP in der Regel unter dem Label homebrew.mxcl.php; genau diese plist taucht auch in Homebrew-Fällen auf.
Prüfen:
brew services list
ls -l ~/Library/LaunchAgents/homebrew.mxcl.php.plist
ls -l /Library/LaunchDaemons/homebrew.mxcl.php.plist
Welche davon wirklich benutzt wird, siehst du an brew services list.
B. In die homebrew.mxcl.php.plist denselben Block einfügen
Also analog zu httpd:
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>65536</integer>
</dict>
Das ist genau die von launchd.plist dokumentierte Form.
C. Danach zusätzlich in php-fpm.conf
In Homebrew liegt die Datei typischerweise unter /opt/homebrew/etc/php/<version>/php-fpm.conf oder ähnlich. Dort ergänzen: rlimit_files = 4096
PHP-FPM dokumentiert diese Direktive ausdrücklich für den Masterprozess.
Wenn du ganz sauber sein willst, kannst du in der Pool-Konfiguration zusätzlich ebenfalls setzen, denn PHP dokumentiert rlimit_files auch für die Childprozesse eines Pools.
Zum Beispiel in der Pool-Datei (www.conf oder dein eigener Pool): rlimit_files = 4096
Mein Rat für dein System
Ich würde bei dir beides setzen:
launchd-plist fürhomebrew.mxcl.phpphp-fpm.confund ggf. Pool-www.conf
Denn dann bist du nicht davon abhängig, ob das Limit nur vom Job geerbt wird oder FPM intern auf dem Systemwert bleibt.
Neustart danach
Nicht nur macOS neu starten, sondern den FPM-Dienst gezielt neu laden: brew services restart php
Wenn du mit eigener plist arbeitest statt brew services, dann entsprechend bootout/bootstrap.
Noch ein wichtiger Punkt
Wenn bei dir httpd als LaunchAgent läuft, ist das für einen Webserver nicht ideal. Apple unterscheidet klar zwischen per-user Agents und systemweiten Daemons; für einen dauerhaft laufenden Webserver ist ein Daemon der passendere Kontext.
Was ich an deiner Stelle als Nächstes tun würde
brew services listfürphpansehen.- Die aktive
homebrew.mxcl.php.plistmitNumberOfFilesergänzen. - In
php-fpm.confzusätzlichrlimit_files = 4096setzen. - Falls vorhanden, in der Pool-Datei ebenfalls
rlimit_files = 4096. brew services restart php.
Wenn du mir den Inhalt deiner aktiven homebrew.mxcl.php.plist und den Pfad deiner php-fpm.conf zeigst, formuliere ich dir die exakten Einträge ohne Rateschritt.
HardResourceLimits auf 65536?
Danke für die Hinweise. Wie setze ich für fpm das HardResourceLimits auf 65536?
HardResourceLimits auf 65536
Ich prüfe kurz die belastbaren Stellen: bei php-fpm selbst gibt es eine dokumentierte Direktive für offene Dateien, und ich gleiche das mit dem macOS-launchd-Modell ab. Danach trenne ich sauber, was FPM intern kann und was nur launchd/das System setzen kann.
Für php-fpm selbst kannst du kein separates HardResourceLimits = 65536 setzen.
php-fpm bietet in seiner Konfiguration nur die Direktive rlimit_files für offene Dateien an — einmal global für den Masterprozess und optional auch pro Pool. Eine getrennte Angabe von soft und hard kennt FPM dort nicht.
Das heißt sachlich:
- Hard-Limit 65536 setzt du außerhalb von FPM, also über
launchdmitHardResourceLimits -> NumberOfFiles = 65536. - FPM selbst bekommt dann über
rlimit_files = 4096den praktischen Arbeitswert für offene Dateien.
Konkret für dich
1) In php-fpm.conf
Zum Beispiel: rlimit_files = 4096
Das ist der FPM-Wert, den der Masterprozess verwendet. PHP dokumentiert rlimit_files ausdrücklich als FPM-Direktive.
2) Optional zusätzlich im Pool, z. B. www.conf
Falls du es auch auf Pool-Ebene explizit setzen willst: rlimit_files = 4096
Auch das ist von FPM vorgesehen.
3) Das eigentliche Hard-Limit bleibt in der plist
Dort:
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>65536</integer>
</dict>
Diese Schlüssel sind genau die von launchd.plist vorgesehenen Resource-Limits. NumberOfFiles ist der passende Schlüssel für offene Dateien.
Der entscheidende Punkt
php-fpm kann also nicht selbst sagen:
„soft 4096, hard 65536“.
Es kann nur ungefähr sagen:
„verwende für offene Dateien diesen FPM-Wert rlimit_files“.
Das harte Oberlimit muss vom Betriebssystem bzw. launchd kommen.
Mein Rat für deine Konstellation
Setze bei FPM einfach: rlimit_files = 4096
und halte das eigentliche Hard-Limit von 65536 in der plist.
Wenn die plist-Werte bei dir weiter nicht greifen, liegt das Problem eher daran, welcher Job tatsächlich geladen wird, nicht an der Syntax von FPM.
Der nächste sinnvolle Schritt ist dann, den wirklich aktiven php-fpm-Job und seine geladene Definition zu prüfen, statt weiter nur an der Konfigurationsdatei zu drehen.
bleibt es bei 256
Trotz aller Änderungen und ach in php-fpm.conf bleibt es bei 256.
Wenn ich jedoch "sudo launchctl limit maxfiles 4096 65536" gesetzt habe, dann habe ich auch launchctl limit maxfiles = 4096. Technisch geht das ändern also. Ich habe nach der temporären Änderung ider die Tranklusion in der chinesischen Seite aktiviert.
Ich erhalte dort dennochg den Fehler: {{Modul:Vorlage:lang}} mw.title.lua:230: too many expensive function calls
Demnach könnte es auch eine andere Ursachge haben, oder sollte ich noch weiter erhöhen?
andere Ursache
Jetzt ist der Befund deutlich klarer: Der maxfiles-Flaschenhals war real, aber nicht die ganze Ursache. Ich prüfe die MediaWiki-/Scribunto-Seite des neuen Fehlers, damit wir sauber zwischen OS-Limit und Parserlimit trennen.
Ja — jetzt ist klar: Es gibt bei dir mindestens zwei verschiedene Grenzen, und die aktuelle Fehlermeldung betrifft nicht mehr primär maxfiles, sondern das MediaWiki-Parserlimit für „expensive function calls“. Die Meldung
mw.title.lua:230: too many expensive function calls
zeigt genau auf dieses Parser-/Scribunto-Limit. MediaWiki hat dafür die Einstellung $wgExpensiveParserFunctionLimit; ihr Default ist 100 pro Parse. Wenn dieses Limit überschritten wird, wirft Scribunto bei solchen Aufrufen eine Exception.
Der wichtige Schluss daraus ist:
- Das temporäre
launchctl limit maxfiles 4096 65536war nicht wirkungslos. Es hat den einen Flaschenhals tatsächlich erweitert. - Aber danach stößt dieselbe Seite an die nächste Grenze: zu viele teure Parser-/Lua-Aufrufe.
Also: andere Ursache?
Ja, zusätzlich.
Nicht „stattdessen“, sondern zusätzlich.
maxfiles und „expensive function calls“ sind zwei verschiedene Ebenen:
- OS-/Dienstebene: offene Dateien / Dateideskriptoren
- MediaWiki-/Parser-Ebene: teure Parserfunktionen / teure Scribunto-Titelabfragen
Sollte man weiter erhöhen?
Nicht als ersten Schritt.
Man kann \$wgExpensiveParserFunctionLimit erhöhen, aber die MediaWiki-Doku stellt klar, dass das Limit gerade die Seitenkomplexität begrenzt; die saubere Lösung ist vorrangig, die Zahl der teuren Aufrufe zu senken. Dass man den Wert erhöhen kann, ist dokumentiert, aber es ist eher ein kontrollierter Ausnahmehebel als die beste erste Maßnahme.
Was bei Lua als „expensive“ hineinläuft
Gerade in Scribunto sind bestimmte mw.title-Operationen teuer. Relevant aus der Doku:
mw.title.new( ID )ist teuer; auch beim Laden noch nicht geladener Titel kann der Zähler steigen.mw.title.newBatch(...):lookupExistence():getTitles()zählt einmal pro 25 Titel-Lookups, also deutlich günstiger als viele Einzelabfragen.- Auf teure Titel-Felder/Methoden wie etwa
pageLang,idin bestimmten Fällen,redirectTarget, Dateimetadaten usw. kann der Zähler ebenfalls steigen;getContent()zeichnet außerdem Transklusion/Linkbeziehungen auf und ist gerade in massenhafter Verwendung ein Warnsignal.
Das heißt für deine konkrete Situation mit vielen chinesischen Zeichen und häufigem Aufruf von {{Modul:Vorlage:lang}}:
Sehr wahrscheinlich ruft dieses Modul oder eines seiner Hilfsmodule für viele einzelne Zeichen/Tokens wiederholt teure
mw.title- oder parsernahe Funktionen auf.
Das ist an dieser Stelle eine begründete Schlussfolgerung, weil die Fehlermeldung direkt aus mw.title.lua kommt und die Scribunto-Doku genau diese Titelabfragen als teuer beschreibt.
Was ich dir jetzt fachlich rate
1) Zuerst im Modul ansetzen, nicht den Grenzwert blind hochdrehen
Suche in Modul:Vorlage:lang und seinen Hilfsmodulen gezielt nach:
mw.title.new(...)mw.title.makeTitle(...):getContent().exists.id.pageLangframe:callParserFunction(...)frame:expandTemplate(...)#ifexistindirekt über Parserfunktionen
Denn genau diese Stellen sind die typischen Kandidaten für das teure Zählen.
2) Besonders wichtig: viele Einzeltitel zu Batch-Lookups bündeln
Wenn dein Modul für viele Zeichen oder Sprachcodes jeweils einzeln Titelobjekte erzeugt und Existenz/Metadaten prüft, ist das oft der Kernfehler. Die Scribunto-Doku nennt mit mw.title.newBatch(...):lookupExistence():getTitles() ausdrücklich eine gebündelte Variante, bei der der Zähler nur einmal pro 25 Einträge steigt.
Das ist vermutlich der wirksamste technische Hebel, falls dein Modul viele Seiten-/Existenzabfragen macht.
3) getContent() und Seitennachladungen so weit wie möglich vermeiden
Wenn das Modul für jedes Zeichen oder für jede Tabellenzelle Inhalte anderer Seiten nachlädt, ist das strukturell teuer. In solchen Fällen ist meist besser:
- Daten in eine Lua-Datentabelle verlagern (
mw.loadData) - gleiche Werte innerhalb eines Aufrufs cachen
- dieselbe Titelprüfung nicht hunderte Male wiederholen
Scribunto dokumentiert mw.loadData() ausdrücklich als effiziente Datennutzung; mw.loadJsonData() erhöht den teuren Zähler sogar um 1.
4) Den Grenzwert nur moderat anheben, falls die Seite fachlich wirklich so groß sein muss
Das kannst du in LocalSettings.php setzen: $wgExpensiveParserFunctionLimit = 300;
oder testweise auch höher.
Aber ich würde das nur stufenweise tun, z. B.:
- erst
200 - dann
300
und nicht sofort extrem hoch. Die Doku sagt klar, dass diese Limits die Seitenkomplexität begrenzen sollen. Wenn die Seite schon jetzt wegen massenhafter Transklusionen und Lua schwer ist, verschiebt ein sehr hoher Wert das Problem eher, statt es zu lösen.
Meine klare Antwort auf deine Frage
- Ja, es ist jetzt erkennbar eine weitere Ursache.
- Nein, ich würde nicht zuerst weiter
maxfileserhöhen, denn die neue Fehlermeldung ist eine andere Grenze. - Ja, man kann
\$wgExpensiveParserFunctionLimiterhöhen — aber der sachlich bessere erste Schritt ist,Modul:Vorlage:langauf teuremw.title-Nutzung zu prüfen und zu bündeln.
Der nächste sinnvolle Schritt ist jetzt nicht mehr die OS-Ebene, sondern ein Blick in Modul:Vorlage:lang, besonders auf alle mw.title-Zugriffe. Wenn du mir den Modulcode gibst, kann ich dir die teuren Stellen gezielt markieren.
Der Flaschenhals war "$wgExpensiveParserFunctionLimit"
Der Flaschenhals war "$wgExpensiveParserFunctionLimit = 300;"
Ich mußte auf 300 gehen - mit 200 kam er weit, aber nicht bis zum Schluß
Jetzt lädt die Seite auch schnell.
Mein M4 bleibt nur zu 10% ausgelastet, wenn eijne Berechnung erfolgt.
Nur, wie setzen wir HardResourceLimits und NumberOfFiles dauerhaft?
Wenn ich ein Shell-Skript anlegen soll, um dies beim Staretn des M4 jeweils zu setzen, dann ohne den User, da ich alles so eingerichtet habe, daß nach einem remote Neustart sich niemand anmelden muß, da dann niemand vor Ort am Server ist.
Gebe mir wenn, dann auch das geeignete Verzeichnis und alle auszuführenden Anweisungen, da ich dies nicht täglich mache und wieder vergesse.
Parserlimit war der akute Bremsklotz
Jetzt ist das Bild deutlich klarer: Das Parserlimit war der akute Bremsklotz, und dein System selbst ist nicht annähernd ausgelastet. Ich prüfe nun den saubersten dauerhaften Weg für macOS ohne Benutzer-Login, damit das Limit nach Remote-Neustart verlässlich gesetzt wird.
Ja. Da dein temporäres Kommando sudo launchctl limit maxfiles 4096 65536
nachweislich wirkt, ist für dich der sachlich naheliegende Dauerweg nicht mehr das Weiterdrehen an Homebrew-plists, sondern ein eigener systemweiter LaunchDaemon, der beim Booten ohne Benutzer-Login läuft, das Limit setzt und anschließend php-fpm und httpd neu startet.
launchd läuft bereits beim Booten als PID 1 und verwaltet den system-Kontext; LaunchDaemons sind genau für solche systemweiten Jobs gedacht.
Der Grund, warum ich dir diese Variante empfehle:
Die Manpage sagt ausdrücklich, dass SoftResourceLimits/HardResourceLimits per Job setrlimit(2) setzen; NumberOfFiles ist der richtige Schlüssel. Aber du hast praktisch gezeigt, dass dein sicher wirksamer Hebel das globale launchctl limit maxfiles ...ist. Ein eigener Boot-Job, der genau dieses bewährte Kommando ausführt, ist in deiner Lage die robusteste Lösung.
Was du anlegst
1) Shell-Skript
Pfad: /usr/local/sbin/set-maxfiles-and-restart-webstack.sh
Inhalt:
#!/bin/zsh
/bin/launchctl limit maxfiles 4096 65536
# Webdienste danach neu starten, damit sie das Limit sicher erben.
/bin/launchctl kickstart -k system/homebrew.mxcl.php
/bin/launchctl kickstart -k system/homebrew.mxcl.httpd
Warum kickstart -k?
launchctl kickstart -k service-target startet einen Dienst sofort neu; die Manpage dokumentiert genau diesen Neustartmechanismus.
Dann Rechte setzen:
sudo mkdir -p /usr/local/sbin
sudo chown root:wheel /usr/local/sbin/set-maxfiles-and-restart-webstack.sh
sudo chmod 755 /usr/local/sbin/set-maxfiles-and-restart-webstack.sh
2) LaunchDaemon-plist
Pfad: /Library/LaunchDaemons/local.set-maxfiles-and-restart-webstack.plist
Inhalt:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.set-maxfiles-and-restart-webstack</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/sbin/set-maxfiles-and-restart-webstack.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/var/log/set-maxfiles-and-restart-webstack.log</string>
<key>StandardErrorPath</key>
<string>/var/log/set-maxfiles-and-restart-webstack.err</string>
</dict>
</plist>
Dann Rechte setzen:
sudo chown root:wheel /Library/LaunchDaemons/local.set-maxfiles-and-restart-webstack.plist
sudo chmod 644 /Library/LaunchDaemons/local.set-maxfiles-and-restart-webstack.plist
sudo plutil -lint /Library/LaunchDaemons/local.set-maxfiles-and-restart-webstack.plist
Dass ein systemweiter LaunchDaemon in /Library/LaunchDaemons/ liegt und mit root:wheel gesetzt wird, ist auch in Apples Dokumentation so gezeigt.
So lädst du ihn
Zuerst alte Instanz entfernen, falls schon geladen: sudo launchctl bootout system /Library/LaunchDaemons/local.set-maxfiles-and-restart-webstack.plist 2>/dev/null
Dann laden: sudo launchctl bootstrap system /Library/LaunchDaemons/local.set-maxfiles-and-restart-webstack.plist
Die launchctl-Manpage beschreibt bootstrap/bootout genau für das Laden und Entfernen von Diensten im system-Domain-Kontext.
Danach kannst du ihn sofort testweise auslösen: sudo launchctl kickstart -k system/local.set-maxfiles-and-restart-webstack
Kontrolle
1) Jobstatus
sudo launchctl print system/local.set-maxfiles-and-restart-webstack
2) Globales Limit prüfen
launchctl limit maxfiles
3) Logdateien ansehen
sudo tail -50 /var/log/set-maxfiles-and-restart-webstack.log
sudo tail -50 /var/log/set-maxfiles-and-restart-webstack.err
4) Danach Webdienste prüfen
sudo launchctl print system/homebrew.mxcl.php
sudo launchctl print system/homebrew.mxcl.httpd
Wichtige Vorbedingung
Das funktioniert in dieser Form nur sauber, wenn php und httpd tatsächlich im system-Domain laufen, also als LaunchDaemons.
Falls einer davon nur als LaunchAgent im Benutzerkontext läuft, wäre system/homebrew.mxcl... das falsche Ziel. Die launchctl-Manpage trennt system-, user- und gui-Domänen ausdrücklich.
Prüfen:
sudo launchctl print system | grep homebrew.mxcl.httpd
sudo launchctl print system | grep homebrew.mxcl.php
Wenn beide dort auftauchen, ist das Ziel richtig.
Warum ich dir nicht primär zu Homebrew-plist-Patches rate
Weil du bereits praktisch gesehen hast, dass diese Änderungen bei dir nicht zuverlässig den gewünschten Effektgezeigt haben. Zudem sind Homebrew-Service-Dateien erfahrungsgemäß nicht der beste Ort für dauerhafte lokale Sonderlogik. Dein eigener LaunchDaemon ist davon getrennt und bleibt unter deiner Kontrolle. Die Launchd-Dokumentation selbst beschreibt diese systemweiten Jobs genau über eigene plists.
Mein Rat für deinen konkreten Fall
Nimm genau diese Variante mit:
/usr/local/sbin/set-maxfiles-and-restart-webstack.sh/Library/LaunchDaemons/local.set-maxfiles-and-restart-webstack.plist
Sie erfüllt alle von dir genannten Bedingungen:
- kein Benutzer-Login nötig
- läuft nach Remote-Neustart
- setzt genau das Kommando, das bei dir schon bewiesen funktioniert
- startet php-fpm und httpd danach neu, damit sie das Limit sicher übernehmen
Wenn du mir den Output von
gibst, prüfe ich dir noch, ob die beiden Servicenamen exakt homebrew.mxcl.php und homebrew.mxcl.httpd sind oder bei dir leicht anders heißen.
Wie erzeuge ich mit was die Shell
Wie erzeuge ich mit was die Shell set-maxfiles-and-restart-webstack.sh?
Am einfachsten direkt im Terminal mit sudo nano.
1) Verzeichnis anlegen
sudo mkdir -p /usr/local/sbin
2) Datei mit nano anlegen
sudo nano /usr/local/sbin/set-maxfiles-and-restart-webstack.sh
Dann diesen Inhalt einfügen:
#!/bin/zsh
/bin/launchctl limit maxfiles 4096 65536
# Webdienste neu starten, damit sie das Limit übernehmen
/bin/launchctl kickstart -k system/homebrew.mxcl.php
/bin/launchctl kickstart -k system/homebrew.mxcl.httpd
3) In nano speichern
Ctrl+Odrücken- mit
Enterbestätigen Ctrl+Xzum Beenden
4) Datei ausführbar machen
sudo chown root:wheel /usr/local/sbin/set-maxfiles-and-restart-webstack.sh
sudo chmod 755 /usr/local/sbin/set-maxfiles-and-restart-webstack.sh
5) Inhalt prüfen
cat /usr/local/sbin/set-maxfiles-and-restart-webstack.sh
Danach ist das Skript erstellt.
Im nächsten Schritt legst du dann die Datei /Library/LaunchDaemons/local.set-maxfiles-and-restart-webstack.plist an.
homebrew.mxcl.php
Ich habe das et-maxfiles-and-restart-webstack.sh ohne den Restart von homebrew.mxcl.php und homebrew.mxcl.httpd und es funktioniert – maxfiles ist nach dem Restart des Computer ohne weitere Änderungen auf 4096 – danke dafür, daß macht den M4 nun schneller, doch der tut ja kaum was.
der entscheidende Punkt
Gut. Dann ist der entscheidende Punkt jetzt geklärt:
- Der dauerhafte Weg über deinen eigenen Boot-Job funktioniert.
- Die Homebrew-
plist-Änderungen waren für deinen Fall also nicht der verlässlich wirksame Hebel. - Der eigentliche akute Bremsklotz auf MediaWiki-Seite war zusätzlich
$wgExpensiveParserFunctionLimit.
Dass der M4 dabei kaum Last zeigt, passt dazu. Solche Grenzen liegen oft nicht bei CPU oder RAM, sondern bei Parserlogik, Limits und Architektur der Aufrufe. Wenn das Limit der eigentliche Engpass ist, bleibt die Maschine äußerlich „gelassen“, obwohl die Anwendung vorher stockte.
Dein jetziger Stand ist damit sachlich recht klar:
- maxfiles dauerhaft behoben,
- ExpensiveParserFunctionLimit passend angehoben,
- Seite lädt nun schnell,
- System hat noch deutliche Reserven.
Das ist ein brauchbarer und sauberer Zwischenstand.
Für später ist nur eine Sache im Hinterkopf sinnvoll:
Wenn du weitere sehr große Transklusions- oder Lua-Seiten baust, ist nicht der M4 das erste Problem, sondern wieder eher:
- Zahl teurer Parserfunktionen,
- Struktur der
#invoke-Ketten, - und eventuell weitere MediaWiki-Parserlimits.
Aber für jetzt ist die Arbeitsfähigkeit hergestellt.
nächster Fehler
Suchen wir nun den nächsten Fehler. Ich bekomme die Meldung:
Suchen wir nun den nächsten Fehler.
Ich bekomme die Meldung:
erlanguage prefix as to a global site ID. [Called from Wikibase\Client\Hooks\LangLinkHandler:\:localLinksToArray in /Library/WebServer/Documents/mediawiki/extensions/Wikibase/client/includes/Hooks/LangLinkHandler.php at line 239] in /Library/WebServer/Documents/mediawiki/includes/debug/MWDebug.php on line 485
Folgendes ist der Block von LangLinkHandler.php at line 239:
private function localLinksToArray( array $flatLinks ) {
$links = [];
$sites = $this->siteLookup->getSites();
foreach ( $flatLinks as [ 'link' => $link ] ) {
$lang = $link->getInterwiki();
$page = $link->getDBkey();
if ( $link->getFragment() !== '' ) {
$page .= '#' . $link->getFragment();
}
if ( $sites->hasNavigationId( $lang ) ) {
$site = $sites->getSiteByNavigationId( $lang );
$wiki = $site->getGlobalId();
$links[$wiki] = $page;
} else {
wfWarn( "Failed to map interlanguage prefix $lang to a global site ID." );
}
}
return $links;
}
Wikibase lang.
Ich prüfe jetzt die belastbaren Stellen in Wikibase selbst: Was getInterwiki() hier genau liefert, wie navigationId aufgelöst wird, und unter welchen Konstellationen diese Warnung absichtlich entsteht. Danach können wir den Fehler auf deine Site-Konfiguration eingrenzen, statt nur am Symptom zu drehen.
Ja — die Warnung ist inhaltlich ziemlich klar.
LangLinkHandler:\:localLinksToArray() nimmt für jeden lokalen Sprachlink den von getInterwiki() gelieferten Präfix, also etwa de, en, fr, und versucht dann, diesen Präfix über die Navigation ID eines registrierten Sites-Eintrags auf eine globale Site-ID abzubilden. Genau dafür ist SiteList:\:$byNavigationId gedacht; die Doku beschreibt diese Navigation IDs ausdrücklich als Alias-Index für Interlanguage-Präfixe. Wenn kein Site-Eintrag mit dieser Navigation ID existiert, kommt genau deine Warnung.
Das bedeutet sachlich:
- Auf der Seite oder in einer transkludierten Seite gibt es einen Sprachlink wie
\[\[de:…]],\[\[en:…]]usw. - MediaWiki erkennt ihn als Interlanguage-Link.
- Wikibase Client will ihn in sein Site-/Sitelink-Modell übersetzen.
- In deiner Sites-Tabelle gibt es aber keinen passenden Site-Eintrag, dessen
navigation-idgenau diesem Präfix entspricht.
Die Ursache ist also sehr wahrscheinlich
Nicht „LangLinkHandler kaputt“, sondern:
Interlanguage-Präfix vorhanden, aber kein passender Wikibase-/Sites-Eintrag mit derselben Navigation ID.
Das passt auch zu deiner bisherigen Konstellation mit lokalem Wikibase und eigener Site-Konfiguration.
Was du prüfen solltest
1) Welcher Präfix ist es genau?
In deiner Meldung ist der Anfang abgeschnitten, aber typischerweise steht dort so etwas wie:
deenzhcommons- o. ä.
Der entscheidende Punkt ist der Wert von: $lang = $link->getInterwiki();
also der Präfix des Sprachlinks.
2) Gibt es dafür einen Site-Eintrag?
MediaWiki/Wikibase stellt dafür das Maintenance-Skript addSite bereit. Dort ist --navigation-id ausdrücklich die Navigation ID des Sites. Genau diese ID wird hier gesucht.
Mit anderen Worten:
Wenn du z. B. irgendwo \[\[de:Beispiel]] verwendest, dann brauchst du in der Sites-Konfiguration einen Site-Eintrag, dessen
- globale Site-ID z. B.
dewikioder etwas Eigenes ist, - und dessen
navigation-id=deist.
Die zwei praktischen Lösungen
Variante A — den fehlenden Site-Eintrag anlegen
Das ist die saubere Lösung, wenn du diese Sprachlinks im Wikibase-/Sitelink-Sinn wirklich haben willst.
Beispiel:
php maintenance/run.php addSite dewiki wikipedia \
--language de \
--interwiki-id de \
--navigation-id de \
--pagepath 'https://de.wikipedia.org/wiki/$1'
Das Beispiel ist nur schematisch; wichtig ist hier der Punkt --navigation-id de. Das ist genau die Zuordnung, die hasNavigationId( 'de' ) später finden muss. Die Option ist in der Doku des Skripts ausdrücklich vorgesehen.
Wenn du noch das alte Skriptformat verwendest, wäre es bei älteren MW-Versionen statt run.php direkt maintenance/addSite.php; die aktuelle Doku weist auf diesen Unterschied hin.
Variante B — den Sprachlink nicht als Interlanguage-Link verwenden
Wenn diese Links auf deiner Seite gar nicht als echte Sprachlinks/Sitelinks gedacht sind, sondern nur normale Verweise sein sollen, dann solltest du nicht die Sprachlink-Syntax \[\[de:...]] verwenden. MediaWiki behandelt solche Präfixe als Interlanguage-Links. Seit MW 1.44 gibt es dafür auch die Parserfunktion #interlanguagelink, was denselben Charakter noch einmal bestätigt.
Dann wäre die Korrektur eher:
- normalen externen/internen Link verwenden,
- oder die Präfixnutzung so ändern, dass sie nicht als Sprachlink in den LangLinkHandler hineinläuft.
Was ich bei dir für am wahrscheinlichsten halte
Bei deinem Wiki ist am wahrscheinlichsten:
- Du hast in Wikitext oder importierten Seiten Sprachlinks wie
\[\[de:...]]. - Diese Präfixe existieren zwar im Interwiki-/Sprachlink-Sinn, aber nicht sauber in deiner Sites-Konfiguration von Wikibase.
- Deshalb scheitert genau die Abbildung
interlanguage prefix -> global site ID.
Wie du es gezielt prüfst
Im Wikitext
Suche auf der betroffenen Seite und in transkludierten Vorlagen nach:
\[\[de:
\[\[en:
\[\[fr:
\[\[zh:
und ähnlichen Präfixen.
In deiner Sites-Konfiguration
Liste die Sites und prüfe, ob für den fraglichen Präfix eine passende navigation-id existiert. Wenn du lieber direkt in der DB schaust, prüfe in den sites-bezogenen Tabellen den Eintrag für diesen Präfix. Der Kern ist: Es muss ein Site-Eintrag existieren, den getSiteByNavigationId( $lang ) findet.
Mein Urteil
Die Warnung bedeutet sehr wahrscheinlich:
Ein Interlanguage-Präfix aus dem Wikitext ist in deinem Wikibase-Site-Register nicht mit einer passenden navigation-id hinterlegt.
Also nicht blind am PHP-Code drehen, sondern entweder:
- den passenden Site-Eintrag mit
--navigation-id <präfix>anlegen, oder - den Wikitext so ändern, dass kein echter Sprachlink daraus wird.
Wenn du mir den genauen Präfix aus der Warnung und den Output deiner aktuellen Sites gibst, sage ich dir direkt, welcher konkrete addSite-Befehl fehlt.
ich lösche diese Zeile einfach
Am Ende der Fraglichen Seite steht vergleichsweise [[no:…]] und verlinkt dann zu der englischen Seite – das brauche ich nicht – ich lösche diese Zeile einfach.
richtige Korrektur.
Ja — das ist hier sehr wahrscheinlich die einfachste und richtige Korrektur.
[[no:…]] wird von MediaWiki als Interlanguage-Link behandelt. Genau dadurch läuft der Eintrag in den LangLinkHandler, und genau dort scheitert dann die Zuordnung, wenn für no kein passender Wikibase-Site-Eintrag mit entsprechender navigation-id existiert. Wenn du diesen Link ohnehin nicht brauchst, beseitigst du mit dem Löschen die Ursache direkt.
Das ist in diesem Fall sachlich besser als:
- am Core-/Extension-Code zu drehen,
- künstlich einen unnötigen Site-Eintrag nur für diese Altlast anzulegen,
- oder eine Warnung zu unterdrücken, deren Ursache du bereits verstanden hast.
Damit ist dieser Fehlerpfad sauber erledigt.
Abschied
Wir sind damit durch und es funktioniert so für mich. Danke für deine Unterstützung. Bis bald.
Abschied
Gern. Das Wesentliche ist jetzt geklärt und bei dir arbeitsfähig eingerichtet.
Bis bald.