Come mis datos: por qué todos malinterpretamos la E/S de archivos
En besttechvideos publicaron un video de la última LinuxConf en Australia donde Stewart Smith, un ingeniero de software de MySQL AB, comenta acerca de cómo debemos proceder para mantener la integridad de los datos en disco al desarrollar una aplicación.
El video está disponible aquí (en flash) o aquí (en ogg).
Como la disertación es en inglés, les dejo una síntesis de lo que Smith comenta:
Una breve introducción
Al comienzo de los tiempos, toda la entrada y salida de archivos era sincrónica; es decir, una llamada a grabar implicaba que los datos llegaban al dispositivo físico en el momento, y el procesamiento se detenía hasta finalizar la operación. Por razones obvias la performance de este método es muy baja, de ahí que surgió la llamada entrada/salida asíncrona, que permite continuar la ejecución del código mientras los datos son leídos o escritos a disco.
Lamentablemente, no existe un mundo sin fallas: las computadoras se cuelgan, se corta la luz, se termina la batería, alguien se tropezó con el cable, etc…
Bien, ¿cuando ocurre un corte de energia, qué es lo que se pierde?
- Lo que está en buffers de la aplicación
- Lo que está en buffers de las librerías
- Lo que está en buffers del S.O. (page/buffer cache)
¿Dónde radican los errores que llevan a la pérdida de datos?
- En el código de la aplicación
- En el código de la librería utilizada
- En el código del kernel del sistema operativo
¿Cuál es el flujo de los datos desde una aplicación hacia el disco, al grabar?
En un resumen burdo: con las funciones fwrite, fprint y otras, la aplicación pasa los datos a la librería (p. ej., glibc); la librería a su vez dirige los datos con write y similares al sistema operativo; y este último hacia el disco mediante page out y flushing periódico realizado entre 5 y 30 segundos (o más si se trata de una laptop). En cualquiera de estos momentos nuestros datos son vulnerables ante una falla de energía.
¿Es write atómico? ¿Qué pasa si se corta la energía en la mitad de una operación write?
Pues no, y si se corta la energía en la mitad de la operación lo más probable es que la información en el archivo sea un conglomerado entre los datos de la versión anterior y los de la que se estaba guardando.
¿Cómo puede evitarse esto?
El truco más viejo consiste en escribir sobre un archivo temporal y luego renombrar al finalizar la operación de escritura. De esa forma, los datos originales estarían seguros aún si se corta la energía durante la etapa de escritura del archivo temporal. Y nunca tenemos que olvidarnos de las excepciones (permiso denegado, espacio en disco agotado).
¿Hummm… por qué dice estarían en la afirmación anterior?
Aquí tenemos una interesante cuestión. Stewart indica en que close y rename no implican sync. Redondeando, nadie asegura que la operación de cambio de nombre rename se realice después del write y consecuente close, aún estando en el orden correcto. Las operaciones sobre los datos de un archivo (tales como la grabación) se almacenan en buffers diferentes a las operaciones sobre los metadatos (tales como el renombrado), por lo que puede ocurrir (y generalmente ocurre) que estas últimas se realicen primero. En el triste caso de un fallo de energía, habremos perdido la información original por haberse efectuado el cambio de nombre primero, y además tendríamos un archivo corrupto, dado que los datos habrían quedado a medio grabar.
Por suerte para nosotros, el modo ordered del sistema de archivos ext3, nos asegura que el orden de prioridad es: primero write, luego close y luego rename. En otras palabras: primero grabar los datos al disco, luego actualizar el inodo y finalmente actualizar la entrada en el directorio.
Bien, ahora sabemos que haciendo fsync mas la técnica del archivo temporal, sobre un sistema de archivos ext3 y efectuando los controles de errores correspondientes, si ocurre un fallo durante una operación de escritura, los datos originales se mantienen seguros…
¿Qué tal en otras plataformas?
El Sr. Smith nos da una advertencia interesante cuando observa que el estándar POSIX define como válida una implementación de fsync nula. En otras palabras, no podemos quedarnos tranquilos al utilizar fsync en nuestras aplicaciones, pues puede que el sistema operativo subyacente implemente dicha función sólo haciendo nada. O sea, que una fsync implementada como sigue es válida en el estándar POSIX:
int fsync (int fd) { return 0; }
¿Y cómo se dio cuenta?
Cuando se cansó de tener páginas corruptas con MySQL, pues el fsync de MacOS X no descarga a disco el contenido del buffer de escritura, sino que se requiere un fcntl adicional. Así que hay que hacer algunas definiciones diferentes dependiendo del sistema operativo para el cual se compile la aplicación…
En el video, Stewart también discurre con bastante detalle sobre el tratamiento de archivos de gran tamaño y las técnicas utilizadas para la recuperación de datos (undo/redo logs) y prealocación de espacio.
En fin, un poco de charla para espabilar las neuronas y refrescarse con en inglés. Espero que lo disfruten…
You must be logged in to post a comment.