Cálculo de pérdida de datos en LizardFS

Advertencia

La instalación LizardFS que describo de pasada en este documento es un compendio de malas prácticas de libro. Por favor, no utilices nada que se parezca a esta topología en tu instalación LizardFS.

Como consecuencia evidente, no interpretes este documento como que LizardFS es un mal sistema de ficheros proclive a la pérdida de datos. LizardFS tiene muchos problemas técnicos y culturales, pero este artículo no trata sobre ellos.

Personalmente, he intentado mejorar la configuración de esa instalación LizardFS, pero la resistencia de su propietario ha sido inamovible... incluso tras esta pérdida de datos.

En Detección y diagnóstico de pérdida de datos en LizardFS describo un caso catastrófico de pérdida de datos en una instalación LizardFS debido a que se configuró para que los diferentes discos duros presentes en una máquina se mostrasen a LizardFS como nodos de almacenamiento separados (y, por tanto, con fallos no correlados). Esto es conveniente para ver muchos discos duros y poder emplear una corrección de errores 8+2 tal y como describo en el artículo anterior. Lamentablemente, si se pierde un ordenador, perderemos varios discos duros a la vez. Dado que una configuración 8+2 solo corrige dos errores, es interesante calcular la probabilidad de fallo si se cae un servidor. Es decir, calcular cuántos ficheros se pierden si se cae un ordenador con cinco discos duros en una red con treinta discos duros.

Advertencia

Esto es una mala práctica. Todos los discos duros de un servidor deberían verse como un único nodo de almacenamiento. La previsión era hacerlo así en el futuro, pero, mientras la red de almacenamiento crecía, se quería utilizar redundancia 8+2. La propuesta inicial era crecer rápido en discos duros y ordenadores, y que, mientras tanto, el modelo de fallo ante el que protegerse era la pérdida de discos duros aislados. En caso de la caída momentanea de un servidor, se aceptaba que los datos no estuviesen disponibles durante unos minutos/horas.

Lamentablemente, la red de almacenamiento nunca creció, nunca se reconfiguró para ajustarse a esa nueva realidad y cuando cayó una máquina, el operador borró todos sus discos duros antes de poder recuperar los datos de la red de almacenamiento.

Un desastre, vaya.

Posiblemente se pueda dar una respuesta analítica, pero prefiero hacer el cálculo mediante simulación para tener flexibilidad a la hora de modelar la red real.

El código Python es el siguiente:

import random

a = [i for i in range(33)]
n = 0
for i in range(1, 1000000):
   random.shuffle(a)
   if sorted(a[:10])[2] < 5:
       n += 1
   if not (i % 1000):
       print(f'{100 * n / i :0.2f}%')

Este pequeño código modela el caso de una red con 33 discos duros de los que se caen cinco discos duros y donde podemos corregir el fallo de dos discos duros.

El algoritmo es simple:

  • Por sencillez y sin pérdida de generalidad, suponemos que los discos duros que fallan son los cinco primeros (del 0 al 4).
  • Desordenamos los 33 discos duros y tomamos los diez primeros de la lista. Con esto simulamos la grabación de 8+2 y el reparto por discos duros suponiendo que sean equiprobables (todos del mismo tamaño y mismo nivel de ocupación).
  • Una vez que hemos elegido los diez discos duros donde grabamos los datos, ordenamos esos discos duros por secuencia numérica.
  • Si el tercer disco duro es uno de los primeros cinco discos duros que consideramos que han fallado, dado que hemos ordenado los discos duros seleccionados para esa grabación, sabemos que se han producido al menos tres errores. Nuestro esquema de redundancia solo puede corregir dos errores o menos. Estamos en pérdida de datos.
  • Simulamos el proceso muchas veces y proporcionamos una estadística.

En mi caso, la simulación me dice que la probabilidad de que un fragmento [1] LizardFS falle con la situación descrita es de algo menos del 15%, lo que es un auténtico desastre, sobre todo considerando que si un fichero es largo y está formado por varios fragmentos [1], las probabilidades de que el fichero sobreviva caen rápidamente.

En nuestro caso la inmensa mayoría de los ficheros son lo bastante pequeños como para consistir en un único fragmento [1], pero nuestra pérdida de datos real fue del 0.84%. ¿A qué se debe esta discrepancia?

El programa Python anterior supone que la grabación en los discos duros es equiprobable, pero no era nuestro caso, fundamentalmente porque los discos duros tenían tamaños muy dispares y además habíamos configurado LizardFS para que siempre se usase al menos uno de los discos duros de alta capacidad. Esta última característica fue un intento desesperado para mejorar la supervivencia de la red de almacenamiento ante caídas de ciertas máquinas consideradas débiles, y el resultado confirmó las previsiones.

Para modelar esto por software es necesario reflejar los pesos de cada disco duro. Una forma sencilla de hacerlo sería poner cada disco duro varias veces en la selección, en función de su peso. Desordenar la lista y luego eliminar los duplicados. El resto del código es el mismo. Para eliminar los duplicados pero mantener el orden, me aprovecho de que a partir de Python 3.7 se garantiza que los diccionarios mantienen el orden de inserción de claves [2].

Por ejemplo, veamos el siguiente código:

import random

a = [i for i in range(5)]
a += [i for i in range(5, 20)]
a += [i for i in range(5, 20)]
a += [i for i in range(20, 25)]
a += [i for i in range(20, 25)]
a += [i for i in range(20, 25)]
a += [i for i in range(25, 31)]
a += [i for i in range(25, 31)]
a += [i for i in range(25, 31)]
a += [i for i in range(25, 31)]
a += [31] * 1000
a += [32] * 1000

n = 0
for i in range(1, 1000000):
   random.shuffle(a)
   b = list(dict.fromkeys(a))
   if sorted(b[:10])[2] < 5:
     n += 1
   if not (i % 1000):
     print(f'{100 * n / i :0.2f}%')

Aquí estamos simulando la siguiente configuración, más comparable a nuestro caso real:

  1. Cinco discos duros de tamaño T. Estos discos duros son los que van a fallar.
  2. Quince discos duros de tamaño 2*T.
  3. Cinco discos duros de tamaño 3*T.
  4. Seis discos duros de tamaño 4*T.
  5. Dos discos duros de alta capacidad que siempre entrarán en el reparto, por eso les damos un peso tan alto.

Simulando esa configuración me sale una probabilidad de pérdida de un fragmento [1] LizardFS del 0.93%, bastante parecida a la pérdida real del 0.84%. No coinciden porque el almacenamiento simulado no es exactamente igual al real, se trata de un ejemplo.

Naturalmente, esta probabilidad de error tan baja comparada con el primer caso es debida a que consideramos que se cae una máquina determinada. La pérdida será salvaje si se cae una máquina diferente, más crítica. Es mejor simular los diferentes escenarios en vez de confiar en el sentido común, que en este tipo de cosas no es buen consejero.

Por ejemplo, si los dos discos duros grandes estuviesen en la misma máquina y esta cayese... no tendríamos pérdida de datos porque nuestro esquema 8+2 nos permite sobrevivir a la pérdida de dos discos duros, aunque sean los más grandes e importantes de la red.

Mejor aplicar buenas prácticas, por supuesto. Pero ante configuraciones bizarras, mejor simular, presentar los resultados al cliente... y quedar en las manos de su buen juicio.

Nota

Además de la disponibilidad, también se pueden simular otras métricas, como rendimiento o capacidad efectiva del almacenamiento en función de la redundancia y reglas topológicas complejas.

[1] (1, 2, 3, 4) Doy una definición de fragmento en LizardFS en Detección y diagnóstico de pérdida de datos en LizardFS.
[2] Ver [Python-Dev] Guarantee ordered dict literals in v3.7?