Viele Webanwendungen greifen bei Caching-Bedarf reflexartig zu Redis oder Memcached. Doch für Daten, die auf demselben Server verarbeitet werden — wie Anwendungs-Konfigurationen, Übersetzungs-Tabellen, Routing-Maps oder berechnete Metadaten — erzeugt ein externer Cache unnötigen Overhead: TCP-Handshakes, Serialisierung und Kontextwechsel kosten wertvolle Millisekunden. Genau hier ist **APCu (APC User Cache)** unschlagbar: Daten liegen als native C-Strukturen im Shared Memory des Betriebssystems. Ein `apcu_fetch()` ist kein Netzwerkanruf, sondern ein direkter RAM-Zugriff in Nanosekunden.
APCu: Direkter Shared Memory Master-Prozess (SHM-Segment) Worker 1 Worker 2 Worker N Latenz: < 1 µs (In-Memory) Redis / Memcached: TCP/Socket PHP Worker Redis Daemon Socket/TCP Serialisierung + Syscalls + Network Stack Latenz: ~ 200 – 1.000 µs
Abbildung 1: APCu teilt ein gemeinsames Shared-Memory-Segment zwischen allen PHP-Prozessen ohne Inter-Prozess-Kommunikation über Netzwerk-Sockets.
--- ## 1. Abgrenzung: OPcache vs. APCu Häufig herrscht Verwirrung über die Rollenverteilung: - **OPcache:** Cacht ausschließlich vorkompilierten PHP-Bytecode (`.php`-Dateien). Er verhindert, dass PHP Skripte bei jedem Aufruf erneut parsen und kompilieren muss. Details dazu findest du in unserem Leitfaden zu PHP-Performance und OPcache. - **APCu:** Cacht reine **Anwendungsdaten** (Strings, Arrays, Objekte). Wenn du eine SQL-Abfrage, eine Konfigurationsdatei oder ein berechnetes Token zwischenspeichern willst, ist APCu das Werkzeug der Wahl. Beide Module arbeiten Hand in Hand und ergänzen sich perfekt. --- ## 2. Shared-Memory-Architektur in den verschiedenen PHP-Betriebsarten Der wichtigste Grund für Missverständnisse bei APCu ist die Annahme, der Cache sei „einfach global da“. In Wahrheit hängt die Lebensdauer und Reichweite des Shared-Memory-Segments strikt davon ab, **wie** PHP ausgeführt wird: ### A. PHP-FPM (Der Standard) - **Architektur:** Der `php-fpm`-Master-Prozess allokiert beim Booten das SHM-Segment via `mmap()` oder POSIX Shared Memory. Alle Kind-Worker (`pm.max_children`) greifen auf dieselben Speicheradressen zu. - **Reload:** Ein Graceful Reload (`systemctl reload php8.3-fpm`) startet die Worker neu und leert das APCu-Segment geordnet. - **Pool-Grenzen:** Jeder FPM-Pool (z. B. `www.conf` vs. `api.conf`), der unter einem eigenen Master oder separaten Rechten läuft, kann ein getrenntes Segment besitzen. ### B. Apache mit `mod_php` (Prefork MPM) - **Architektur:** Hier ist der Apache-Parent-Prozess der Master. Beim Server-Start reserviert Apache das Shared-Memory-Segment für alle geforkten `httpd`-Prozesse. - **Reload:** Ein `systemctl reload apache2` (Graceful Restart) behält das APCu-Segment oft bei, während ein harter Neustart (`restart`) das Segment komplett neu initialisiert. - **Hinweis:** Da `mod_php` an das Prefork-MPM gebunden ist (kein Threading), ist der RAM-Verbrauch je Prozess deutlich höher als bei FPM. ### C. Moderne Application Server: FrankenPHP, RoadRunner & Swoole In modernen Setups bleibt die PHP-Laufzeitumgebung als persistenter Daemon dauerhaft im Speicher: - **FrankenPHP / RoadRunner / Workerman:** Die Worker-Prozesse beenden sich nach einem Request nicht, sondern bleiben am Leben. - **Die Besonderheit:** In solchen Long-Running-Prozessen können einfache PHP-Variablen im Skript-Scope bereits requests-übergreifend gecacht werden! - **Rolle von APCu:** Laufen mehrere Worker-Prozesse (Multi-Worker), teilen diese normalen PHP-Speicher nicht. Hier dient APCu weiterhin als Brücke, um Daten zwischen den Workern synchron zu halten, ohne auf externe Dienste wie Redis zurückzugreifen. ### D. PHP-CLI (Kommandozeile & Cronjobs) - **Isolierter Speicher:** Ein Aufruf auf der Konsole (`php script.php` oder Cronjob) startet einen **eigenen, isolierten PHP-Prozess**. - **Warum `apc.enable_cli` standardmäßig aus ist:** Wenn ein CLI-Skript endet, stirbt sein Prozess und das Shared-Memory-Segment wird sofort vom Betriebssystem freigegeben. Ein Cache, der beim Skript-Ende erlischt, bringt selten Vorteile. - **Wann `apc.enable_cli = 1` sinnvoll ist:** 1. **Unit- & Integrationstests:** Tests (PHPUnit), die auf APCu-Funktionalität aufbauen. 2. **Dauerläufer-CLI-Dienste:** Eigene Konsolen-Worker oder Message-Consumer, die über Stunden laufen und In-Memory-Lookups brauchen. - ⚠ **Wichtig:** Ein Konsolenskript kann **niemals** auf den APCu-Speicher von PHP-FPM oder Apache zugreifen! Ein Vorwärmen des Web-Caches per CLI-Cronjob ist technisch unmöglich. --- ## 3. Installation von APCu Unter Debian und Ubuntu ist die Installation unkompliziert: ```bash sudo apt update sudo apt install php8.3-apcu ``` Prüfe anschließend, ob das Modul in der jeweiligen SAPI aktiv ist: ```bash # Für CLI: php -m | grep apcu # Für FPM (z.B. über phpinfo() im Web oder FPM-Test): php-fpm8.3 -m | grep apcu ``` --- ## 4. Die perfekten `php.ini`-Einstellungen für APCu Die Standard-Konfiguration der Distributionen reicht für produktive Umgebungen selten aus: Der Standard-Speicher (`apc.shm_size = 32M`) läuft bei größeren Projekten schnell voll, was zu ständigen Cache-Invalidierungen und Speicher-Fragmentierung führt. Erstelle oder bearbeite die Konfigurationsdatei `/etc/php/8.3/mods-available/apcu.ini`: ```ini ; APCu aktivieren extension=apcu.so apc.enabled=1 ; Größe des Shared-Memory-Segments (Standard 32M ist fast immer zu klein!) ; Für produktive Systeme meist 128M oder 256M empfohlen: apc.shm_size=128M ; Lebensdauer von Einträgen (in Sekunden) ; 0 = Einträge verbleiben bis zum manuellen Löschen oder Server-Neustart apc.ttl=7200 ; Garbage Collection TTL: Wie lange verwaiste Einträge im Speicher verbleiben dürfen apc.gc_ttl=3600 ; Slam Defense: Verhindert "Cache Stampedes" bei stark frequentierten Keys ; Wenn 100 Worker gleichzeitig feststellen, dass ein Key fehlt, berechnet nur einer den Wert neu. apc.slam_defense=1 ; CLI-Zugriff: Im Standard 0. Nur aktivieren (1), wenn du Tests oder Long-Running CLI-Worker nutzt. apc.enable_cli=0 ; Serializer-Backend apc.serializer=php ``` Nach Änderungen muss der Webdienst neu geladen werden: ```bash # Bei PHP-FPM: sudo systemctl reload php8.3-fpm # Bei Apache mod_php: sudo systemctl reload apache2 ``` --- ## 5. Saubere Nutzung im PHP-Code: Das Atomic Cache Pattern Ein häufiges Anti-Pattern ist das getrennte Prüfen und Schreiben (`fetch` $\rightarrow$ `if false` $\rightarrow$ `store`): ```php // ❌ SCHLECHT: Race Condition bei hohem Traffic (Cache Stampede) $val = apcu_fetch('my_heavy_key'); if ($val === false) { $val = calculateExpensiveData(); // 50 Worker tun das gleichzeitig! apcu_store('my_heavy_key', $val, 3600); } ``` Nutze stattdessen das atomare **`apcu_entry()`**. Diese Funktion garantiert, dass die Berechnungs-Callback-Funktion genau **einmal** ausgeführt wird, selbst wenn hunderte Requests gleichzeitig eintreffen: ```php // RICHTIG: Atomares Laden und Befüllen $data = apcu_entry('my_heavy_key', function($key) { // Wird garantiert nur von einem Prozess ausgeführt return calculateExpensiveData(); }, 3600); ``` --- ## 6. Diagnose & Monitoring: Ist der Cache gesund? APCu bringt mächtige Diagnose-Funktionen mit. In PHP kannst du den Status direkt auslesen: ```php $sma = apcu_sma_info(); $memUsed = $sma['seg_size'] - $sma['avail_mem']; $memTotal = $sma['seg_size']; $percent = round(($memUsed / $memTotal) * 100, 1); $info = apcu_cache_info(true); $hits = $info['num_hits']; $misses = $info['num_misses']; $hitRate = ($hits + $misses) > 0 ? round(($hits / ($hits + $misses)) * 100, 2) : 100; echo "APCu Speicherbelegung: {$percent}% ({$memUsed} / {$memTotal} Bytes)\n"; echo "Hit-Rate: {$hitRate}%\n"; ``` ### Typische Alarmsignale: - **Speicherbelegung nahe 100%:** `apc.shm_size` erhöhen! Wenn APCu vollläuft, beginnt das Segment zu fragmentieren. - **Viele Expulsions:** Wenn APCu Speicher freiräumen muss, bevor die reguläre `ttl` abgelaufen ist, ist der Speicher zu knapp bemessen. - **Hohe Miss-Rate:** Prüfe, ob Schlüssel mit zufälligen IDs generiert werden, die nie wiederverwendet werden. --- ## 7. Wann APCu die falsche Wahl ist So schnell APCu ist – es gibt Szenarien, in denen du auf Redis oder Datenbank-Caching setzen musst: - **Multi-Server-Umgebungen (Cluster):** Wenn deine Anwendung hinter einem Load Balancer auf mehreren Instanzen läuft, hat jeder Server sein eigenes APCu-Segment. Daten sind nicht knotenübergreifend synchron. - **Permanente Persistenz:** APCu ist flüchtig. Bei Server-Neustarts oder FPM-Neustarts sind die Daten weg. - **Sehr große Datenmengen:** APCu sollte für Hot-Data (Metadaten, Menübäume, Configs) reserviert sein, nicht für Gigabytes an Rohdaten. Für performancekritische Single-Server-Instanzen ist APCu jedoch der eleganteste, ressourcenschonendste Cache, den das PHP-Ökosystem bietet. --- ## Häufige Fragen (FAQ) ### Was ist der Unterschied zwischen APC und APCu? APC war das historische Gesamtpaket für PHP 5 (Opcode-Cache + User-Cache). Mit PHP 5.5 wurde der Opcode-Teil als offizieller `OPcache` in den PHP-Kern integriert. Der User-Cache wurde als eigenständige Erweiterung ausgekoppelt und heisst seither **APCu**. ### Warum sieht mein Cronjob oder CLI-Skript die Daten aus dem Webserver nicht? Weil PHP-CLI und PHP-FPM völlig getrennte Prozessräume sind. Das Shared-Memory-Segment wird pro Master-Prozess verwaltet. Ein CLI-Befehl allokiert bei gesetztem `apc.enable_cli=1` ein eigenes temporäres Segment, das beim Skriptende wieder zerstört wird. ### Wie leere ich den APCu-Cache im laufenden Betrieb? Entweder über einen Reload des Dienstes (`systemctl reload php8.3-fpm` bzw. `systemctl reload apache2`) oder programmatisch innerhalb eines Web-Requests über den Aufruf von `apcu_clear_cache()`. --- ## Weiterführende Themen & Praxis-Know-how Eine solide Cache-Architektur entfaltet ihre volle Kraft nur im Zusammenspiel mit sauberem Code und einer abgestimmten Datenbank. Weiterführende Praxis-Anleitungen: - PHP schneller konfigurieren: OPcache, Preloading, FPM - PHP-Performance: die acht teuersten Fehler im Code - PostgreSQL Performance-Settings optimieren Im Seminar PHP OOP und fortgeschrittene Technologien vertiefen wir hochperformante Anwendungsarchitekturen und Thread-sichere In-Memory-Konzepte in der Praxis.