2009-03-20 1 views
7

Ho seguito la discussione sul "bug" su EXT4 che causa l'azzeramento dei file in caso di arresto anomalo se si utilizza il processo "crea file temp, scrive file temp, rinomina temp per file di destinazione". POSIX dice che a meno che non venga chiamato fsync(), non si può essere sicuri che i dati siano stati scaricati su harddisk.Modo sicuro ed efficiente per modificare più file su sistemi POSIX?

ovviamente facendo:

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 

non è buono anche con fsync() come il computer può bloccarsi durante 2) o fsync() e si finisce con i file parzialmente scritta.

solito Si è pensato che questo è abbastanza sicuro:

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 

purtroppo non è così. Per rendere più sicuro su EXT4 si avrebbe bisogno di fare:

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 

questo sarebbe al sicuro e il crollo si dovrebbe neanche avere il nuovo contenuto di file o vecchi, contenuti mai azzerate o contenuti parziali. Ma se l'applicazione utilizza molti file, fsync() dopo ogni scrittura sarebbe lento.

Quindi la mia domanda è, come modificare più file in modo efficiente su un sistema in cui è necessario fsync() per essere sicuri che le modifiche siano state salvate sul disco? E intendo davvero modificare molti file, come in migliaia di file. Modificare due file e fare fsync() dopo ciascuno non sarebbe male, ma fsync() rallenta quando si modificano più file.

MODIFICA: ha modificato il file temporaneo fsync() chiuso in ordine corretto, ha aggiunto l'enfasi sulla scrittura di molti molti file.

risposta

0

È necessario scambiare 3 & 4 nella tua ultima inserzione - fsync(fd) utilizza il descrittore di file. e non vedo perché sarebbe particolarmente costoso: vuoi comunque i dati scritti su disco da close(). Quindi il costo sarà lo stesso tra ciò che si vuole accadere e ciò che accadrà con fsync().

Se il costo è eccessivo, (e lo avete) fdatasync(2) evitare la sincronizzazione dei meta-dati, quindi dovrebbe essere più leggero.

EDIT: Così ho scritto un po 'di codice di prova estremamente hacky:

#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; 
} 

Sul mio portatile che produce:

0.595782 
6.338329 
6.116894 

che suggerisce facendo la fsync() è ~ 10 volte più costoso. e fdatasync() è leggermente più economico.

Immagino che il problema che vedo sia che ogni l'applicazione penserà che i dati sono abbastanza importanti per fsync(), quindi i vantaggi in termini di prestazioni di unire le scritture su un minuto saranno eliminati.

+0

Con il metodo rename è possibile scrivere 100.000 file di configurazione senza un fsync(), e fare 100.000 fsync() sarebbe lento. – Raynet

+0

"si desidera che i dati scritti sul disco dalla chiusura() comunque" Di cosa stai parlando? Close dovrebbe solo deallocare la descrizione del file in base a POSIX. Non è necessario svuotare i buffer. http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html – ArekBulski

1

La mia risposta sarebbe di mantenere le modifiche sui file temporanei, e dopo averli scritti tutti, fai un fsync() e poi rinominalo tutti.

+1

fsync() è per-fd - forse stai pensando a sync()? –

+0

Penso che sia tempo per un benchmark - perché non ne scrivi uno e vedremo quale sarà l'impatto? –

+0

Ho fatto un rapido benchmark, lo scenario di rinomina è il 10-20% più lento con fsync() per ogni file dopo la scrittura. Suppongo che fsync() sia il comando corretto mentre scarica il file che ho appena scritto, non voglio svuotare qualcos'altro. – Raynet

3

La risposta breve è: Risolvere questo nel livello dell'app è il posto sbagliato. EXT4 deve assicurarsi che dopo aver chiuso il file, i dati siano scritti in modo tempestivo.Come ora, EXT4 "ottimizza" questa scrittura per poter raccogliere più richieste di scrittura e scoppiarle in un colpo solo.

Il problema è ovvio: non importa quello che fai, non puoi essere sicuro che i tuoi dati finiscano sul disco. Chiamare fdisk() manualmente peggiora solo le cose: fondamentalmente interferisci con l'ottimizzazione di EXT4, rallentando l'intero sistema.

OTOH, EXT4 dispone di tutte le informazioni necessarie per formulare un'ipotesi quando è necessario scrivere i dati sul disco. In questo caso, rinominare il file temporaneo nel nome di un file esistente. Per EXT4, ciò significa che deve posticipare la ridenominazione (in modo che i dati del file originale rimangano intatti dopo un arresto anomalo) o che debbano essere scaricati immediatamente. Dal momento che non può posticipare la rinomina (il processo successivo potrebbe voler vedere i nuovi dati), la ridenominazione significa implicitamente lo svuotamento e tale svuotamento deve avvenire sul livello FS, non sul livello dell'app.

EXT4 potrebbe creare una copia virtuale del filesystem che contiene le modifiche mentre il disco non è stato modificato (ancora). Ma questo non influisce sull'obiettivo finale: un'app non può sapere quali ottimizzazioni devono fare le FS e quindi, le FS devono assicurarsi che faccia il loro lavoro.

Questo è un caso in cui le ottimizzazioni spietate sono andate troppo oltre e hanno rovinato i risultati. Regola d'oro: l'ottimizzazione non deve mai cambiare il risultato finale. Se non puoi mantenerlo, non devi ottimizzare.

Finché Tso ritiene che sia più importante avere un veloce FS, piuttosto che uno che si comporta correttamente, suggerisco di non eseguire l'aggiornamento a EXT4 e chiudere tutte le segnalazioni di questo è "ha funzionato come previsto dalla Tso".

[EDIT] Altre riflessioni su questo. Potresti usare un database invece del file. Ignoriamo lo spreco di risorse per un momento. Qualcuno può garantire che i file, che utilizza il database, non vengano danneggiati da un arresto anomalo? Probabilmente. Il database può scrivere i dati e chiamare fsync() ogni minuto circa. Ma poi, si potrebbe fare lo stesso:

while True; do sync ; sleep 60 ; done 

Di nuovo, il bug in FS impedisce che questo funzioni in ogni caso. Altrimenti, la gente non sarebbe così infastidita da questo bug.

È possibile utilizzare un daemon di configurazione in background come il registro di Windows. Il demone dovrebbe scrivere tutte le configurazioni in un unico file. Potrebbe chiamare fsync() dopo aver scritto tutto. Problema risolto ... per le tue configurazioni. Ora devi fare lo stesso per tutto il resto delle tue applicazioni scrivere: documenti di testo, immagini, qualunque cosa. Voglio dire quasi ogni processo Unix crea un file. Questa è la base folle di tutta l'idea Unix!

Chiaramente, questo non è un percorso percorribile. Quindi la risposta rimane: non c'è soluzione dalla tua parte. Continua a disturbare Tso e gli altri sviluppatori di FS fino a quando non risolvono i loro bug.

+0

Beh, sto ancora cercando una soluzione per farlo, non voglio dipendere da un comportamento che non è stato definito sulle specifiche. – Raynet

+0

Raynet, Tso ha scritto qualcosa che non funziona. Non c'è nulla che tu possa fare fino a che Tso non risolve questo problema. –

+0

Forse, ma preferirei comunque avere un codice che funzioni e non dipende da come Tso o chiunque altro abbia letto le specifiche POSIX. – Raynet