2009-03-20 7 views
7

He estado siguiendo la discusión sobre el "error" en EXT4 que hace que los archivos se pongan a cero en crash si se usa el proceso "crear archivo temporal, escribir archivo temporal, cambiar el nombre de la temperatura al archivo destino". POSIX dice que a menos que se llame a fsync(), no puede estar seguro de que los datos se hayan descargado al disco duro.¿Manera segura y eficiente de modificar archivos múltiples en sistemas POSIX?

Obviamente haciendo:

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 

no es bueno incluso con fsync() que el equipo puede bloquearse durante 2) o fsync() y se termina con el archivo escrito parcialmente.

Por lo general, se ha pensado que esto es bastante seguro:

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 

Por desgracia no lo es. Para que sea más seguro en EXT4 que tendría que hacer:

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 

esto sería seguro y el accidente se debe o bien tienen los nuevos contenidos de los archivos o contenidos antiguos, no reducidos a cero o contenido parcial. Pero si la aplicación usa muchos archivos, fsync() después de cada escritura sería lento.

Así que mi pregunta es, ¿cómo modificar múltiples archivos de manera eficiente en un sistema donde fsync() es necesario para asegurarse de que los cambios se hayan guardado en el disco? Y realmente me refiero a modificar muchos archivos, como en miles de archivos. Modificar dos archivos y hacer fsync() después de cada uno no sería tan malo, pero fsync() ralentiza las cosas al modificar varios archivos.

EDITAR: cambió el archivo fsync() close temp al orden corrent, se le dio más énfasis a la escritura de muchos y muchos archivos.

Respuesta

0

Necesita intercambiar 3 & 4 en su última lista - fsync(fd) utiliza el descriptor de archivo. y no veo por qué eso sería particularmente costoso: desea que los datos se escriban en el disco al cierre() de todos modos. Entonces, el costo será el mismo entre lo que quiere que suceda y lo que sucederá con fsync().

Si el costo es demasiado, (y lo tiene) fdatasync(2) evite sincronizar los metadatos, por lo que debería ser un costo menor.

EDIT: así que escribí algo de código de prueba muy 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; 
} 

En mi portátil que produce:

0.595782 
6.338329 
6.116894 

Qué sugiere hacer el fsync() es ~ 10 veces más caro. y fdatasync() es un poco más barato.

Creo que el problema que veo es que cada aplicación va a pensar que sus datos son lo suficientemente importantes para fsync(), por lo que se eliminarán las ventajas de rendimiento de la fusión de escrituras durante un minuto.

+0

Con el método de renombrado uno podría escribir 100.000 archivos de configuración sin un fsync(), y hacer 100.000 fsync() sería lento. – Raynet

+0

"desea que los datos escritos en el disco al cierre() de todos modos" ¿De qué estás hablando? Se supone que Close solo desasigna la descripción del archivo de acuerdo con POSIX. No es necesario vaciar los almacenamientos intermedios. http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html – ArekBulski

1

Mi propia respuesta sería mantener las modificaciones en los archivos temporales, y después de terminar de escribirlos todos, haga uno fsync() y luego cambie el nombre en todos ellos.

+1

fsync() es por-fd - tal vez usted está pensando en la sincronización()? –

+0

Creo que es hora de un punto de referencia: ¿por qué no escribes uno y veremos cuál es el impacto? –

+0

Hice un punto de referencia rápido, el escenario de cambio de nombre es 10-20% más lento con fsync() para cada archivo después de escribir. Asumiría que fsync() es el comando correcto, ya que vacía el archivo que acabo de escribir, no quiero eliminar nada más. – Raynet

3

La respuesta corta es: Resolver esto en la capa de la aplicación es el lugar equivocado. EXT4 debe asegurarse de que después de cerrar el archivo, los datos se escriben de manera oportuna.Tal como está ahora, EXT4 "optimiza" esta escritura para poder recoger más solicitudes de escritura y reventarlas de una vez.

El problema es obvio: no importa lo que haga, no puede estar seguro de que sus datos terminen en el disco. Llamar a fdisk() manualmente solo empeora las cosas: básicamente se interpone en el camino de la optimización de EXT4, ralentizando todo el sistema.

OTOH, EXT4 tiene toda la información necesaria para adivinar cuándo es necesario escribir datos en el disco. En este caso, cambio el nombre del archivo temporal por el nombre de un archivo existente. Para EXT4, esto significa que debe posponer el cambio de nombre (para que los datos del archivo original permanezcan intactos después de un bloqueo) o que se detenga a la vez. Como no puede posponer el cambio de nombre (es posible que el próximo proceso quiera ver los datos nuevos), el cambio de nombre implica implícitamente enjuagar y esa descarga debe ocurrir en la capa de FS, no en la capa de la aplicación.

EXT4 podría crear una copia virtual del sistema de archivos que contiene los cambios mientras el disco no está modificado (aún). Pero esto no afecta el objetivo final: una aplicación no puede saber qué optimizaciones debe realizar el FS y, por lo tanto, FS debe asegurarse de que hace su trabajo.

Este es un caso donde las optimizaciones despiadadas han ido demasiado lejos y han arruinado los resultados. Regla de oro: la optimización nunca debe cambiar el resultado final. Si no puede mantener esto, no debe optimizar.

Mientras Tso considere que es más importante tener un FS rápido en lugar de uno que se comporta correctamente, sugiero no actualizar a EXT4 y cerrar todos los informes de fallos sobre esto "funciona como lo diseñó Tso".

[EDITAR] Algunas reflexiones más sobre esto. Puede usar una base de datos en lugar del archivo. Vamos a ignorar el desperdicio de recursos por un momento. ¿Alguien puede garantizar que los archivos que utiliza la base de datos no se corrompan por un bloqueo? Probablemente. La base de datos puede escribir los datos y llamar a fsync() cada minuto más o menos. Pero luego, podría hacer lo mismo:

while True; do sync ; sleep 60 ; done 

Una vez más, el error en el FS impide que esto funcione en todos los casos. De lo contrario, la gente no estaría tan molesta por este error.

Puede usar un daemon de configuración de fondo como el registro de Windows. El daemon escribiría todas las configuraciones en un archivo grande. Podría llamar a fsync() después de escribir todo. Problema resuelto ... para tus configuraciones. Ahora necesita hacer lo mismo para todo lo demás que escriben sus aplicaciones: documentos de texto, imágenes, lo que sea. Quiero decir que casi cualquier proceso de Unix crea un archivo. ¡Esta es la maldita base de toda la idea de Unix!

Claramente, este no es un camino viable. Entonces la respuesta sigue siendo: no hay solución de tu parte. Sigue molestando a Tso y a los otros desarrolladores de FS hasta que arreglen sus errores.

+0

Bueno, todavía estoy buscando una solución para hacer esto, no quiero depender del comportamiento que no se haya definido en la especificación. – Raynet

+0

Raynet, Tso ha escrito algo que no funciona. No hay nada que puedas hacer hasta que Tso resuelva este problema. –

+0

Quizás, pero aún así preferiría tener un código que funcione y no dependa de cómo Tso o cualquier otra persona haya leído la especificación POSIX. – Raynet

Cuestiones relacionadas