TL; DR: Wenn der Linux-Kernel einen gepufferten E / A-Schreibvorgang verliert , kann die Anwendung dies herausfinden?
Ich weiß, dass Sie fsync()
die Datei (und das übergeordnete Verzeichnis) für die Haltbarkeit benötigen . Die Frage ist, ob der Kernel fehlerhafte Puffer verliert, deren Schreibvorgang aufgrund eines E / A-Fehlers aussteht. Wie kann die Anwendung dies erkennen und wiederherstellen oder abbrechen?
Denken Sie an Datenbankanwendungen usw., bei denen die Reihenfolge der Schreibvorgänge und die Schreibdauer von entscheidender Bedeutung sein können.
Verlorene schreibt? Wie?
Die Linux - Kernel-Block Schicht kann unter bestimmten Umständen verlieren I / O - Anfragen gepuffert , die erfolgreich durch eingereicht wurde write()
, pwrite()
wie usw., mit einem Fehler:
Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0
(Siehe end_buffer_write_sync(...)
und end_buffer_async_write(...)
infs/buffer.c
).
Auf neueren Kerneln enthält der Fehler stattdessen "Lost Async Page Write" , wie:
Buffer I/O error on dev dm-0, logical block 12345, lost async page write
Da die Anwendung write()
bereits fehlerfrei zurückgegeben wurde, scheint es keine Möglichkeit zu geben, einen Fehler an die Anwendung zurückzumelden.
Sie erkennen?
Ich bin mit den Kernelquellen nicht so vertraut, aber ich denke, dass sie AS_EIO
auf den Puffer gesetzt sind, der nicht ausgeschrieben werden konnte, wenn ein asynchroner Schreibvorgang ausgeführt wird:
set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);
Es ist mir jedoch unklar, ob oder wie die Anwendung dies herausfinden kann, wenn sie später in fsync()
der Datei bestätigt, dass sie sich auf der Festplatte befindet.
Es sieht aus wie wait_on_page_writeback_range(...)
inmm/filemap.c
Macht, do_sync_mapping_range(...)
infs/sync.c
der wiederum von genannt wird sys_sync_file_range(...)
. Es wird zurückgegeben, -EIO
wenn ein oder mehrere Puffer nicht geschrieben werden konnten.
Wenn sich dies, wie ich vermute, auf fsync()
das Ergebnis auswirkt, wenn die App in Panik gerät und ausfällt, wenn ein E / A-Fehler auftritt fsync()
und weiß, wie sie ihre Arbeit beim Neustart erneut ausführen kann, sollte dies eine ausreichende Sicherheit sein?
Vermutlich kann die App nicht erkennen, welche Byte-Offsets in einer Datei den verlorenen Seiten entsprechen, sodass sie diese neu schreiben kann, wenn sie weiß, wie, aber wenn die App alle anstehenden Arbeiten seit dem letzten erfolgreichen fsync()
der Datei wiederholt und diese neu schreibt Alle schmutzigen Kernel-Puffer, die verlorenen Schreibvorgängen für die Datei entsprechen, sollten alle E / A-Fehlerflags auf den verlorenen Seiten löschen und den nächsten fsync()
Abschluss ermöglichen - richtig?
Gibt es dann noch andere harmlose Umstände, fsync()
unter -EIO
denen die Rettung und Wiederholung von Arbeiten zu drastisch wäre?
Warum?
Natürlich sollten solche Fehler nicht auftreten. In diesem Fall ist der Fehler auf eine unglückliche Interaktion zwischen den dm-multipath
Standardeinstellungen des Treibers und dem vom SAN verwendeten Erfassungscode zurückzuführen, der den Fehler beim Zuweisen von Thin Provisioning-Speicher meldet. Dies ist jedoch nicht der einzige Umstand, unter dem sie auftreten können. Ich habe auch Berichte darüber von beispielsweise Thin Provisioning LVM gesehen, wie sie von libvirt, Docker und anderen verwendet werden. Eine kritische Anwendung wie eine Datenbank sollte versuchen, mit solchen Fehlern umzugehen, anstatt blind weiterzumachen, als ob alles in Ordnung wäre.
Wenn der Kernel der Meinung ist, dass es in Ordnung ist, Schreibvorgänge zu verlieren, ohne an einer Kernel-Panik zu sterben, müssen Anwendungen einen Weg finden, um damit umzugehen.
Die praktische Auswirkung ist, dass ich einen Fall gefunden habe, in dem ein Multipath-Problem mit einem SAN zu verlorenen Schreibvorgängen führte, die zu einer Beschädigung der Datenbank führten, weil das DBMS nicht wusste, dass seine Schreibvorgänge fehlgeschlagen waren. Kein Spaß.
quelle
Antworten:
fsync()
kehrt zurück-EIO
wenn der Kernel einen Schreibvorgang verloren hat(Hinweis: Der frühe Teil verweist auf ältere Kernel; unten aktualisiert, um moderne Kernel widerzuspiegeln.)
Es sieht so aus, als würde das Ausschreiben des asynchronen Puffers bei
end_buffer_async_write(...)
Fehlern ein-EIO
Flag auf der Seite für den fehlerhaften fehlerhaften Puffer für die Datei setzen :Dies wird dann erkannt von
wait_on_page_writeback_range(...)
aufgerufen vondo_sync_mapping_range(...)
aufgerufen vonsys_sync_file_range(...)
aufgerufensys_sync_file_range2(...)
, um den Aufruf der C-Bibliothek zu implementierenfsync()
.Aber nur einmal!
Dieser Kommentar zu
sys_sync_file_range
schlägt vor, dass bei
fsync()
Rückgabe-EIO
oder (undokumentiert in der Manpage) der Fehlerstatus so nachträglich gelöscht-ENOSPC
wirdfsync()
Meldung den Erfolg meldet, obwohl die Seiten nie geschrieben wurden.Sicher genug
wait_on_page_writeback_range(...)
löscht die Fehlerbits, wenn sie getestet werden :Wenn die Anwendung erwartet, dass sie es erneut versuchen kann,
fsync()
bis sie erfolgreich ist, und darauf vertraut, dass sich die Daten auf der Festplatte befinden, ist dies furchtbar falsch.Ich bin mir ziemlich sicher, dass dies die Quelle der Datenbeschädigung ist, die ich im DBMS gefunden habe. Es wird wiederholt
fsync()
und glaubt, dass alles gut wird, wenn es erfolgreich ist.Ist das erlaubt?
In den POSIX / SuS-Dokumenten wird
fsync()
dies nicht so oder so angegeben:Linux-Manpage für
fsync()
sagt einfach nichts darüber aus, was bei einem Fehler passiert.So scheint es, dass die Bedeutung von
fsync()
Fehlern "Keine Ahnung, was mit Ihren Schreibvorgängen passiert ist, ob es funktioniert hat oder nicht, versuchen Sie es noch einmal, um sicherzugehen".Neuere Kernel
Auf 4.9
end_buffer_async_write
Sätze-EIO
auf der Seite, nur übermapping_set_error
.Auf der Synchronisierungsseite denke ich, dass es ähnlich ist, obwohl die Struktur jetzt ziemlich komplex zu folgen ist.
filemap_check_errors
inmm/filemap.c
jetzt tut:das hat fast den gleichen Effekt. Fehlerprüfungen scheinen alle durchlaufen zu sein,
filemap_check_errors
was ein Test-and-Clear durchführt:Ich verwende es
btrfs
auf meinem Laptop, aber wenn ich einenext4
Loopback zum Testen erstelle/mnt/tmp
und eine Perf-Sonde darauf einrichte:Ich finde folgenden Aufrufstapel in
perf report -T
:Ein Durchlesen deutet darauf hin, dass sich moderne Kernel genauso verhalten.
Dies scheint zu bedeuten , dass , wenn
fsync()
(oder vermutlichwrite()
oderclose()
) zurückkehrt-EIO
, wird die Datei in einem gewissen undefinierten Zustand zwischen, wenn Sie das letzte Mal erfolgreichfsync()
d oderclose()
d er und seine zuletztwrite()
zehn Zustand.Prüfung
Ich habe einen Testfall implementiert, um dieses Verhalten zu demonstrieren .
Implikationen
Ein DBMS kann dies durch Eingabe der Absturzwiederherstellung bewältigen. Wie um alles in der Welt soll eine normale Benutzeranwendung damit umgehen? Die
fsync()
Manpage gibt keine Warnung aus, dass es "fsync-wenn-du-fühlst-wie-es" bedeutet, und ich gehe davon aus, dass viele Apps mit diesem Verhalten nicht gut umgehen können.Fehlerberichte
Weiterführende Literatur
lwn.net hat dies im Artikel "Verbesserte Fehlerbehandlung auf Blockebene" angesprochen .
Postgresql.org Mailinglisten-Thread .
quelle
errno
ist vollständig ein Konstrukt der Userspace C-Bibliothek. Es ist üblich, die Rückgabewertunterschiede zwischen den Syscalls und der C-Bibliothek wie folgt zu ignorieren (wie Craig Ringer oben), da der Fehlerrückgabewert zuverlässig angibt, auf welche (Syscall- oder C-Bibliotheksfunktion) verwiesen wird: "-1
miterrno==EIO
"bezieht sich auf eine C-Bibliotheksfunktion, während"-EIO
"sich auf einen Systemaufruf bezieht. Schließlich sind Linux-Manpages online die aktuellste Referenz für Linux-Manpages.fsync()
/ oderfdatasync()
wenn die Transaktionsgröße eine vollständige Datei ist; durch Verwendungmmap()
/msync()
wenn die Transaktionsgröße ein seitenausgerichteter Datensatz ist; und durch Verwendung von I auf niedriger Ebene / Ofdatasync()
,, und mehrere gleichzeitige Dateideskriptoren (ein Deskriptor und ein Thread pro Transaktion) für dieselbe Datei, andernfalls " . Die Linux-spezifischen Open File Description Locks (fcntl()
,F_OFD_
) sind bei der letzten sehr nützlich.Ich stimme nicht zu.
write
kann ohne Fehler zurückkehren, wenn der Schreibvorgang einfach in die Warteschlange gestellt wird. Der Fehler wird jedoch beim nächsten Vorgang gemeldet, bei dem das eigentliche Schreiben auf die Festplatte erforderlich ist, dh beim nächstenfsync
, möglicherweise bei einem nachfolgenden Schreibvorgang, wenn das System beschließt, den Cache zu leeren und um am wenigsten beim letzten Schließen der Datei.Aus diesem Grund ist es für die Anwendung wichtig, den Rückgabewert von close zu testen, um mögliche Schreibfehler zu erkennen.
Wenn Sie wirklich in der Lage sein müssen, eine clevere Fehlerverarbeitung durchzuführen, müssen Sie davon ausgehen, dass alles, was seit dem letzten Erfolg geschrieben wurde,
fsync
möglicherweise fehlgeschlagen ist und dass zumindest etwas fehlgeschlagen ist.quelle
fsync()
oderclose()
der Datei , wenn es sich um eine bekommt-EIO
vonwrite()
,fsync()
oderclose()
. Das macht Spaß.write
(2) bietet weniger als Sie erwarten. Die Manpage ist sehr offen über die Semantik eines erfolgreichenwrite()
Anrufs:Wir können daraus schließen, dass ein Erfolg
write()
lediglich bedeutet, dass die Daten die Pufferfunktionen des Kernels erreicht haben. Wenn das Fortbestehen des Puffers fehlschlägt, gibt ein nachfolgender Zugriff auf den Dateideskriptor den Fehlercode zurück. Als letztes Mittel kann das seinclose()
. Die Manpage desclose
Systemaufrufs (2) enthält den folgenden Satz:Wenn Ihre Anwendung das Abschreiben von Daten beibehalten muss, muss sie
fsync
/fsyncdata
regelmäßig verwenden:quelle
fsync()
erforderlich ist. Aber im konkreten Fall , in dem der Kernel die Seiten aufgrund eines E / A - Fehler verliert wirdfsync()
scheitern? Unter welchen Umständen kann es dann danach gelingen?fsync()
Rendite-EIO
bei E / A-Problemen an (Wofür wäre es sonst gut?). Die Datenbank weiß also, dass ein Teil eines vorherigen Schreibvorgangs fehlgeschlagen ist, und kann in den Wiederherstellungsmodus wechseln. Willst du das nicht? Was ist die Motivation für Ihre letzte Frage? Möchten Sie wissen, welcher Schreibvorgang fehlgeschlagen ist, oder den Dateideskriptor zur weiteren Verwendung wiederherstellen?fsync()
zurückkehren kann ,-EIO
wo es ist sicher zu wiederholen, und wenn es möglich ist, den Unterschied zu erkennen.-EIO
. Wenn jeder Dateideskriptor jeweils nur von einem Thread verwendet wird, kann dieser Thread zum letzten zurückkehrenfsync()
und diewrite()
Aufrufe wiederholen . Wenn diesewrite()
s jedoch nur einen Teil eines Sektors schreiben, ist der unveränderte Teil möglicherweise immer noch beschädigt.Verwenden Sie das O_SYNC-Flag, wenn Sie die Datei öffnen. Es stellt sicher, dass die Daten auf die Festplatte geschrieben werden.
Wenn dich das nicht befriedigt, wird es nichts geben.
quelle
O_SYNC
ist ein Albtraum für Leistung. Dies bedeutet, dass die Anwendung während der Datenträger-E / A nichts anderes tun kann , es sei denn, sie erzeugt E / A-Threads. Sie können auch sagen, dass die gepufferte E / A-Schnittstelle unsicher ist und jeder AIO verwenden sollte. Sicherlich können stillschweigend verlorene Schreibvorgänge in gepufferten E / A nicht akzeptabel sein?O_DATASYNC
ist in dieser Hinsicht nur geringfügig besser)Überprüfen Sie den Rückgabewert von close. Das Schließen kann fehlschlagen, während gepufferte Schreibvorgänge erfolgreich zu sein scheinen.
quelle
open()
ing undclose()
ing die Datei alle paar Sekunden. Deshalb haben wirfsync()
...