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.
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.