PHP OPcache bei Deployments - Frischen Code bewusst statt zufällig aktivieren
Ein Deployment ersetzt PHP-Dateien auf dem Datenträger. Das allein beantwortet jedoch nicht, welche kompilierten Anweisungen ein langlebiger PHP-Prozess bei der nächsten Anfrage ausführt. Wenn OPcache beteiligt ist, wann wird neuer Code aktiv: sofort, nach einem kurzen Intervall, nach einer ausdrücklichen Invalidierung oder erst nach einem Neustart des betreffenden Prozesses?
Die nützliche Antwort lautet nicht: „Den Cache immer leeren.“ Stattdessen gilt es, einen zum Deployment-Verfahren passenden Freshness-Vertrag zu wählen und diesen anschließend in derselben Runtime zu prüfen, die Webanfragen verarbeitet. Dieser Artikel betrachtet genau dieses begrenzte Betriebsproblem für eine kleine PHP-Anwendung. Er verspricht kein Zero-Downtime-Deployment, schreibt keinen einzelnen PHP-FPM-Servicenamen vor und schätzt keine ungemessenen Leistungsgewinne.
OPcache hat eine bestimmte Aufgabe
PHP parst und kompiliert ein Skript normalerweise, bevor es ausgeführt wird. Die offizielle OPcache-Einführung erklärt, dass OPcache vorkompilierten Skript-Bytecode im Shared Memory speichert. Dadurch müssen Skripte bei späteren Anfragen nicht erneut geladen und geparst werden. Das ist seine nützliche, aber begrenzte Aufgabe.
OPcache ist weder ein HTTP-Response-Cache noch ein Datenbank-Cache, Composers Paket-Cache oder ein Cache für Anwendungsdaten. Das Leeren eines CDN aktualisiert keinen PHP-Bytecode. Auch composer install legt für sich genommen nicht fest, wann eine langlebige PHP-Runtime jedes geänderte Skript erkennt. Werden all diese Schichten lediglich als „der Cache“ behandelt, lassen sich Deployment-Fehler schwerer eingrenzen.
OPcache bietet außerdem mehr als einen Speichermodus. Sein üblicher In-Memory-Cache und der optionale File Cache sind nicht austauschbar. Diese Unterscheidung wird später wichtig: Die dokumentierte Funktion opcache_reset() setzt den In-Memory-Cache zurück, nicht den File Cache.
Timestamp-Validierung ist eine Freshness-Richtlinie
Die zentrale Einstellung ist opcache.validate_timestamps. Laut der PHP-Dokumentation zur OPcache-Runtime-Konfiguration prüft OPcache bei aktivierter Option gemäß opcache.revalidate_freq auf aktualisierte Skripte. Der Wert 0 führt bei jeder Anfrage eine Prüfung durch. Ein positiver Wert erlaubt ein Intervall, in dem zwischengespeicherter Bytecode weiterverwendet werden kann, bevor der Timestamp erneut geprüft wird.
; Illustrative policy: check script timestamps on every request.
opcache.validate_timestamps=1
opcache.revalidate_freq=0
Für eine überschaubare Website ist diese Richtlinie leicht nachvollziehbar: Wird eine vollständige neue Datei veröffentlicht, kann OPcache ihren geänderten Timestamp bei der nächsten Anfrage erkennen. „Bei jeder Anfrage prüfen“ bedeutet nicht „OPcache deaktivieren“; der kompilierte Bytecode kann weiterhin wiederverwendet werden, wenn sich der Quelltext nicht geändert hat. Die Prüfung kann zusätzlichen Aufwand verursachen, dessen Kosten jedoch von Anwendung, Dateisystem, Traffic und PHP-Build abhängen. Ein Benchmark eines anderen Servers kann nicht entscheiden, ob diese Kosten hier relevant sind.
Auch ein positives Revalidierungsintervall ist eine gültige Richtlinie, sofern der Release-Prozess das daraus entstehende Freshness-Fenster akzeptiert. Beträgt das Intervall beispielsweise zwei Sekunden, lautet der Vertrag nicht „neuer Code ist sofort aktiv“. Er besagt, dass OPcache entsprechend dem konfigurierten Intervall regelmäßig prüft. Deployments und Smoke Tests sollten kein stärkeres Verhalten behaupten, als die Konfiguration bietet.
Das Abschalten der Validierung überträgt Verantwortung
Bei opcache.validate_timestamps=0 erfordern Änderungen am Dateisystem laut PHP-Handbuch einen manuellen Reset, eine gezielte Invalidierung oder einen Neustart des betreffenden Webservers, bevor sie wirksam werden. Dadurch können Timestamp-Prüfungen aus dem Anfragepfad entfernt werden, doch die Freshness-Arbeit verschwindet nicht. Sie wird auf das Deployment-Verfahren übertragen.
; Use only when every release has a reliable refresh step.
opcache.validate_timestamps=0
Dieser Tausch kann in einem kontrollierten Release-System sinnvoll sein. Weniger überzeugend ist er, wenn Dateien direkt bearbeitet, Releases manuell kopiert werden oder der Betreiber nicht benennen kann, welcher Prozess den Cache besitzt. Unter solchen Bedingungen kann eine deaktivierte Validierung nach einer Quelltextänderung alten Bytecode unbegrenzt erhalten. Eine als „Produktionsoptimierung“ übernommene Einstellung ist unbemerkt zu einer Abhängigkeit für die Korrektheit geworden.
Auch Konfiguration besitzt einen Lebenszyklus. Die PHP-Dokumentation zur Konfigurationsdatei weist darauf hin, dass das Laden der Konfiguration von der Server API, kurz SAPI, abhängt. PHP als Servermodul liest seine Konfiguration beim Start, während CGI und CLI sie bei jedem Aufruf lesen; zudem können SAPI-spezifische Dateien und weitere eingelesene INI-Dateien gelten. Das Bearbeiten einer INI-Datei beweist daher nicht, dass ein bereits laufender Webprozess sie übernommen hat.
Invalidieren, Zurücksetzen und Neustarten beantworten verschiedene Fragen
Ein bekanntes Skript invalidieren
opcache_invalidate() richtet sich an ein einzelnes Skript. Ohne das Argument force invalidiert die Funktion nur, wenn die Änderungszeit des Quelltexts neuer als der zwischengespeicherte Bytecode ist; mit force=true erfolgt die Invalidierung unabhängig von diesem Vergleich. Ist OPcaches File Cache aktiviert, invalidiert die dokumentierte Funktion auch dessen entsprechenden Eintrag.
Das ist präzise, wenn ein Deployment die konkret geänderten Dateien kennt. Umständlich wird es, wenn ein Release Hunderte Skripte ändert, Dateien entfernt, einen Autoloader neu erzeugt oder Beziehungen zwischen Klassen verändert. Eine Liste erfolgreicher Aufrufe pro Datei beweist nicht automatisch, dass das Release intern kompatibel ist.
Den In-Memory-Cache zurücksetzen
opcache_reset() setzt den gesamten In-Memory-Opcode-Cache zurück. Skripte werden wieder geladen und geparst, sobald spätere Anfragen sie erreichen. Die Funktion setzt den optionalen File Cache nicht zurück und kann false liefern, wenn OPcache deaktiviert ist oder ein Neustart aussteht beziehungsweise bereits läuft.
Ein vollständiger Reset ist umfassender und einfacher als das Aufzählen von Dateien. Er verwirft jedoch auch weiterhin nützliche kompilierte Einträge, die nachfolgende Anfragen erneut aufbauen müssen. Noch wichtiger ist, dass eine Reset-Funktion in dem tatsächlich relevanten Cache-Kontext ausgeführt werden muss. Einen nicht authentifizierten Web-Endpunkt mit Reset-Funktion bereitzustellen, ist keine vernünftige Abkürzung; er erweitert die Steuerungsoberfläche der Produktion und kann wiederholte Anfragen in eine Störung verwandeln.
Den ausliefernden Prozess verwalten
Eine Prozess-Lifecycle-Operation kann eine klarere Grenze schaffen, wenn die Timestamp-Validierung deaktiviert ist oder Konfigurationsänderungen geladen werden müssen. Der genaue Befehl hängt von Betriebssystem, PHP-Paketierung, SAPI, Service Manager und Verfügbarkeitsanforderungen ab. Einen geratenen Servicenamen aus einem allgemeinen Tutorial neu zu starten, ist kein Deployment-Design.
Ein Neustart kann Arbeit unterbrechen; ein Graceful Reload kann alte und neue Worker überlappen lassen; und beide Vorgänge können scheitern. Das Deployment benötigt deshalb die tatsächliche Unit-Definition, ihr dokumentiertes Verhalten, einen Health Check, aktuelle Logs und einen Rollback-Pfad. „Der Befehl endete mit Exit-Code null“ ist ein engerer Nachweis als „das vorgesehene Release liefert jetzt korrekte Antworten“.
Die CLI sollte nicht für PHP-FPM sprechen
Befehle wie diese sind nützlich, beschreiben jedoch den CLI-Aufruf:
php --ini
php --ri "Zend OPcache"
Die Runtime-Konfiguration dokumentiert opcache.enable_cli separat; der dokumentierte Standardwert ist deaktiviert. Die Regeln für Konfigurationsdateien erlauben SAPIs außerdem, unterschiedliche Dateien zu laden. Folglich beschreibt die CLI-Ausgabe möglicherweise nicht die PHP-FPM-Worker, die die Website ausliefern. Sie ist ein Nachweis für eine Runtime, nicht automatisch für alle Runtimes.
Für die Web-Runtime sollten die erforderlichen Einstellungen über eine geschützte administrative Diagnose, Deployment-Instrumentierung oder Configuration Management erfasst werden. Eine vollständige phpinfo()-Seite gehört nicht ins öffentliche Web. Sie kann Pfade, Module, Umgebungsdetails und Konfiguration offenlegen, die nicht öffentlich sein müssen.
Sofern die Zugriffsrichtlinie es erlaubt, kann opcache_get_status(false) den Zustand der aktuellen In-Memory-Cache-Instanz melden, ohne jedes zwischengespeicherte Skript aufzulisten. Dokumentierte Felder zeigen unter anderem, ob der Cache aktiviert oder voll ist, den Neustartstatus, die Speichernutzung und Statistiken. Die Funktion berichtet nicht über den File Cache, und opcache.restrict_api kann den Zugriff verhindern. Diese Grenzen gehören zur Interpretation des Ergebnisses und sind keine Hindernisse, die umgangen werden sollten.
Frischer Bytecode repariert keine unsichere Dateiveröffentlichung
OPcaches opcache.file_update_protection verhindert das Caching von Dateien, die jünger als die konfigurierte Anzahl Sekunden sind. Die PHP-Dokumentation beschreibt dies als Schutz davor, unvollständig aktualisierte Dateien zu cachen, und weist darauf hin, dass der Wert auf null gesetzt werden kann, wenn alle Aktualisierungen atomar erfolgen.
Diese Einstellung ist keine vollständige Deployment-Transaktion. Werden Dateien einzeln im Live-Verzeichnis überschrieben, können verschiedene Anfragen eine Mischung aus Release-Zuständen sehen. Ein Skript kann auf eine Klasse oder Funktion verweisen, die eine andere Datei noch nicht erhalten hat. Timestamp-Prüfungen erkennen Änderungen; sie können das Kopieren mehrerer Dateien nicht atomar machen.
Ein sicherer Veröffentlichungsmechanismus baut ein vollständiges Release außerhalb des Live-Pfads, validiert es und führt anschließend einen kontrollierten Wechsel aus. Das konkrete Design kann Release-Verzeichnisse, unveränderliche Images oder ein Wartungsfenster verwenden. Symlink-basierte Releases erfordern gesonderte Sorgfalt, weil PHP auch einen Realpath-Cache besitzt. Die Freshness der Pfadauflösung und die des Opcodes hängen betrieblich zusammen, sind aber nicht derselbe Mechanismus.
Drei angemessene Modelle für eine kleine Anwendung
Modell A: Timestamp-Validierung bei jeder Anfrage
validate_timestamps=1 und revalidate_freq=0 werden verwendet, Dateien sicher veröffentlicht und anschließend repräsentative Smoke Requests gesendet. Dieses Modell bevorzugt eine einfache Freshness-Regel gegenüber der Vermeidung jeder Timestamp-Prüfung. Es ist ein vernünftiger Ausgangspunkt, wenn der Traffic überschaubar ist und keine lokale Messung die Validierung als relevanten Engpass ausweist.
Modell B: Timestamp-Validierung mit begrenztem Intervall
Eine positive Revalidierungsfrequenz wird verwendet und das entstehende Fenster ausdrücklich akzeptiert. Die Verifikation sollte entsprechend dieser Richtlinie warten oder einen erneuten Versuch durchführen, statt aus einer einzigen sofortigen Anfrage zu schließen, dass dauerhaft das falsche Release läuft. Das Intervall sollte eine betriebliche Entscheidung sein und keine undokumentierte Überraschung.
Modell C: Keine Timestamp-Validierung, Release-gesteuerter Refresh
Die Validierung sollte nur deaktiviert werden, wenn jedes Deployment einen authentifizierten und beobachtbaren Refresh-Schritt für die ausliefernde Runtime besitzt. Ein Release sollte sicher abbrechen, wenn dieser Schritt fehlschlägt. Dieses Modell kann Prüfungen im Anfragepfad reduzieren, koppelt die Korrektheit jedoch an das Release-System und erhöht die Kosten, wenn dieses für eine „schnelle Änderung“ umgangen wird.
Keines dieser Modelle ist immer das beste. Die passende Wahl hängt vom gemessenen Workload, der Deployment-Disziplin, der akzeptablen Freshness-Verzögerung und davon ab, wie leicht sich der ausliefernde Prozess ohne schädliche Unterbrechung verwalten lässt.
Eine Deployment-Checkliste, die Nachweise liefert
- Die ausliefernde SAPI, geladene INI-Dateien, Timestamp-Richtlinie, das Revalidierungsintervall und die Aktivierung des File Cache dokumentieren.
- Festlegen, wann neuer Code unter dieser Richtlinie aktiv sein soll.
- Ein vollständiges Release bauen und validieren, bevor es Anfragen ausgesetzt wird.
- Über einen atomaren Wechsel oder ein ausdrückliches Wartungsverfahren veröffentlichen, statt eine unbekannte Folge von Live-Dateien zu kopieren.
- Falls die Richtlinie Invalidierung, Reset oder Prozessverwaltung verlangt, dies über einen geschützten Deployment-Pfad ausführen und das Ergebnis prüfen.
- Cache- oder Prozesszustand im Webkontext beobachten, nicht nur über PHP CLI.
- Das geänderte Verhalten, einen benachbarten unveränderten Pfad und einen Fehlerpfad anfragen; aktuelle Anwendungs- und PHP-FPM-Logs prüfen.
- Das vorherige Release und ein getestetes Wiederherstellungsverfahren bereithalten.
Ein Versionsmarker, der nur über einen authentifizierten Health Check erreichbar ist, kann diese Verifikation klarer machen. Er sollte das Release-Artefakt bezeichnen, keine Secrets offenlegen und nicht so tun, als beweise ein einzelner Marker die Funktion aller Routen. Der Marker beantwortet „welches Release hat geantwortet“; Verhaltenstests beantworten, ob dieses Release funktioniert.
Fazit
OPcache macht die Freshness von PHP-Quelltext zu einem Teil des Deployment-Vertrags. Bei aktivierter Timestamp-Validierung umfasst dieser Vertrag das Revalidierungsintervall. Bei deaktivierter Validierung muss das Release-System die betreffende Runtime gezielt aktualisieren. Gezielte Invalidierung, vollständiger Reset und Prozess-Lifecycle-Operationen sind unterschiedliche Werkzeuge und keine austauschbaren Beschwörungsformeln.
Für eine kleine Anwendung ist oft diejenige Richtlinie am besten vertretbar, die sich mit möglichst wenigen versteckten Annahmen erklären und prüfen lässt. Timestamp-Prüfungen sollten erst dann optimiert werden, wenn Messungen die zusätzliche Abhängigkeit vom Release-Prozess rechtfertigen. Bis dahin ist das Wissen, wann neuer Code aktiv wird, wertvoller als ein anspruchsvoll wirkender Befehl zum Leeren des Caches.
