APCu Shared Memory für PHP konfigurieren: FPM, Apache mod_php und CLI im Vergleich

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:

sudo apt update
sudo apt install php8.3-apcu

Prüfe anschließend, ob das Modul in der jeweiligen SAPI aktiv ist:

# 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:

; 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:

# 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 → if false → store):

// ❌ 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:

//  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:

$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 heißt 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:

Im Seminar PHP OOP und fortgeschrittene Technologien vertiefen wir hochperformante Anwendungsarchitekturen und Thread-sichere In-Memory-Konzepte in der Praxis.