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 tener 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 hayan sido enviadas con éxito 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(...)
enfs/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
Dado que 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 núcleo, pero creo que se 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 me queda claro si la aplicación puede enterarse de esto o cómo puede hacerlo cuando más tarde sea fsync()
el archivo para confirmar que está en el disco.
Parece que wait_on_page_writeback_range(...)
enmm/filemap.c
poder por do_sync_mapping_range(...)
enfs/sync.c
que se llama turno sys_sync_file_range(...)
. Regresa -EIO
si no se pueden escribir uno o más buffers.
Si, como supongo, esto se propaga a fsync()
resultado, entonces si la aplicación entra en pánico y se rescata si recibe un error de E / S fsync()
y sabe cómo volver a hacer su trabajo cuando se reinicia, ¿debería ser suficiente protección?
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 el último éxito fsync()
del archivo, y eso reescribe cualquier almacenamiento intermedio de kernel sucio correspondiente a escrituras perdidas en el archivo, que debería borrar cualquier indicador de error de E / S en las páginas perdidas y permitir fsync()
que se complete la siguiente , ¿verdad?
¿Existen entonces otras circunstancias inofensivas donde fsync()
pueda regresar -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 dm-multipath
valores predeterminados del controlador y el código de detección utilizado por la SAN para informar la falla en la asignación del almacenamiento de aprovisionamiento delgado. Pero esta no es la única circunstancia donde 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.
fuente
Respuestas:
fsync()
regresa-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 de búfer asíncrono en
end_buffer_async_write(...)
fallas establece un-EIO
indicador en la página de búfer sucio fallido para el archivo :que es detectada a continuación, por
wait_on_page_writeback_range(...)
como llamado pordo_sync_mapping_range(...)
como llamado porsys_sync_file_range(...)
como llamado porsys_sync_file_range2(...)
para implementar la función de biblioteca Cfsync()
.¡Pero solo una vez!
Este comentario en
sys_sync_file_range
sugiere que cuando se
fsync()
devuelve-EIO
o (sin documentar en la página de manual)-ENOSPC
, se borrará el estado de error, por lo que unfsync()
informe posterior informará el éxito a pesar de que las páginas nunca se escribieron.Efectivamente
wait_on_page_writeback_range(...)
borra los bits de error cuando los prueba :Entonces, si la aplicación espera que pueda volver a intentarlo
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 intentarlo
fsync()
y piensa que todo estará bien cuando tenga éxito.¿Esto está permitido?
Los documentos POSIX / SuS en
fsync()
realmente no especifican esto de ninguna manera:La página de manual de Linux
fsync()
simplemente no dice nada sobre lo que sucede en caso de falla.Por lo tanto, parece que el significado de los
fsync()
errores 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
conjuntos-EIO
en la página, solo a través demapping_set_error
.En el lado de la sincronización, creo que es similar, aunque la estructura ahora es bastante compleja de seguir.
filemap_check_errors
enmm/filemap.c
ahora hace:que tiene el mismo efecto Parece que todas las comprobaciones de errores pasan, lo
filemap_check_errors
que hace una prueba y borra:Lo estoy usando
btrfs
en mi computadora portátil, pero cuando creo unext4
loopback para probar/mnt/tmp
y configurar una sonda de rendimiento en él:Encuentro la siguiente pila de llamadas en
perf report -T
:Una lectura completa sugiere que sí, los núcleos modernos se comportan igual.
Esto parece significar que si
fsync()
(o, presumiblemente,write()
oclose()
retornos)-EIO
, el archivo está en un estado no definido entre el último éxito cuandofsync()
D oclose()
D y su más recientewrite()
estado de diez.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
fsync()
página del manual 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 de capa de bloque" .
hilo de la lista de correo postgresql.org .
fuente
errno
es completamente una construcción de la biblioteca C del espacio de usuario. Es común ignorar las diferencias de valor de retorno entre las llamadas al sistema y la biblioteca C de esta manera (como lo hace Craig Ringer, más arriba), ya que el valor de retorno de error identifica confiablemente a cuál (función de la biblioteca syscall o C) se hace referencia a: "-1
conerrno==EIO
"se refiere a una función de biblioteca C, mientras que"-EIO
"se refiere a una llamada al sistema. Finalmente, las páginas de manual de Linux en línea son la referencia más actualizada para las páginas de manual de Linux.fsync()
/fdatasync()
cuando el tamaño de la transacción es un archivo completo; al usarmmap()
/msync()
cuando el tamaño de la transacción es un registro alineado con la página; y al usar I de bajo nivel / O,fdatasync()
y múltiples descriptores de archivos concurrentes (un descriptor y un hilo por transacción) al mismo archivo de otra manera " . Los bloqueos de descripción de archivo abierto específicos de Linux (fcntl()
,F_OFD_
) son muy útiles con el último.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, es decir, en la próximafsync
, posiblemente en una escritura siguiente si el sistema decide vaciar la caché y en 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 el último éxito
fsync
puede haber fallado y que, al menos, algo ha fallado.fuente
fsync()
oclose()
del archivo si se pone una-EIO
dewrite()
,fsync()
oclose()
. Bueno, eso es divertido.write
(2) proporciona menos de lo que espera. La página de manual es muy abierta sobre la semántica de unawrite()
llamada exitosa :Podemos concluir que un éxito
write()
significa simplemente 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 serclose()
. La página de manual de laclose
llamada al sistema (2) contiene la siguiente oración:Si su aplicación necesita persistir en la escritura de datos, debe usar
fsync
/fsyncdata
regularmente:fuente
fsync()
es obligatorio. Pero en el caso específico en el que el núcleo pierde las páginas debido a un error de E / S sefsync()
fallará? ¿En qué circunstancias puede tener éxito después?fsync()
retornos-EIO
en problemas de E / S (¿para qué sería bueno de lo contrario?). Por lo tanto, la base de datos sabe que algo de una escritura anterior falló y podría entrar en modo de recuperación. ¿No es esto lo que quieres? ¿Cuál es la motivación de tu última pregunta? ¿Desea saber qué escritura falló o recuperar el descriptor de archivo para su uso posterior?fsync()
pueda volver a-EIO
donde sea seguro volver a intentarlo, y si es posible notar la diferencia.-EIO
. Si cada descriptor de archivo solo es utilizado por un hilo a la vez, este hilo podría volver al últimofsync()
y rehacer laswrite()
llamadas. Pero aún así, si esoswrite()
solo escriben parte de un sector, la parte no modificada aún puede estar corrupta.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.
fuente
O_SYNC
Es una pesadilla para el rendimiento. Significa que la aplicación no puede hacer nada más mientras está ocurriendo la E / S del disco a menos que genere hilos de E / S. También podría decir que la interfaz de E / S almacenada no es segura y todos deberían usar AIO. ¿Seguramente las escrituras perdidas en silencio no pueden ser aceptables en E / S almacenadas?O_DATASYNC
es solo un poco mejor en ese sentido)Verifique el valor de retorno de close. cerrar puede fallar mientras que las escrituras almacenadas en el búfer parecen tener éxito.
fuente
open()
ing yclose()
ing del archivo cada pocos segundos. es por eso que tenemosfsync()
...