webentwicklung-frage-antwort-db.com.de

Schreiben von Programmen zur Behebung von E / A-Fehlern, die unter Linux verlorene Schreibvorgänge verursachen

TL; DR: Wenn der Linux-Kernel einen gepufferten E/A-Schreibvorgang verliert , kann die Anwendung dies dann irgendwie herausfinden?

Ich weiß, dass Sie die Datei (und ihr übergeordnetes Verzeichnis) für die Haltbarkeit fsync() müssen . Die Frage ist:Wenn der Kernel aufgrund eines E/A-Fehlers fehlerhafte Puffer verliert, für die Schreibvorgänge anstehen,Wie kann die Anwendung dies erkennen und wiederherstellen oder abbrechen?

Denken Sie an Datenbankanwendungen usw., bei denen die Reihenfolge der Schreibvorgänge und die Haltbarkeit der Schreibvorgänge von entscheidender Bedeutung sein können.

Lost schreibt? Wie?

Die Blockschicht des Linux-Kernels kann unter bestimmten Umständenlosegepufferte E/A-Anforderungen, die erfolgreich von write(), pwrite() usw. gesendet wurden, mit einem Fehler wie:

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(...) in fs/buffer.c ).

Auf neueren Kerneln enthält der Fehler stattdessen "Asynchrones Seitenschreiben verloren" , wie:

Buffer I/O error on dev dm-0, logical block 12345, lost async page write

Da das write() der Anwendung bereits fehlerfrei zurückgegeben wurde, scheint es keine Möglichkeit zu geben, einen Fehler an die Anwendung zurückzumelden.

Sie entdecken?

Ich bin nicht so vertraut mit den Kernel-Quellen, aber ichdenke,dass es AS_EIO Auf den Puffer setzt, der nicht ausgeschrieben werden konnte, wenn es funktioniert ein asynchrone schreiben:

    set_bit(AS_EIO, &page->mapping->flags);
    set_buffer_write_io_error(bh);
    clear_buffer_uptodate(bh);
    SetPageError(page);

mir ist jedoch unklar, ob oder wie die Anwendung dies herausfinden kann, wenn fsync() die Datei später zur Bestätigung auf der Festplatte abruft.

Es sieht so aus, als würde wait_on_page_writeback_range(...) in mm/filemap.c durch do_sync_mapping_range(...) in fs/sync.c aufgerufen sys_sync_file_range(...) . Es wird -EIO Zurückgegeben, wenn ein oder mehrere Puffer nicht geschrieben werden konnten.

Wenn sich dies, wie ich vermute, zum Ergebnis von fsync() ausbreitet, dann gerät die App in Panik und gerät außer Gefecht, wenn sie einen E/A-Fehler von fsync() erhält und weiß, wie man den Fehler korrigiert. Ist die Arbeit nach dem Neustart ausreichend?

Es gibt vermutlich keine Möglichkeit für die App, zu wissen,welcheByte-Offsets in einer Datei den verlorenen Seiten entsprechen, sodass sie neu geschrieben werden können, wenn sie weiß, wie, aber wenn die App alle wiederholt Die ausstehende Arbeit seit dem letzten erfolgreichen fsync() der Datei, die alle fehlerhaften Kernelpuffer für verlorene Schreibvorgänge gegen die Datei überschreibt, sollte alle E/A-Fehlerflags auf den verlorenen Seiten löschen und die nächste fsync() zu vervollständigen - oder?

Gibt es dann irgendwelche anderen harmlosen Umstände, in denen fsync()-EIO Zurückgeben könnte, wo das Aussteigen und Wiederherstellen von Arbeit zu drastisch wäre?

Warum?

Natürlich sollten solche Fehler nicht auftreten. In diesem Fall ergab sich der Fehler aus einer unglücklichen Interaktion zwischen den Standardeinstellungen des Treibers dm-multipath Und dem von SAN) verwendeten Erkennungscode, um einen Fehler bei der Zuweisung von Thin Provisioning-Speicher zu melden Es ist nicht der einzige Umstand, in dem siecanpassieren - Ich habe auch Berichte von Thin Provisioned LVM gesehen, wie sie beispielsweise von libvirt, Docker und anderen verwendet werden Eine kritische Anwendung wie eine Datenbank sollte versuchen, mit solchen Fehlern umzugehen, anstatt blindlings weiterzumachen, als ob alles in Ordnung wäre.

Wenn derKerneldenkt, dass es in Ordnung ist, Schreibvorgänge zu verlieren, ohne mit 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) verloren gegangenen Schreibzugriff aufgetreten ist, der zu einer Beschädigung der Datenbank geführt hat, da das DBMS nicht wusste, dass seine Schreibzugriffe fehlgeschlagen waren.

135
Craig Ringer

fsync() gibt -EIO zurück, wenn der Kernel einen Schreibvorgang verloren hat

(Hinweis: Der frühe Teil verweist auf ältere Kernel; unten aktualisiert, um den modernen Kernel widerzuspiegeln.)

Es sieht so aus, als ob beim Ausschreiben des asynchronen Puffers in end_buffer_async_write(...) failures ein -EIO -Flag auf der Seite mit dem fehlerhaften Puffer für die Datei gesetzt wurde:

set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);

das wird dann von wait_on_page_writeback_range(...) als von do_sync_mapping_range(...) als von sys_sync_file_range(...) als von sys_sync_file_range2(...) aufgerufen erkannt, um den C-Bibliotheksaufruf fsync() zu implementieren.

Aber nur einmal!

Dieser Kommentar zu sys_sync_file_range

168  * SYNC_FILE_RANGE_WAIT_BEFORE and SYNC_FILE_RANGE_WAIT_AFTER will detect any
169  * I/O errors or ENOSPC conditions and will return those to the caller, after
170  * clearing the EIO and ENOSPC flags in the address_space.

schlägt vor, dass wenn fsync()-EIO oder (undokumentiert in der Manpage) -ENOSPC zurückgibt, Fehlerstatus löschen, sodass eine nachfolgende fsync() sogar den Erfolg meldet obwohl die Seiten nie geschrieben wurden.

Sicher genug wait_on_page_writeback_range(...) löscht die Fehlerbits, wenn sie getestet werden :

301         /* Check for outstanding write errors */
302         if (test_and_clear_bit(AS_ENOSPC, &mapping->flags))
303                 ret = -ENOSPC;
304         if (test_and_clear_bit(AS_EIO, &mapping->flags))
305                 ret = -EIO;

Wenn die Anwendung also erwartet, dass sie fsync() erneut versuchen kann, 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 Ursache für die Datenbeschädigung ist, die ich im DBMS gefunden habe. Es wiederholt fsync() und denkt, dass alles gut sein wird, wenn es erfolgreich ist.

Ist das erlaubt?

Die POSIX/SuS-Dokumente zu fsync() spezifizieren dies nicht wirklich so oder so:

Wenn die Funktion fsync () fehlschlägt, kann nicht garantiert werden, dass ausstehende E/A-Vorgänge abgeschlossen wurden.

Die Linux-Manpage für fsync() sagt einfach nichts darüber aus, was bei einem Fehler passiert.

Die Bedeutung von fsync() -Fehlern scheint also "Keine Ahnung, was mit Ihren Schreibvorgängen passiert ist, ob sie funktioniert haben oder nicht, versuchen Sie es besser noch einmal, um sicherzugehen" zu sein.

Neuere Kernel

Am 4.9 end_buffer_async_write setzt -EIO auf der Seite, einfach über mapping_set_error.

    buffer_io_error(bh, ", lost async page write");
    mapping_set_error(page->mapping, -EIO);
    set_buffer_write_io_error(bh);
    clear_buffer_uptodate(bh);
    SetPageError(page);

Auf der Synchronisationsseite denke ich, dass es ähnlich ist, obwohl die Struktur nun ziemlich komplex ist, um zu folgen. filemap_check_errors in mm/filemap.c macht jetzt:

    if (test_bit(AS_EIO, &mapping->flags) &&
        test_and_clear_bit(AS_EIO, &mapping->flags))
            ret = -EIO;

das hat fast den gleichen Effekt. Fehlerprüfungen scheinen alle filemap_check_errors zu durchlaufen, was ein Test-and-Clear durchführt:

    if (test_bit(AS_EIO, &mapping->flags) &&
        test_and_clear_bit(AS_EIO, &mapping->flags))
            ret = -EIO;
    return ret;

Ich verwende btrfs auf meinem Laptop, aber wenn ich eine ext4 -Schleife zum Testen auf /mnt/tmp erstelle und einen Perf-Test darauf einrichte:

Sudo dd if=/dev/zero of=/tmp/ext bs=1M count=100
Sudo mke2fs -j -T ext4 /tmp/ext
Sudo mount -o loop /tmp/ext /mnt/tmp

Sudo perf probe filemap_check_errors

Sudo perf record -g -e probe:end_buffer_async_write -e probe:filemap_check_errors dd if=/dev/zero of=/mnt/tmp/test bs=4k count=1 conv=fsync

Ich finde die folgende Aufrufliste in perf report -T:

        ---__GI___libc_fsync
           entry_SYSCALL_64_fastpath
           sys_fsync
           do_fsync
           vfs_fsync_range
           ext4_sync_file
           filemap_write_and_wait_range
           filemap_check_errors

Ein Durchlesen deutet darauf hin, dass sich moderne Kernel genauso verhalten.

Dies scheint zu bedeuten, dass, wenn fsync() (oder vermutlich write() oder close()) -EIO zurückgibt, sich die Datei in einem undefinierten Zustand befindet, zwischen dem Zeitpunkt, an dem Sie erfolgreich fsync()d oder close()d it und dem zuletzt write()ten 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 Manpage fsync() gibt keine Warnung aus, dass dies "fsync-wenn-du-willst-es" bedeutet, und ich erwarte, dass eine Menge von Apps mit diesem Verhalten nicht gut zurechtkommt.

Fehlerberichte

Weitere Lektüre

lwn.net hat dies im Artikel "Verbesserte Block-Layer-Fehlerbehandlung" angesprochen.

postgresql.org-Mailinglisten-Thread .

89
Craig Ringer

Da write () der Anwendung bereits fehlerfrei zurückgegeben wurde, scheint es keine Möglichkeit zu geben, einen Fehler an die Anwendung zurückzumelden.

Ich stimme nicht zu. write kann ohne Fehler zurückgegeben werden, wenn der Schreibvorgang einfach in die Warteschlange gestellt wird. Der Fehler wird jedoch bei der nächsten Operation gemeldet, bei der das eigentliche Schreiben auf die Festplatte erforderlich ist, dh bei der nächsten fsync, möglicherweise auf einem Nachstehend schreiben, wenn das System beschließt, den Cache zu leeren und zumindest 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, clevere Fehler zu verarbeiten, müssen Sie davon ausgehen, dass alles, was seit dem letzten erfolgreichen fsynckann geschrieben wurde, fehlgeschlagen ist und dass zumindest etwas fehlgeschlagen ist .

22
Serge Ballesta

write (2) liefert weniger als erwartet. Die Manpage ist sehr offen über die Semantik eines erfolgreichen write() -Aufrufs:

Eine erfolgreiche Rückkehr von write() garantiert nicht, dass Daten auf die Festplatte geschrieben wurden. Bei einigen fehlerhaften Implementierungen ist nicht einmal garantiert, dass Speicherplatz für die Daten reserviert wurde. Die einzige Möglichkeit, um sicherzugehen, besteht darin, fsync (2) aufzurufen, nachdem Sie alle Ihre Daten geschrieben haben.

Wir können daraus schließen, dass eine erfolgreiche write() lediglich bedeutet, dass die Daten die Puffereinrichtungen des Kernels erreicht haben. Wenn das Fortbestehen des Puffers fehlschlägt, gibt ein nachfolgender Zugriff auf den Dateideskriptor den Fehlercode zurück. Als letzter Ausweg kann das close() sein. Die Manpage des Systemaufrufs close (2) enthält den folgenden Satz:

Es ist durchaus möglich, dass Fehler bei einer vorherigen write (2) -Operation zuerst bei der letzten close () gemeldet werden.

Wenn Ihre Anwendung weiterhin Daten schreiben muss, muss sie regelmäßig fsync/fsyncdata verwenden:

fsync() überträgt ("leert") alle modifizierten Kerndaten von (dh modifizierten Puffer-Cache-Seiten für) die Datei, auf die durch den Dateideskriptor fd verwiesen wird, auf das Plattengerät (oder ein anderes permanentes Speichergerät), so dass alle geänderten Informationen auch nach einem Absturz oder Neustart des Systems abgerufen werden können. Dies beinhaltet das Durchschreiben oder Leeren eines Festplattencaches, falls vorhanden. Der Anruf wird blockiert, bis das Gerät meldet, dass die Übertragung abgeschlossen ist.

1
fzgregor

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 zufriedenstellt, wird es nichts geben.

0
toughmanwang