2009-03-20 12 views
7

Podążałem za dyskusją na temat "błędu" na EXT4, który powoduje wyzerowanie plików w razie awarii, jeśli używa się "Utwórz plik tymczasowy, napisz tymczasowy plik, zmień nazwę tymczasowego pliku docelowego". POSIX mówi, że jeśli fsync() nie zostanie wywołana, nie możesz mieć pewności, że dane zostały spłukane na dysk twardy.Bezpieczny i skuteczny sposób modyfikacji wielu plików w systemach POSIX?

Oczywiście robi:

0) get the file contents (read it or make it somehow) 
1) open original file and truncate it 
2) write new contents 
3) close file 

nie jest dobra nawet z fsync(), jak komputer może się wysypać podczas 2) lub fsync() i kończy się z części pisemnej pliku.

Zwykle uważano, że jest to dość bezpieczne:

0) get the file contents (read it or make it somehow) 
1) open temp file 
2) write contents to temp file 
3) close temp file 
4) rename temp file to original file 

Niestety tak nie jest. Żeby było bezpiecznie na EXT4 będzie trzeba zrobić:

0) get the file contents (read it or make it somehow) 
1) open temp file 
2) write contents to temp file 
3) fsync() 
4) close temp file 
5) rename temp file to original file 

Byłoby to bezpieczne i na katastrofy powinien albo masz nowe zawartość pliku czy stary, nigdy wyzerowany zawartości lub częściowych zawartość. Ale jeśli aplikacja używa wielu plików, fsync() po każdym zapisie będzie wolny.

Moje pytanie brzmi: jak skutecznie zmodyfikować wiele plików w systemie, gdzie fsync() jest wymagany, aby mieć pewność, że zmiany zostały zapisane na dysku? I naprawdę chodzi mi o modyfikowanie wielu plików, tak jak w tysiącach plików. Modyfikowanie dwóch plików i robienie fsync() po każdym nie byłoby zbyt złe, ale fsync() spowalnia działanie podczas modyfikowania wielu plików.

EDYCJA: zmieniono plik fsync() close temp na kolejność corrent, dodano nacisk na pisanie wielu wielu plików.

Odpowiedz

0

Musisz zamienić 3 & 4 w swoim ostatnim wpisie - fsync(fd) używa deskryptora pliku. i nie widzę powodu, dla którego byłoby to szczególnie kosztowne - mimo to chcesz, aby dane zapisywane na dysku przez zamknięcie(). Tak więc koszt będzie taki sam pomiędzy tym, co chcesz, a co stanie się z fsync().

Jeśli koszt jest zbyt duży (i go masz) fdatasync(2) unikaj synchronizowania metadanych, więc powinno to być lżejszy koszt.

EDIT: Więc napisałem jakiś bardzo hacky kod testowy:

#include <unistd.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <sys/time.h> 
#include <time.h> 
#include <stdio.h> 
#include <string.h> 

static void testBasic() 
{ 
    int fd; 
    const char* text = "This is some text"; 

    fd = open("temp.tmp", O_WRONLY | O_CREAT); 
    write(fd,text,strlen(text)); 
    close(fd); 
    rename("temp.tmp","temp"); 
} 

static void testFsync() 
{ 
    int fd; 
    const char* text = "This is some text"; 

    fd = open("temp1", O_WRONLY | O_CREAT); 
    write(fd,text,strlen(text)); 
    fsync(fd); 
    close(fd); 
    rename("temp.tmp","temp"); 
} 

static void testFdatasync() 
{ 
    int fd; 
    const char* text = "This is some text"; 

    fd = open("temp1", O_WRONLY | O_CREAT); 
    write(fd,text,strlen(text)); 
    fdatasync(fd); 
    close(fd); 
    rename("temp.tmp","temp"); 
} 

#define ITERATIONS 10000 

static void testLoop(int type) 
{ 
    struct timeval before; 
    struct timeval after; 
    long seconds; 
    long usec; 
    int i; 

    gettimeofday(&before,NULL); 
    if (type == 1) 
    { 
     for (i = 0; i < ITERATIONS; i++) 
     { 
      testBasic(); 
     } 
    } 
    if (type == 2) 
    { 
     for (i = 0; i < ITERATIONS; i++) 
     { 
      testFsync(); 
     } 
    } 
    if (type == 3) 
    { 
     for (i = 0; i < ITERATIONS; i++) 
     { 
      testFdatasync(); 
     } 
    } 
    gettimeofday(&after,NULL); 

    seconds = (long)(after.tv_sec - before.tv_sec); 
    usec = (long)(after.tv_usec - before.tv_usec); 
    if (usec < 0) 
    { 
     seconds--; 
     usec += 1000000; 
    } 

    printf("%ld.%06ld\n",seconds,usec); 
} 

int main() 
{ 
    testLoop(1); 
    testLoop(2); 
    testLoop(3); 
    return 0; 
} 

na moim laptopie, który produkuje:

0.595782 
6.338329 
6.116894 

co sugeruje robi fsync() jest ~ 10 razy droższe. i fdatasync() jest nieco tańsze.

Zgaduję, że problem, który widzę, polega na tym, że , każda aplikacja o numerze będzie uważała, że ​​dane są wystarczająco ważne dla fsync(), więc korzyści związane z wydajnością łączenia zapisów w ciągu minuty zostaną wyeliminowane.

+0

Przy pomocy metody zmiany nazwy można było napisać 100 000 plików konfiguracyjnych bez fsync(), a wykonanie 100 000 fsync() byłoby wolne. – Raynet

+0

"chcesz, aby dane zapisywane na dysku przez zamknięcie() w każdym razie" O czym ty mówisz? Close ma tylko zwolnić opis pliku zgodnie z POSIX. Nie ma wymogu płukania buforów. http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html – ArekBulski

1

Moja własna odpowiedź polegałaby na zachowaniu zmian w plikach tymczasowych, a po skończeniu zapisywania ich wszystkich, wykonaj jedną fsync(), a następnie zmień ich nazwę.

+1

fsync() to per-fd - być może myślisz o synchronizacji()? –

+0

Myślę, że nadszedł czas na benchmark - dlaczego go nie napiszesz, a zobaczymy, jaki jest wpływ? –

+0

Zrobiłem szybki test porównawczy, scenariusz zmiany nazwy jest 10-20% wolniejszy z fsync() dla każdego pliku po zapisie. Zakładam, że fsync() jest właściwym poleceniem, ponieważ opróżnia plik, który właśnie napisałem, nie chcę niczego spłukiwać. – Raynet

3

Krótka odpowiedź brzmi: Rozwiązanie problemu w warstwie aplikacji jest niewłaściwe. EXT4 musi upewnić się, że po zamknięciu pliku dane są zapisywane w odpowiednim czasie.Tak jak teraz, EXT4 "optymalizuje" to pisanie, aby móc zebrać więcej żądań zapisu i za jednym zamachem je rozłożyć.

Problem jest oczywisty: Bez względu na to, co zrobisz, nie możesz mieć pewności, że dane zostaną zapisane na dysku. Ręczne wywoływanie fdisk() tylko pogarsza sprawę: zasadniczo utrudniasz optymalizację EXT4, spowalniając cały system.

OTOH, EXT4 zawiera wszystkie informacje niezbędne do odgadnięcia, kiedy konieczne jest zapisanie danych na dysku. W takim przypadku zmieniam nazwę pliku tymczasowego na nazwę istniejącego pliku. W przypadku EXT4 oznacza to, że musi albo odłożyć zmianę nazwy (aby dane pierwotnego pliku pozostały nienaruszone po awarii), albo musi się natychmiast przepłukać. Ponieważ nie może odłożyć zmiany nazwy (następny proces może chcieć zobaczyć nowe dane), przemianowanie domyślnie oznacza opróżnianie, a to musi występować w warstwie FS, a nie w warstwie aplikacji.

EXT4 może utworzyć wirtualną kopię systemu plików, która zawiera zmiany, podczas gdy dysk nie jest modyfikowany (jeszcze). Nie ma to jednak wpływu na ostateczny cel: aplikacja nie wie, jakie optymalizacje ma FS, a FS musi upewnić się, że wykonuje swoją pracę.

Jest to przypadek, w którym bezwzględne optymalizacje poszły za daleko i zrujnowały wyniki. Złota zasada: Optymalizacja nigdy nie może zmienić wyniku końcowego. Jeśli nie możesz tego utrzymać, nie możesz optymalizować.

Tak długo, jak Tso uważa, że ​​ważniejsze jest posiadanie szybkiego FS, a nie takiego, który zachowuje się poprawnie, sugeruję nie aktualizować do EXT4 i zamknąć wszystkie raporty o błędach na temat "działa zgodnie z projektem Tso".

[EDIT] Jeszcze więcej myśli na ten temat. Możesz użyć bazy danych zamiast pliku. Zignorujmy marnotrawstwo zasobów na chwilę. Czy ktokolwiek może zagwarantować, że pliki, których używa baza danych, nie zostaną uszkodzone przez awarię? Prawdopodobnie. Baza danych może zapisywać dane i wywoływać fsync() co minutę. Ale wtedy możesz zrobić to samo:

while True; do sync ; sleep 60 ; done 

Znowu błąd w FS uniemożliwia to działanie w każdym przypadku. W przeciwnym razie ludzie nie byliby tak zaniepokojeni tym błędem.

Można użyć demona konfiguracji w tle, takiego jak rejestr systemu Windows. Demon wypisze wszystkie konfiguracje w jednym dużym pliku. Może wywołać fsync() po zapisaniu wszystkiego. Problem rozwiązany ... dla twoich konfiguracji. Teraz musisz zrobić to samo dla wszystkiego, co pisze twoja aplikacja: dokumenty tekstowe, obrazy, cokolwiek. Mam na myśli prawie każdy proces Unixa tworzy plik. To jest szalona podstawa całej idei Uniksa!

Oczywiście nie jest to realna ścieżka. Tak więc pozostaje odpowiedź: Nie ma rozwiązania po twojej stronie. Nie przejmuj się Tso i innymi programistami FS, dopóki nie naprawią swoich błędów.

+0

Cóż, nadal szukam rozwiązania, aby to zrobić, nie chcę polegać na zachowaniu, które nie zostało zdefiniowane w specyfikacji. – Raynet

+0

Raynet, Tso napisał coś, co nie działa. Nie możesz nic zrobić, dopóki Tso nie rozwiąże problemu. –

+0

Być może, ale nadal wolałbym mieć kod, który działa i nie zależy od tego, w jaki sposób Tso lub ktokolwiek inny przeczytał specyfikację POSIX. – Raynet

Powiązane problemy