Remapeo de sectores defectuosos en discos duros modernos (II)

En mi artículo Remapeo de sectores defectuosos en discos duros modernos (I) explico la teoría. Veamos ahora un caso práctico real.

Cualquiera que me conozca personalmente o que siga este blog sabe perfectamente que estoy obsesionado con los sistemas de backup. Profesionalmente nunca he perdido datos. A nivel personal tampoco me ha ocurrido nada irremediable aunque una vez perdí unas cuantas fotografías por la ventana de tiempo que existió entre copiarlas a mi ordenador y hacer copia de seguridad del mismo. Una semana, en aquel momento. Tuve suerte de aprender la dura realidad de la vida con un coste más bajo que otros.

No es una cuestión de suerte. Es una cuestión de planificación y disciplina. Tema sobre el que escribir un día de estos.

El hecho es que dispongo de varios mecanismos de backup para cada máquina que controlo, incluyendo mi portátil. El más agresivo, que empleo cuando voy a salir de viaje con él, es una copia física sector a sector en un disco duro externo mediante dd. Esto me permite poner a andar mi portátil en 30 segundos simplemente sustituyendo un disco duro por otro. También me permite no perder ni un byte de información, configuraciones, etc., en caso de pérdida o robo del portátil.

Hacer una copia con dd supone parar mi portátil durante cuatro horas. No es ningún problema si lo hago de noche mientras duermo. A nivel técnico la única precaución que hay que tomar es no equivocarse en la dirección de la copia y gestionar correctamente las modificaciones LVM que documento en mi artículo LVM and cloning HDs.

El problema con dd es que si durante la copia encontramos un sector dañado en el disco original nos encontraremos en la desagradable situación de que la copia está a medio hacer, posiblemente corrupta e inutilizable. Precisamente cuando más falta nos hace.

Dependiendo del fichero con errores podríamos recuperarlo gracias a alguno de mis otros backups, menos completos y agresivos. La solución a este problema es alternar el uso de dos discos duros externos pero eso duplica el coste. Factible y recomendable. En mi caso me apoyo en otros mecanismos de backup adicionales.

Una posibilidad simple es revisar el disco duro origen entero antes de iniciar el backup. La probabilidad de desarrollar un defecto en el intervalo entre una operación y otra es bajo.

Básicamente lo que hago es ejecutar un diagnóstico S.M.A.R.T. inicial, luego leer el disco duro completo para detectar errores y finalmente realizar un nuevo S.M.A.R.T. y comparar resultados a la caza de problemas latentes. Si todo va bien esa noche realizo el backup en sí, mientras duermo tranquilamente.

Los diagnósticos S.M.A.R.T. y la lectura del disco duro completo se pueden hacer con el portátil en producción. Afecta un poco el rendimiento pero mi actividad normal no es muy exigente en lo que ancho de banda de disco duro se refiere. El diagnóstico S.M.A.R.T. se muestra con el comando smartctl -A /dev/sda.

En agosto de 2014 hice el proceso normal. La revisión completa del disco duro iba bien hasta que:

root@ubuntu:~# dd if=/dev/sda of=/dev/null bs=65536
dd: reading `/dev/sda': Input/output error
5411176+1 records in
5411176+1 records out
354626879488 bytes (355 GB) copied, 6727.98 s, 52.7 MB/s

Mi disco duro mide 500GB y aquí podemos ver como falla tras 355GB. Lo primero que intento es insistir un poco por si acaso algún reintento tiene éxito:

import time, random

size = 1000*1000*1000*1000
offset_problema = 343969792

a = open("/dev/sdb", "r")

while True:
    rnd = random.randint(0,size-65536)
    print rnd
    a.seek(rnd)
    b=a.read(1024)
    a.seek(offset_problema)
    try :
        a.read(65536)
    except Exception :
        print time.ctime()
        continue
    break

Este pequeño programa intenta leer la zona dañada mientras mueve el cabezal del disco duro aleatoriamente entre reintentos. Si conseguimos leer el sector correctamente el disco duro lo remapeará en un área diferente de la superficie. En mi caso no tuve éxito. Los valores no coinciden con el error concreto que estoy describiendo porque he utilizado el mismo programa en otros discos duros para enfrentarme a otros errores.

El S.M.A.R.T. nos mostraba lo siguiente:

jcea@ubuntu:~$ sudo smartctl -A /dev/sda
[...]
196 Reallocated_Event_Count        33
197 Current_Pending_Sector          2
[...]

Aquí vemos claramente que mi disco duro tiene 33 sectores remapeados y que ahora mismo hay dos sectores problemáticos pendientes de resolución. Si estuviésemos usando el estupendo ZFS no hubiera hecho falta nada de todo esto y, de hecho, ya nos hubiera indicado directamente qué ficheros están afectados. Trabajando en Linux con ext4 tenemos que hacerlo todo a mano :-(.

Primero vemos exactamente qué bloques nos están fallando. Vamos revisando volumen lógico a volumen lógico (ZFS, ¡cuánto te hecho de menos!) hasta que localizamos el problema:

jcea@ubuntu:~$ sudo tune2fs -l /dev/mapper/jcea
...
Block size:               4096
...
jcea@ubuntu:~$ sudo badblocks -b 4096 /dev/mapper/jcea
142272
142330

Podemos determinar los inodes afectados con:

jcea@ubuntu:~$ sudo debugfs -R "icheck 142272 142330" /dev/mapper/jcea

En este caso no me sale nada. Tras pensarlo un poco ello puede ser debido a que esos sectores no están asignados a ningún fichero. Puede tratarse de espacio libre. La estadística está en mi contra ya que la ocupación de mi volumen home es del 97%. Que fallen dos sectores libres es como que te toque la lotería.

Para comprobarlo arranco la máquina en modo single user y lleno el volumen con un fichero con ceros. Hago un sync para asegurarme de que los ceros se han grabado realmente en el disco duro y luego borro el fichero:

$ dd if=/dev/zero of=/mnt/jcea/z-cero bs=65536
$ sync
$ rm /mnt/jcea

Para mi sorpresa esta operación tiene éxito. Contra todo pronóstico los errores estaban en sectores libres del disco.

Sigo revisando el resto de volúmenes lógicos, por si acaso. Usamos dd porque es bastante más rápido que badblocks, podemos trabajar con la partición sin abrir el LUKS y descartamos rápidamente las particiones sin problemas. Las que dan errores se reescanean con badblocks en su versión descifrada.

Encuentro un problema en otro volumen:

root@ubuntu:~$ sudo dd if=/dev/LVM/email of=/dev/null bs=65536
dd: reading `/dev/LVM/email': Input/output error
12078+1 records in
12078+1 records out
791556096 bytes (792 MB) copied, 62.2223 s, 12.7 MB/s

root@ubuntu:~$ sudo tune2fs -l /dev/mapper/email
...
Block size:               4096
...

root@ubuntu:~$ sudo badblocks -b 4096 /dev/mapper/email
192960
192994

jcea@ubuntu:~$ sudo debugfs -R "icheck 192960 192994" /dev/mapper/email
debugfs 1.41.11 (14-Mar-2010)
Block   Inode number
192960  787864
192994  787692

jcea@ubuntu:~$ sudo debugfs -R "ncheck 787864 787692" /dev/mapper/email
debugfs 1.41.11 (14-Mar-2010)
Inode   Pathname
787864  /127.0.0.1/Inbox
787692  /127.0.0.1/programacion.sbd/Python.sbd/Python DEV

Esta vez no tenemos suerte. Hay dos ficheros dañados. Comprobémoslo:

jcea@ubuntu:~$ cat ".thunderbird/vtcvfnl8.default/Mail/127.0.0.1/Inbox" >>/dev/null
jcea@ubuntu:~$ cat ".thunderbird/vtcvfnl8.default/Mail/127.0.0.1/programacion.sbd/Python.sbd/Python DEV" >>/dev/null
cat: .thunderbird/vtcvfnl8.default/Mail/127.0.0.1/programacion.sbd/Python.sbd/Python DEV: Input/output error

Vemos que el fichero Inbox se lee correctamente. El nivel de error estaba en el límite y un reintento ha conseguido leer el sector. Con suerte el disco duro aprovechará la ocasión para refrescar su flujo magnético. No tenemos tanto éxito con la carpeta de correo Python DEV.

Veamos qué dice S.M.A.R.T.:

jcea@ubuntu:~$ sudo smartctl -A /dev/sda
196 Reallocated_Event_Count     33
197 Current_Pending_Sector       1

Aquí vemos que el disco duro ha remapeado 33 sectores en el pasado y que ahora mismo tiene uno pendiente. Intentamos leerlo varias veces por si suena la flauta, sin éxito.

Lo más fácil, si hubiese fallado un fichero normal, sería borrar la versión dañada y recuperarla de un backup previo. Pero los ficheros de correo son especiales en el sentido de que van cambiando poco a poco y recuperar la versión de hace una semana supone perder información.

Otra peculiaridad de los ficheros de correo electrónico es que, salvo en que se realicen compactaciones, los ficheros son append only. Es decir el correo nuevo se añade al final del fichero [1].

Este fichero en particular mide bastante: 496Mbytes. Falla en el offset 470142976. Ese offset ya está presente en el backup. Nos aseguramos de que no han ocurrido compactaciones o cambios de estado de ningún tipo en ese fragmento del fichero, entre el original y el backup:

$ dd "if=Python DEV" bs=470134970 count=1 | sha256sum

Vemos que todo lo anterior coincide exactamente. Ahora cogemos 4096 bytes en el offset 470142976 del backup y lo copiamos en el fichero Python DEV local. A lo bruto.

Ahora S.M.A.R.T. me dice:

jcea@ubuntu:~$ sudo smartctl -A /dev/sda
196 Reallocated_Event_Count     34
197 Current_Pending_Sector       0

El disco duro ha decidido que el sector problemático es irrecuperable y lo remapea en un área diferente.

Volvemos a revisar todo el disco duro. Ya no hay errores. Estamos listos para hacer la copia de seguridad "de verdad".

[1]

Es importante conocer las características del fichero que nos da problemas. Los ficheros estáticos se recuperan directamente de un backup previo. Las bases de datos pueden requerir hacer un replay de los ficheros de logs. Los ficheros de correo electrónico suelen ser append only, salvo cuando se hace una compactación (esporádica) o se cambian los modos de algún mensaje de la carpeta (frecuente, en este caso he tenido suerte).

Una motivación de muchas para migrar mi correo electrónico a IMAP4 es trabajar con ficheros estáticos en lo posible. La recuperación catastrófica debe ser un criterio de selección. Doy algunos detalles en Migrar Thunderbird de "mbox" a "IMAP" (I): Consideraciones.

Sectores dañados fuera de los volúmenes lógicos

Lo descrito funciona si los sectores dañados están dentro de los volúmenes lógicos. Pero si los sectores corruptos están en las áreas de metadatos LVM tendremos un problema serio.

Afortunadamente la estadística está de nuestra parte aunque, como ya hemos visto, a veces lo improbable ocurre.

ZFS

ZFS es otro nivel.

ZFS capea con la corrupción de los metadatos simplemente grabándolos dos o tres veces (según lo críticos que sean) en el zpool, separando las copias entre sí para evitar que una zona mala les afecte simultaneamente. Un par de artículos míos antiguos sobre el tema:

No tenemos que preocuparnos por el espacio libre y los errores en los ficheros indican directamente el fichero afectado. Así de simple:

$ zpool scrub PRUEBA
$ zpool status -v PRUEBA
  pool: PRUEBA
 state: ONLINE
status: One or more devices has experienced an error resulting in data
        corruption.  Applications may be affected.
action: Restore the file in question if possible.  Otherwise restore the
        entire pool from backup.
   see: http://www.sun.com/msg/ZFS-8000-8A
 scan: scrub repaired 0 in 0h0m with 2 errors on Mon Jan 12 17:06:36 2015
config:

        NAME        STATE     READ WRITE CKSUM
        PRUEBA      ONLINE       0     0     3
          /tmp/z/z  ONLINE       0     0     6

errors: Permanent errors have been detected in the following files:

        /PRUEBA/corrompemos_el_fichero_a_proposito

Limpio y sencillo.

Naturalmente si pudiese usar ZFS en mi portátil no estaría haciendo copia física de disco a disco con un dd. Pero esa es otra historia.