uso una saber robaron robada reposicion rectificar que puede propiedad notaria mis mal localizar las hacer estan escrituras escritura costo copia con como casa c linux linux-kernel posix

una - se puede hacer mal uso de copia de escrituras



Escribir programas para hacer frente a errores de E/S que causan escrituras perdidas en Linux (5)

TL; DR: Si el kernel de Linux pierde una escritura de E / S almacenada en un búfer , ¿hay alguna forma de que la aplicación se entere?

Sé que tiene que fsync() el archivo (y su directorio principal) para mayor durabilidad . La pregunta es si el núcleo pierde buffers sucios que están pendientes de escritura debido a un error de E / S, ¿cómo puede la aplicación detectar esto y recuperarlo o cancelarlo?

Piense en aplicaciones de bases de datos, etc., donde el orden de las escrituras y la durabilidad de la escritura pueden ser cruciales.

Perdido escribe? ¿Cómo?

La capa de bloque del kernel de Linux puede, en algunas circunstancias, perder solicitudes de E / S almacenadas en búfer que se han enviado correctamente mediante write() , pwrite() , etc., con un error como:

Buffer I/O error on device dm-0, logical block 12345 lost page write due to I/O error on dm-0

(Ver end_buffer_write_sync(...) y end_buffer_async_write(...) en fs/buffer.c ).

En los núcleos más nuevos, el error contendrá "escritura de página asíncrona perdida" , como:

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

Como la aplicación write() ya habrá regresado sin error, parece que no hay forma de informar un error a la aplicación.

¿Detectarlos?

No estoy tan familiarizado con las fuentes del kernel, pero creo que establece AS_EIO en el búfer que no se pudo escribir si está haciendo una escritura asíncrona:

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

pero no está claro para mí si la aplicación puede averiguarlo o cómo hacerlo cuando más tarde fsync() s el archivo para confirmar que está en el disco.

Parece que wait_on_page_writeback_range(...) en mm/filemap.c podría ser do_sync_mapping_range(...) en fs/sync.c que a su vez es llamado por sys_sync_file_range(...) . Devuelve -EIO si no se pueden escribir uno o más buffers.

Si, como supongo, esto se propaga al resultado de fsync() , entonces si la aplicación entra en pánico y se rescata si obtiene un error de E / S de fsync() y sabe cómo volver a hacer su trabajo cuando se reinicia, eso debería ser suficiente salvaguarda?

Presumiblemente, no hay forma de que la aplicación sepa qué desplazamientos de bytes en un archivo corresponden a las páginas perdidas, por lo que puede reescribirlas si lo sabe, pero si la aplicación repite todo su trabajo pendiente desde la última fsync() del archivo, y eso reescribe cualquier búfer de kernel sucio correspondiente a escrituras perdidas en el archivo, eso debería borrar cualquier indicador de error de E / S en las páginas perdidas y permitir que se complete el siguiente fsync() , ¿verdad?

¿Hay alguna otra circunstancia inofensiva en la que fsync() pueda devolver -EIO donde rescatar y rehacer el trabajo sería demasiado drástico?

¿Por qué?

Por supuesto, tales errores no deberían suceder. En este caso, el error surgió de una desafortunada interacción entre los valores predeterminados del controlador dm-multipath y el código de detección utilizado por la SAN para informar sobre fallas en la asignación del almacenamiento de aprovisionamiento delgado. Pero esta no es la única circunstancia en la que pueden suceder: también he visto informes de LVM de aprovisionamiento delgado, por ejemplo, como lo usan libvirt, Docker y más. Una aplicación crítica como una base de datos debería tratar de hacer frente a tales errores, en lugar de continuar ciegamente como si todo estuviera bien.

Si el kernel piensa que está bien perder escrituras sin morir con el pánico del kernel, las aplicaciones tienen que encontrar una manera de hacer frente.

El impacto práctico es que encontré un caso en el que un problema de múltiples rutas con una SAN causó escrituras perdidas que terminaron causando corrupción en la base de datos porque el DBMS no sabía que sus escrituras habían fallado. No es divertido.


Como la aplicación write () ya habrá regresado sin error, parece que no hay forma de informar un error a la aplicación.

No estoy de acuerdo. write puede regresar sin error si la escritura simplemente se pone en cola, pero el error se informará en la próxima operación que requerirá la escritura real en el disco, eso significa en la próxima fsync , posiblemente en una escritura siguiente si el sistema decide vaciar la caché y al menos en el último archivo cerrado.

Esa es la razón por la cual es esencial que la aplicación pruebe el valor de retorno de close para detectar posibles errores de escritura.

Si realmente necesita poder realizar un procesamiento inteligente de errores, debe suponer que todo lo que se escribió desde la última fsync exitosa puede haber fallado y que, al menos, algo ha fallado.


fsync() devuelve -EIO si el núcleo perdió una escritura

(Nota: la primera parte hace referencia a núcleos más antiguos; actualizada a continuación para reflejar los núcleos modernos)

Parece que la escritura del búfer asíncrono en las end_buffer_async_write(...) establece un indicador -EIO en la página de búfer sucio fallido para el archivo :

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

que luego es detectado por wait_on_page_writeback_range(...) como lo llama do_sync_mapping_range(...) como lo llama sys_sync_file_range(...) como lo llama sys_sync_file_range2(...) para implementar la biblioteca C llamada fsync() .

¡Pero solo una vez!

Este comentario en 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.

sugiere que cuando fsync() devuelve -EIO o (no documentado en la página de -ENOSPC ) -ENOSPC , borrará el estado de error, por lo que un fsync() posterior informará de éxito aunque las páginas nunca se hayan escrito.

Efectivamente wait_on_page_writeback_range(...) borra los bits de error cuando los prueba :

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;

Entonces, si la aplicación espera que pueda volver a intentar fsync() hasta que tenga éxito y confíe en que los datos están en el disco, está terriblemente mal.

Estoy bastante seguro de que esta es la fuente de la corrupción de datos que encontré en el DBMS. Vuelve a fsync() y cree que todo estará bien cuando tenga éxito.

¿Esto está permitido?

Los documentos POSIX / SuS en fsync() realmente no especifican esto de ninguna manera:

Si la función fsync () falla, no se garantiza que las operaciones de E / S pendientes se hayan completado.

La página de manual de Linux para fsync() simplemente no dice nada sobre lo que sucede en caso de falla.

Por lo tanto, parece que el significado de los errores de fsync() es "no sé qué sucedió con sus escritos, podría haber funcionado o no, mejor intente nuevamente para estar seguro".

Núcleos más nuevos

En 4.9 end_buffer_async_write establece -EIO en la página, solo a través de 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);

En el lado de la sincronización, creo que es similar, aunque la estructura ahora es bastante compleja de seguir. filemap_check_errors en mm/filemap.c ahora hace:

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

que tiene el mismo efecto Las comprobaciones de errores parecen pasar por filemap_check_errors que realiza una prueba y borrado:

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

Estoy usando btrfs en mi computadora portátil, pero cuando creo un loopback ext4 para probar en /mnt/tmp y configuro una sonda de rendimiento en él:

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

Encuentro la siguiente pila de llamadas en el 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

Una lectura completa sugiere que sí, los núcleos modernos se comportan igual.

Esto parece significar que si fsync() (o presumiblemente write() o close() ) devuelve -EIO , el archivo está en un estado indefinido entre la última vez que fsync() d o close() d se close() última vez write() diez estados.

Prueba

He implementado un caso de prueba para demostrar este comportamiento .

Trascendencia

Un DBMS puede hacer frente a esto ingresando la recuperación de fallas. ¿Cómo se supone que una aplicación de usuario normal debe hacer frente a esto? La página del fsync() man fsync() no advierte que significa "fsync-if-you-like-it-it" y espero que muchas aplicaciones no se adapten bien a este comportamiento.

Informes de errores

Otras lecturas

lwn.net tocó esto en el artículo "Manejo mejorado de errores en la capa de bloques" .

hilo de la lista de correo postgresql.org .


Use el indicador O_SYNC cuando abra el archivo. Asegura que los datos se escriban en el disco.

Si esto no te satisface, no habrá nada.


Verifique el valor de retorno de close. cerrar puede fallar mientras que las escrituras almacenadas en memoria intermedia parecen tener éxito.


write (2) proporciona menos de lo que espera. La página de manual es muy abierta sobre la semántica de una llamada exitosa de write() :

Un retorno exitoso de write() no garantiza que los datos se hayan confirmado en el disco. De hecho, en algunas implementaciones con errores, ni siquiera garantiza que el espacio se haya reservado con éxito para los datos. La única manera de estar seguro es llamar a fsync (2) después de que haya terminado de escribir todos sus datos.

Podemos concluir que una write() exitosa write() simplemente significa que los datos han llegado a las instalaciones de almacenamiento en búfer del núcleo. Si la persistencia del búfer falla, un acceso posterior al descriptor de archivo devolverá el código de error. Como último recurso que puede estar close() . La página de manual de la llamada al sistema close (2) contiene la siguiente oración:

Es muy posible que los errores en una operación anterior de write (2) se informen primero en el close final ().

Si su aplicación necesita conservar datos, debe usar fsync / fsyncdata de forma regular:

fsync() transfiere ("enjuaga") todos los datos internos modificados de (es decir, páginas de caché de búfer modificadas para) el archivo al que hace referencia el descriptor de archivo fd al dispositivo de disco (u otro dispositivo de almacenamiento permanente) para que toda la información modificada se puede recuperar incluso después de que el sistema se bloqueó o se reinició. Esto incluye escribir o vaciar un caché de disco si está presente. La llamada se bloquea hasta que el dispositivo informa que la transferencia se ha completado.