TL; DR: Jeśli jądro Linuksa utraci buforowany zapis we / wy , czy jest jakiś sposób, aby aplikacja się dowiedziała?
Wiem, że potrzebujesz fsync()
pliku (i jego katalogu nadrzędnego) dla trwałości . Pytanie brzmi: jeśli jądro traci brudne bufory oczekujące na zapis z powodu błędu we / wy, w jaki sposób aplikacja może to wykryć i odzyskać lub przerwać?
Pomyśl o aplikacjach baz danych itp., Gdzie kolejność i trwałość zapisu mogą być kluczowe.
Zagubione zapisy? W jaki sposób?
Warstwa blok Czy Linux Kernel jest w pewnych okolicznościach stracić buforowane żądań I / O, które zostały wprowadzone z powodzeniem write()
, pwrite()
itp, z błędem jak:
Buffer I/O error on device dm-0, logical block 12345
lost page write due to I/O error on dm-0
(Zobacz end_buffer_write_sync(...)
i end_buffer_async_write(...)
wfs/buffer.c
środku).
W nowszych jądrach błąd będzie zawierał „utracony asynchroniczny zapis strony” , na przykład:
Buffer I/O error on dev dm-0, logical block 12345, lost async page write
Ponieważ aplikacja write()
została już zwrócona bez błędów, wydaje się, że nie ma możliwości zgłoszenia błędu z powrotem do aplikacji.
Wykrywanie ich?
Nie jestem zaznajomiony ze źródłami jądra, ale myślę , że ustawia AS_EIO
bufor, który nie został zapisany, jeśli wykonuje zapis asynchroniczny:
set_bit(AS_EIO, &page->mapping->flags);
set_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
SetPageError(page);
ale nie jest dla mnie jasne, czy i w jaki sposób aplikacja może się o tym dowiedzieć, kiedy później fsync()
będzie plik, aby potwierdzić, że znajduje się na dysku.
Wygląda na to, że wait_on_page_writeback_range(...)
wmm/filemap.c
potędze, do_sync_mapping_range(...)
wfs/sync.c
którym jest sprawdzany sys_sync_file_range(...)
. Zwraca, -EIO
jeśli nie można zapisać jednego lub więcej buforów.
Jeśli, jak się domyślam, rozprzestrzeni się to do fsync()
wyniku, to jeśli aplikacja panikuje i wyskakuje, jeśli otrzyma błąd we / wy fsync()
i wie, jak ponownie wykonać swoją pracę po ponownym uruchomieniu, to powinno być wystarczającym zabezpieczeniem?
Prawdopodobnie nie ma sposobu, aby aplikacja wiedziała, które przesunięcia bajtów w pliku odpowiadają utraconym stronom, aby mogła je przepisać, jeśli wie jak, ale jeśli aplikacja powtórzy całą swoją oczekującą pracę od ostatniego udanego fsync()
pliku, a to przepisuje wszelkie brudne bufory jądra odpowiadające utraconym zapisom w pliku, które powinny wyczyścić wszelkie flagi błędów we / wy na utraconych stronach i pozwolić na zakończenie następnej fsync()
- prawda?
Czy są zatem inne, nieszkodliwe okoliczności, do których fsync()
mogą powrócić, w -EIO
których ratowanie i ponawianie pracy byłoby zbyt drastyczne?
Czemu?
Oczywiście takie błędy nie powinny się zdarzyć. W tym przypadku błąd wynikał z niefortunnej interakcji między dm-multipath
ustawieniami domyślnymi sterownika a kodem rozpoznawczym używanym przez sieć SAN do zgłaszania niepowodzenia alokacji alokacji elastycznej. Ale to nie jedyna okoliczność, w której mogą się zdarzyć - widziałem również raporty o tym, na przykład z cienkiego aprowizowanego LVM, używanego przez libvirt, Docker i inne. Krytyczna aplikacja, taka jak baza danych, powinna próbować radzić sobie z takimi błędami, zamiast działać na ślepo, jakby wszystko było w porządku.
Jeśli jądro uważa, że można stracić zapisy bez umierania z powodu paniki jądra, aplikacje muszą znaleźć sposób, aby sobie z tym poradzić.
Praktyczny wpływ jest taki, że znalazłem przypadek, w którym problem wielościeżkowy z SAN spowodował utratę zapisów, które wylądowały, powodując uszkodzenie bazy danych, ponieważ DBMS nie wiedział, że jego zapisy nie powiodły się. Nie śmieszne.
źródło
Odpowiedzi:
fsync()
zwraca,-EIO
jeśli jądro utraciło zapis(Uwaga: wczesne części odnoszą się do starszych jąder; zaktualizowane poniżej, aby odzwierciedlały nowoczesne jądra)
Wygląda na to, że asynchroniczny zapis bufora w przypadku
end_buffer_async_write(...)
awarii ustawia-EIO
flagę na stronie uszkodzonego buforu dla pliku :która jest wykrywana przez
wait_on_page_writeback_range(...)
co wywołana przezdo_sync_mapping_range(...)
co wywołana przezsys_sync_file_range(...)
co wywołana przezsys_sync_file_range2(...)
do realizacji połączenia biblioteki Cfsync()
.Ale tylko raz!
Ten komentarz na
sys_sync_file_range
sugeruje, że gdy
fsync()
zwróci-EIO
lub (nieudokumentowane na stronie podręcznika)-ENOSPC
, wyczyści stan błędu, więc kolejnyfsync()
poinformuje o sukcesie, nawet jeśli strony nigdy nie zostały zapisane.Oczywiście
wait_on_page_writeback_range(...)
czyści bity błędów podczas ich testowania :Więc jeśli aplikacja spodziewa się, że może spróbować ponownie,
fsync()
aż się powiedzie i będzie ufać, że dane znajdują się na dysku, to jest strasznie źle.Jestem prawie pewien, że to jest źródło uszkodzenia danych, które znalazłem w DBMS. Ponawia próbę
fsync()
i myśli, że wszystko będzie dobrze, gdy się powiedzie.Czy to jest dozwolone?
Dokumentacja POSIX / SuS
fsync()
tak naprawdę nie określa tego w żaden sposób:Strona podręcznika systemu Linux po
fsync()
prostu nie mówi nic o tym, co dzieje się w przypadku awarii.Wygląda więc na to, że znaczenie
fsync()
błędów brzmi „nie wiem, co się stało z twoimi zapisami, mogło zadziałać lub nie, lepiej spróbuj ponownie, aby się upewnić”.Nowsze jądra
W
end_buffer_async_write
zestawach 4.9-EIO
na stronie, po prostu przezmapping_set_error
.Po stronie synchronizacji myślę, że jest podobnie, chociaż struktura jest teraz dość złożona do naśladowania.
filemap_check_errors
wmm/filemap.c
teraz robi:co ma podobny efekt. Wydaje się, że wszystkie kontrole błędów przechodzą,
filemap_check_errors
co przeprowadza test i wyczyszczenie:Używam
btrfs
na moim laptopie, ale kiedy tworzęext4
pętlę zwrotną do testowania/mnt/tmp
i ustawiam na niej sondę perf:Znajduję następujący stos wywołań w
perf report -T
:Czytanie sugeruje, że tak, nowoczesne jądra zachowują się tak samo.
Wydaje się to oznaczać, że jeśli
fsync()
(lub przypuszczalniewrite()
lubclose()
) powróci-EIO
, plik jest w jakimś niezdefiniowanym stanie między ostatnim pomyślnymfsync()
d lubclose()
d, a ostatnimwrite()
dziesięcioma stanami.Test
Zaimplementowałem przypadek testowy, aby zademonstrować to zachowanie .
Implikacje
DBMS może sobie z tym poradzić, uruchamiając odzyskiwanie po awarii. Jak, u licha, ma sobie z tym radzić zwykła aplikacja użytkownika?
fsync()
Strona człowiek nie daje ostrzeżenie, że to znaczy „fsync-if-you-feel-jak-to”, a ja się spodziewać wiele z aplikacji nie będzie dobrze poradzić sobie z tym zachowaniem.Zgłaszanie błędów
Dalsza lektura
Witryna lwn.net poruszyła ten temat w artykule „Ulepszona obsługa błędów w warstwie bloków” .
Wątek z listą dyskusyjną postgresql.org .
źródło
errno
jest całkowicie konstrukcją biblioteki C w przestrzeni użytkownika. Często ignoruje się różnice wartości zwracanej między wywołania systemowe a biblioteką C w ten sposób (jak Craig Ringer powyżej), ponieważ wartość zwracana przez błąd niezawodnie identyfikuje, do której z nich (wywołanie systemowe lub funkcja biblioteki C) się odnosi: „-1
zerrno==EIO
„odnosi się do funkcji biblioteki C, podczas gdy„-EIO
”odnosi się do wywołania systemowego. Wreszcie, internetowe strony podręcznika systemu Linux są najbardziej aktualnymi źródłami informacji o stronach podręcznika systemowego.fsync()
/fdatasync()
gdy rozmiar transakcji jest kompletnym plikiem; używającmmap()
/,msync()
gdy rozmiar transakcji jest wyrównany do strony; oraz używając niskiego poziomu I / Ofdatasync()
i wiele współbieżnych deskryptorów plików (jeden deskryptor i wątek na transakcję) do tego samego pliku w przeciwnym razie " . Specyficzne dla Linuksa blokady opisu otwartego pliku (fcntl()
,F_OFD_
) są bardzo przydatne w przypadku ostatniego.Nie zgadzam się.
write
może powrócić bez błędu, jeśli zapis jest po prostu w kolejce, ale błąd zostanie zgłoszony podczas następnej operacji, która będzie wymagała rzeczywistego zapisu na dysku, to znaczy w następnymfsync
, prawdopodobnie następnym zapisie, jeśli system zdecyduje się opróżnić pamięć podręczną io godzinie przynajmniej przy ostatnim zamknięciu pliku.Dlatego tak ważne jest, aby aplikacja przetestowała zwracaną wartość close, aby wykryć ewentualne błędy zapisu.
Jeśli naprawdę potrzebujesz umieć sprytnie przetwarzać błędy, musisz założyć, że wszystko, co zostało napisane od ostatniego sukcesu,
fsync
mogło się nie udać , a przynajmniej coś zawiodło.źródło
fsync()
lubclose()
pliku, jeśli otrzyma-EIO
odwrite()
,fsync()
lubclose()
. Cóż, to zabawne.write
(2) zapewnia mniej niż się spodziewasz. Strona podręcznika jest bardzo otwarta na temat semantyki udanegowrite()
połączenia:Możemy wywnioskować, że sukces
write()
oznacza jedynie, że dane dotarły do funkcji buforowania jądra. Jeśli utrwalanie bufora nie powiedzie się, późniejszy dostęp do deskryptora pliku zwróci kod błędu. To może być ostatecznośćclose()
. Strona podręcznikaclose
wywołania systemowego (2) zawiera następujące zdanie:Jeśli Twoja aplikacja musi utrwalać zapisywanie danych, musi używać
fsync
/fsyncdata
regularnie:źródło
fsync()
jest to wymagane. Ale w konkretnym przypadku, gdy jądro traci strony z powodu błędu I / O ,fsync()
nie powiedzie się? W jakich okolicznościach może to potem odnieść sukces?fsync()
zwraca się-EIO
w przypadku problemów z I / O (co by się przydało w innym przypadku?). Dzięki temu baza danych wie, że część poprzedniego zapisu nie powiodła się i może przejść do trybu odzyskiwania. Czy nie tego chcesz? Jaka jest motywacja Twojego ostatniego pytania? Czy chcesz wiedzieć, który zapis nie powiódł się, lub odzyskać deskryptor pliku do dalszego użytku?fsync()
mogą powrócić-EIO
tam, gdzie można bezpiecznie spróbować ponownie, i czy można stwierdzić różnicę.-EIO
. Jeśli każdy deskryptor pliku jest używany tylko przez jeden wątek na raz, ten wątek może wrócić do ostatniegofsync()
i powtórzyćwrite()
wywołania. Ale nadal, jeśli tewrite()
zapisują tylko część sektora, niezmodyfikowana część może nadal być uszkodzona.Użyj flagi O_SYNC podczas otwierania pliku. Zapewnia zapis danych na dysku.
Jeśli to cię nie zadowoli, nic nie będzie.
źródło
O_SYNC
to koszmar wydajności. Oznacza to, że aplikacja nie może robić nic innego, gdy występuje we / wy dysku, chyba że odrodzi się z wątków we / wy. Równie dobrze można powiedzieć, że buforowany interfejs I / O jest niebezpieczny i każdy powinien używać AIO. Z pewnością dyskretnie utracone zapisy nie mogą być akceptowane w buforowanych we / wy?O_DATASYNC
jest tylko trochę lepszy w tym względzie)Sprawdź zwracaną wartość zamknięcia. close może się nie powieść, podczas gdy buforowane zapisy wydają się udać.
źródło
open()
ing iclose()
ing plik co kilka sekund. dlatego mamyfsync()
...