Opcache precompile – jetzt wirklich

Beim letzten Beitrag wollte ich eigentlich auf das Primen des Opcaches kommen – aber ich wurde augehalten 😀

Opcache primen

Nachdem wir jetzt den Opcache vernĂŒnftig eingestellt haben, möchten wir den doch bitte auch ordentlich nutzen.

Und wenn ich eines nicht besonders gerne habe, sind das freigerĂ€umte Resourcen, die dann doch erst mal nicht und/oder erst nach langer Nutzungszeit genutzt werden… ich möchte NĂ€gel mit Köpfen machen.

Also nutzen wir doch die Möglichkeit, dass wir den Opcache bereits vorbefĂŒllen können, bevor der PHP-Interpreter seine Griffel an ein jungfrĂ€uliches Scriptchen legt.

Jede PHP Datei also ausfĂŒhren?

Äh… denkbar… aber… ich möchte dann bitte nicht der Server sein, denn dass wird sicher ein wenig heiß und unbequem…

Es gibt eine elegantere Lösung.

Elegantere Lösung

In PHP gibt es ein lustiges Kommando

opcache_compile_file

Das macht auch genau das, was man vermutet, wenn man es so liest: Es compiliert ein PHP-Script und – was man nicht liest – lĂ€dt es beim Opcache ab.

Jetzt mĂŒssen wir doch nur sĂ€mtliche PHP-Dateien einer Hierarchie zusammensuchen und diesem netten Herren ĂŒbergeben, der sich dann um das kĂŒmmert, was unser Herzenswunsch ist.

PHP-Dateien zusammensuchen

Wir könnten jetzt natĂŒrlich das Rad neu erfinden und einen banalen rekursiven Algorithmus basteln, der alle PHP-Dateien in einer Baumstruktur heraussucht. FingerĂŒbung.

Aber das hat glĂŒcklicherweise schon jemand anderes gemacht und hat dem UngetĂŒm selbstredend einen modernen, beschreibenden und unfassbar langen Namen gegeben:

RecursiveIteratorIterator

Rant.

Als ich anfing mit der Programmierung, haben wir alle recht schnell verstanden, dass hinter einem

mov

eigentlich ein „move“ steht. Und hinter einem

br

ein Branch (also „goto“ – damals war das noch nicht böse, heute in Assembler auch noch nicht). Möchte jemand wissen, dass es danach, fĂŒr heutige Gehirne auch ungewöhnlich, weitergeht, nĂ€mlich „erst das Ziel, dann die Quelle“?

mov ax, 01h
int 33h

Heißt nix anderes als „schieb 01h in Register ax und rufe den Interrupt 33h auf“ – das zusammen schaltete den Mauscursor ein 🙂 Irgendwie ist das doch knackig auf den Punkt, oder?

Damals (in der „guten, alten Zeit ™“) hat man auch noch gewusst, dass man hier nicht kommentieren muss, dass „Register AX mit 01h gefĂŒllt“ wird – sondern man schrieb davor „Mauscursor einschalten“, damit jeder mittelmĂ€ĂŸig begabte Programmierer nach einem noch wusste, was dieser Buchstabensalat elegante Code macht.

Heute muss man den jungen (mittelalten und vielen Ă€lteren) Programmierern alles möglichst mit Buntstiften und in aller AusfĂŒhrlichkeit – möglichst unter Verwendung sĂ€mtlicher Buchstaben des Alphabets – schon beim Kommando selbst erklĂ€ren. Da werden dann aus gut und schnell schreib- und erlernbaren Kommandos riesige BuchstabensalatwĂŒsten, die ohne (Auto-)Completion und Referenzen, so umfangreich wie die Bibel nebst Kommentar der ersten siebenhundert PĂ€pste, nicht mehr beherrschbar sind.

HMPF. Ich werde alt.

Dann mal happy coding!

Ich könnte ja jetzt fies sein und sagen „baut euch damit jetzt den passenden Code“, aber ich will ja mal nicht so sein:

/**
 * OpCache - alle Dateien in den Cache hieven
 */

$path = realpath('.');

$objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST);
foreach ($objects as $name => $object) {
 $path_parts = pathinfo($name);
 if (strtolower($path_parts['extension']) == 'php') {
 echo "$name";
 if (@opcache_compile_file($name)) {
 echo " compiled\n<br>";
 } else {
 echo " error\n<br>";
 }
 }
}

Wieso twitterst du opcache_compile_file?

*kopf* *tisch*

Ich möchte nicht sehen, wenn beim Compilieren was schief lĂ€uft, denn die Funktion liefert mir einen passenden Returncode… wozu dann noch die Konsole oder das Log vollmĂŒllen?

Übrigens

Das Prime-Script bekommt man fehlerfrei nur ausgefĂŒhrt, wenn es der tatsĂ€chliche Webserver ausfĂŒhrt, weil der Zend-Opcache in der oben genannten Konfiguration keine CLI-Scripte cached!

wget https://www.deinvollcoolerdomainname.de/opcache_prime.php

Übrigens Teil 2

Auf unserem Server prime ich ĂŒbrigens nicht (mehr). Es hat sich gezeigt, dass einfach zu viele Scripte tatsĂ€chlich im Alltagsbetrieb nie angegriffelt werden – da lohnt es sich einfach nicht, den wertvollen Arbeitsspeicher dafĂŒr aufzuwenden.

Hintergrund ist, dass im WordPress-Universum Plugins nicht immer nur unbedingt genau eine Sache gut machen (etwas, was ich an Unix und seiner Philosophie so liebe) – manchmal können die neben einer bestimmten Sache noch ein Dutzend anderer, die ich nie brauche. Und die dafĂŒr nötigen Scripte mĂŒssen natĂŒrlich auch nicht gecached werden.

Übrigens sage ich „gekĂ€itscht“ und nicht „gekÀÀÀÀscht“. DiesbezĂŒglich hat mich ein Kollege letztens versucht zu korrigieren. Der lebt jetzt in Sibireien und sucht nach Öl. Naja. Nicht wirklich.

Opcache precompile

Grad hatte ich ja davon erzĂ€hlt, dass ich wissen wollte, wie groß die PHP-Scripte in einer bestimmten Hierarchie sind.

Das habe ich jetzt fĂŒr ein paar andere VerzeichnisbĂ€ume durchgelutscht und bin auf das Ergebnis gekommen, das

Opcache-Limit einzustellen 😀

Nun ja. Ganz fĂŒrchterlich großes Kino, eine Konfigurationsdatei anpacken und fertig.

Möchte man meinen, ja.

Aber doch nicht mit dem Dicken. Da gibt es doch so fĂŒrchterlich viele lustige Schalterchen, an denen man herumspielen kann, also suchen wir doch einfach mal eine Einstellungskombination, die möglichst maximalen Nutzen bei minimalem Schaden Speicherverbrauch möglich macht.

Was wollen wir?

Wir wollen sĂ€mtliche und alle noch so unnĂŒtzen PHP-Scripte, die irgendwann und irgendwie mal ausgefĂŒhrt werden, im OpCache haben.

Stecken wir uns doch einfach mal vernĂŒnftig hohe Ziele 🙂

Was wissen wir?

ZunĂ€chst mal, wir haben uns gerade die GrĂ¶ĂŸen besorgt, die unsere PHP-Scriptdateien insgesamt haben.

In meinem Fall waren das so 140MB und ’nen bisschen Kleingeld. Unterstellen wir des Weiteren, das die Scripte vollstĂ€ndig inklusive dem ganzen MĂŒll (also den fast immer sinnlosen Kommentaren… was hab ich davon, wenn mir jemand sagt, dass jetzt sofort eine Schleife losgeht – statt mir zu sagen, wieso). Denn die Erfahrung lehrt, wenn die Kommentare beim Cachen abgeklemmt werden (was möglich ist), lĂ€uft nicht mehr alles, weil irgendein Held auf irgendwelche Kommentare angewiesen ist.

Ich könnte manchmal so brechen… wie auch immer. Ist halt so.

Und was wir auch noch wissen, naja, ich weiß es, ihr noch nicht – die Anzahl der PHP-Scripte. Das bekommt ihr hin, oder? Ich hab das so gemacht:

find . -type f -name '*.php' | wc -l

Das spuckt mir aus, wie viele Zeilen Output der find geworfen hat – und die wiederum entspricht dann der Anzahl der PHP-Dateien. Geht sicher eleganter, aber „it gets the job done“ und muss nicht erst durch ein Gremien beraten und durch ein weiteres beschlossen werden – „einfach mal machen“, meine Devise (…und manchmal hinterher doch noch mal nachdenken…).

Ach so, das Ergebnis. Bei mir waren es so 10.000 Dateien.

Wie…?

Mit vim.

Ok – jetzt mal im Ernst, WIE???

Hmpf. Ok.

vim /usr/local/etc/php56/conf.d/opcache.ini

Und bei der Anzeige erst mal einen frischen Kaffee holen und auf dem zweiten Bildschirm (ein dritter wĂ€re mal eine Maßnahme) schon mal Google anwerfen und jede einzelne Option nachrecherchieren.

Ja wirklich. Jede. Und auch die, die nicht in der Standard-Konfiguration steht. Denn da sind wahre SchĂ€tze verborgen 😀

Ok. Dann eben nicht, ich finde es ja immer total spannend die ganzen Optionen auszuprobieren…

Endergebnis

In meinem Fall kam ich mit folgendem Erguss heraus, da ist vermutlich immer noch Spielraum fĂŒr mehr Nerdyness, aber so tut es es erstmal und nimmt akzeptable Resourcen in Anspruch.

zend_extension = opcache.so

[opcache]
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 196
opcache.interned_strings_buffer = 64
opcache.max_accelerated_files = 12288
opcache.max_wasted_percentage = 10
opcache.use_cwd = 1
opcache.validate_timestamps = 1
opcache.revalidate_freq = 30
opcache.revalidate_path = 0
opcache.save_comments = 1
opcache.enable_file_override = 1
opcache.optimization_level = "0xffffffff"
opcache.inherited_hack = 1
opcache.dups_fix = 0
opcache.blacklist_filename =
opcache.max_file_size = 0
opcache.consistency_checks = 0
opcache.force_restart_timeout = 180
opcache.log_verbosity_level = 0
opcache.preferred_memory_model =
opcache.protect_memory = 0
opcache.mmap_base =
opcache.restrict_api =
opcache.enable_file_override = On
opcache.file_cache = "/var/opcache/cache.dat"

Die Fetten sind die, auf die es wirklich ankommt. Die Roten (ok und Fetten) sind die, bei denen ich noch ĂŒberlege, ob ich mir ein kleines Auswertungsscript schreiben soll, das die maximal und mit machbarem Aufwand parsbaren, konstanten Strings aus den Scripten extrahiert und die LĂ€ngen dann summiert… hebe ich mir fĂŒr heute Nacht auf – nicht.

Die memory_consumption muss man leider aus mir nicht erklĂ€rbaren GrĂŒnden locker mal doppelt so hoch wĂ€hlen wie die tatsĂ€chliche ScriptgrĂ¶ĂŸe. Auch noch eine offene Baustelle:

  • KlĂ€ren, wieso memory_consumption so viel höher eingestellt werden muss.

Kleiner Pro-Tipp: Nicht das fast-shutdown aktivieren!!11eins. Das shreddert ziemlich schwer bemerkbar und nur in seltsam seltenen Situationen die Verarbeitung. Mal ganz abgesehen davon:

If enabled, a fast shutdown sequence is used that doesn’t free each allocated block, but relies on the Zend Engine memory manager to deallocate the entire set of request variables en masse.

Sich auf die Garbage Collection der Runtime zu verlassen hat irgendwie was von Java. Und da haben wir was gegen (*duck* & *weg*).

Du hast alles falsch gemacht, Mirko!

Falls jemandem auffĂ€llt, dass ich den Opcache dazu anhalte, dass er alle 30 Sekunden nachsieht, ob ein Script vielleicht zwischenzeitlich auf der Platte geĂ€ndert wurde und erneute in den Cache geladen werden sollte – ja, das trifft zu. Ist auch besser so in einer Umgebung, die stĂ€ndig von einem bestimmten, seltsamen Menschen befummelt wird und der nicht stĂ€ndig den Opcache neu per Hand befĂŒllen möchte.