Ein Importlauf verarbeitet zwanzigtausend Datensätze und meldet am Ende Erfolg. Zwei Minuten später fällt der Strom aus. Nach dem Neustart fehlen die letzten dreihundert Zeilen, obwohl jeder Schreibaufruf einen gültigen Wert zurückgegeben hatte.
Im Bild liegen zwischen der Anwendung und der Platte zwei Zwischenspeicher. Wer nur schreibt, gibt die Daten am ersten ab und weiß nicht, wie weit sie kommen.
Was PHP fsync() macht
PHP fsync() verlangt vom Betriebssystem, alle für eine geöffnete Datei zwischengespeicherten Daten auf das Speichergerät zu schreiben. Der Aufruf kehrt erst zurück, wenn das Betriebssystem diesen Vorgang bestätigt. Genau darin liegt der Unterschied zu einem gewöhnlichen Schreibaufruf, der sofort weiterläuft.
<?php
$handle = fopen('/var/daten/buchungen.log', 'ab');
fwrite($handle, "4711;120,50;2026-09-20\n");
if (!fsync($handle)) {
error_log('Synchronisierung fehlgeschlagen');
}
fclose($handle);
Der Rückgabewert ist wahr oder falsch und gehört geprüft. Schlägt der Aufruf fehl, liegen die Daten weiterhin nur im Zwischenspeicher, und die Anwendung sollte das keinesfalls als erfolgreichen Abschluss melden.
Die Signatur im Original
Die englische Beschreibung macht in einem Satz klar, worin sich die Funktion von ihren Verwandten unterscheidet:
fsync(resource $stream): bool
/* stream - an open file pointer, as returned by fopen()
return value - true on success or false on failure
The function synchronizes changes to the file, including its
meta-data, with the storage device. It asks the operating system
to flush all buffered modifications of the open file descriptor
to the disk. Unlike fflush() it does not stop at the operating
system, it waits until the write has been acknowledged. */
Der letzte Halbsatz ist der ganze Punkt. Ein gewöhnliches Leeren des Puffers reicht die Daten weiter, PHP fsync() wartet auf die Bestätigung.
Der Weg der Daten: zwei Puffer bis zur Platte
Zwischen einem Schreibaufruf im PHP-Skript und der physischen Speicherung liegen zwei Zwischenstationen. PHP hält einen eigenen Puffer, damit nicht jeder kleine Schreibvorgang das Betriebssystem bemüht. Das Betriebssystem hält einen zweiten, damit die Platte nicht bei jeder Änderung anlaufen muss.
flowchart TD
A[PHP-Skript] -->|fwrite| B
B[Puffer in PHP] -->|fflush| C
C[Puffer im Betriebssystem] -->|fsync| D
D[Speichergeraet]
C -->|irgendwann von selbst| D
Der untere Pfeil ist der Normalfall. Das Betriebssystem schreibt seinen Puffer nach eigenem Ermessen weg, meist innerhalb weniger Sekunden. Bei einem Absturz in diesem Zeitfenster sind die Daten verloren, und genau dieses Fenster schließt PHP fsync().
fflush(), fsync() und fdatasync() im Vergleich
Drei Funktionen mit ähnlichen Namen, die an drei verschiedenen Stellen ansetzen. Die Verwechslung von fflush() und PHP fsync() ist der häufigste Irrtum in diesem Umfeld.
| Funktion | Bringt die Daten bis | Metadaten | Kosten |
fflush() | zum Betriebssystem | nein | sehr gering |
fdatasync() | auf das Gerät | nur wenn nötig | hoch |
fsync() | auf das Gerät | ja | hoch |
<?php
/* fflush leert nur den Puffer von PHP. Danach liegt alles
im Betriebssystem und ist bei einem Absturz weiterhin weg. */
fflush($handle);
/* fdatasync schreibt die Nutzdaten auf das Geraet, laesst
aber Metadaten wie die Aenderungszeit aussen vor. */
fdatasync($handle);
/* fsync schreibt Nutzdaten und Metadaten. */
fsync($handle);
Wenn eine Datei ihre Größe behält und nur der Inhalt an bekannten Stellen geändert wird, genügt fdatasync() und spart einen Schreibvorgang. In allen anderen Fällen, besonders beim Anhängen, ist PHP fsync() die richtige Wahl, weil die neue Dateigröße selbst ein Metadatum ist.
Das sichere Speichermuster
Eine synchronisierte Datei kann trotzdem halb geschrieben sein, wenn der Absturz mitten im Schreiben passiert. Der Ausweg ist, gar nicht erst in die Zieldatei zu schreiben. Stattdessen entsteht eine temporäre Datei im selben Verzeichnis, die erst nach der Synchronisierung an ihren Platz rückt.
<?php
function sicherSpeichern(string $ziel, string $inhalt): bool
{
$temp = $ziel . '.tmp' . getmypid();
$handle = fopen($temp, 'wb');
if ($handle === false) {
return false;
}
fwrite($handle, $inhalt);
fsync($handle); /* jetzt liegt der Inhalt sicher */
fclose($handle);
return rename($temp, $ziel); /* atomar im selben Dateisystem */
}
Das Umbenennen innerhalb desselben Dateisystems ist unteilbar. Es gibt keinen Moment, in dem die Zieldatei halb alt und halb neu ist: Entweder zeigt der Verzeichniseintrag auf die alte Fassung oder auf die vollständige neue. Wer parallel schreibende Prozesse hat, kombiniert das mit PHP flock() für Dateisperren beim parallelen Schreiben.
Was PHP fsync() kostet
Der Aufruf wartet auf die Hardware, und das dauert. Je nach Gerät und Dateisystem liegt eine einzelne Synchronisierung im Bereich von wenigen Millisekunden. Das klingt harmlos, summiert sich aber sofort: Bei zwanzigtausend Datensätzen und je einem Aufruf pro Satz wird aus einem Importlauf von zwei Sekunden einer von mehreren Minuten.
<?php
$zaehler = 0;
foreach ($datensaetze as $satz) {
fwrite($handle, $satz . "\n");
/* Nicht nach jeder Zeile, sondern in Bloecken.
Im schlimmsten Fall gehen 500 Zeilen verloren,
dafuer bleibt der Lauf brauchbar schnell. */
if (++$zaehler % 500 === 0) {
fsync($handle);
}
}
fsync($handle);
Die Blockgröße ist eine bewusste Abwägung zwischen Geschwindigkeit und der Menge, die im Ernstfall verloren geht. Fachlich getrieben wird sie von der Frage, wie viel Nacharbeit ein Verlust bedeutet. Bei Buchungen ist die Antwort meist eine andere als bei Messwerten.
Das Verzeichnis nicht vergessen
Eine neu angelegte Datei besteht aus zwei Teilen: dem Inhalt und dem Eintrag im Verzeichnis. PHP fsync() sichert den Inhalt. Der Verzeichniseintrag gehört aber zum Verzeichnis, und das ist unter Unix ebenfalls eine Datei.
<?php
/* Nur unter Unix. Windows laesst ein Verzeichnis
nicht als Datei oeffnen und meldet einen Fehler. */
if (DIRECTORY_SEPARATOR === '/') {
$verzeichnis = fopen(dirname($ziel), 'r');
if ($verzeichnis !== false) {
fsync($verzeichnis);
fclose($verzeichnis);
}
}
In der Praxis fällt dieser Schritt selten auf, weil die meisten Dateisysteme Verzeichnisänderungen ohnehin zügig festschreiben. Wer eine lückenlose Zusage braucht, kommt aber nicht darum herum.
Verfügbarkeit: erst ab PHP 8.1
Die Funktion kam mit PHP 8.1 dazu, zusammen mit fdatasync(). Auf älteren Umgebungen führt der Aufruf zu einem schwerwiegenden Fehler, weshalb auf gemeinsam genutzten Servern eine Prüfung sinnvoll ist.
<?php
if (function_exists('fsync')) {
fsync($handle);
} else {
fflush($handle); /* schwaechere Zusage, aber besser als nichts */
}
Dass die Funktion so spät kam, hat nichts mit fehlendem Bedarf zu tun. Datenbanken und Protokolldienste haben denselben Systemaufruf immer schon genutzt, PHP hatte ihn nur nie zugänglich gemacht. Wer Dateien schreibt, findet die passenden Grundlagen in PHP file_put_contents zum Schreiben, Anhängen und Sichern von Dateien.
Wo die Zusage endet
Ein Punkt gehört zur Ehrlichkeit dazu: PHP fsync() gibt die Bestätigung des Betriebssystems weiter, und diese ist nur so verlässlich wie die Ebene darunter. Festplatten und SSDs haben einen eigenen Schreibcache und melden den Vorgang gelegentlich als abgeschlossen, bevor die Daten dauerhaft liegen.
Bei Netzlaufwerken gilt die Zusage des Protokolls, nicht die einer Platte. Und in virtuellen Umgebungen hängt das Ergebnis davon ab, wie der Wirt seine Speicherschicht konfiguriert hat. Für die allermeisten Anwendungen ist das ohne Belang, für eine Nachweispflicht dagegen ein Punkt, den man mit dem Betrieb klären sollte.
Fazit
Ein erfolgreicher Schreibaufruf sagt aus, dass PHP die Daten übergeben hat. Wo sie in diesem Moment liegen, sagt er nicht. PHP fsync() schließt diese Lücke, indem es auf die Bestätigung des Betriebssystems wartet, und fflush() tut das ausdrücklich nicht.
Praktisch bleiben drei Dinge hängen. Das Muster aus temporärer Datei, Synchronisierung und Umbenennen macht halb geschriebene Dateien unmöglich. Gesynct wird in Blöcken, nicht nach jedem Datensatz, sonst wird der Lauf unbrauchbar langsam. Und die Funktion gibt es erst ab PHP 8.1, was auf gemeinsam genutzten Servern eine Prüfung wert ist.