Esta semana hablamos de tamaño, algo que debería preocuparle a cualquier administrador de sistemas a cargo del sistema de backups de cualquier proyecto, y en particular de los backups de una base de datos.

A menudo recibo preguntas sobre cuál es la mejor herramienta de compresión a aplicar en un sistema de copias de seguridad: ¿gzip? ¿bzip2? ¿algún otro?

El entorno de pruebas

Para poder probar diferentes formatos y herramientas, creé un archivo .csv (comma-separated values, valores separados por comas) de tamaño 3.700.635.579 bytes transformando un dump reciente de todos los nodos de la porción europea de España en OpenStreetMap. Tenía un total de 46.741.126 de filas y tenía la siguiente pinta:

171773  38.6048402      -0.0489871      4       2012-08-25 00:37:46     12850816        472193  rubensd
171774  38.6061981      -0.0496867      2       2008-01-19 10:23:21     666916  9250    j3m
171775  38.6067166      -0.0498342      2       2008-01-19 10:23:21     666916  9250    j3m
171776  38.6028122      -0.0497136      5       2012-08-25 00:26:40     12850816        472193  rubensd
171933  40.4200658      -3.7016652      6       2011-11-29 05:37:42     9984625 33103   sergionaranja 

De hecho, el fichero original es en realidad un tsv (tab-separated values, valores separados por tabuladores), y no un csv, pero sólo porque soy demasiado vago como para añadir el código extra FIELDS SEPARATED BY ',' cada vez que lo importo y exporto. Puede descargar este archivo en formato 7z, o crear el suyo propio desde los extractos de OpenStreetMap de Geofabrik.

Todos los tests se hicieron en un servidor casi inactivo con un Intel Quad Core i7-3770@3.40GHz con hyper threading, exponiendo 8 cpus al kernel. 32 GB de ram. 2 discos duros clásicos (no SSD) de 3 TB en RAID 1. Todo corriendo bajo CentOS 6.5 x86_64. El sistema de archivos era ext4 con las opciones por defecto del sistema operativo.

Tamaño en tabla

Para una importación a MySQL, propuse la siguiente estructura de tabla:

CREATE TABLE `nodes` (
  `id` bigint PRIMARY KEY,
  `lat` decimal(9,7),
  `lon` decimal(10,7),
  `version` int,
  `timestamp` timestamp,
  `changeset` bigint,
  `uid` bigint,
  `user` varchar(255)
);

Y estos fueron los tamaños en la base de datos (una vez que nos aseguramos de que no había operaciones de escritura pendientes):

  • Archivo de datos MySQL en MyISAM (.MYD): 2,755,256,596 bytes.(*)
  • Espacio de tablas MySQL de InnoDB (.ibd): 3,686,793,216 bytes.
  • Espacio de tablas MySQL de InnoDB con formato de filas compressed (.ibd): 1,736,441,856 bytes.

¿Por qué ocupa más espacio en texto plano que en la base de datos? Aunque las bases de datos están optimizadas para acceso rápido, y no en ocupación de disco, estamos usando un conjunto de tipos de datos muy compacto (enteros y timestamps en vez de cadenas de texto), ahorrando espacio en el proceso. ¡Esta es la razón por la que un diseño adecuado de base de datos es crítico para el rendimiento!

Podemos ver que una de las pocas razones por las que la gente sigue utilizando MyISAM es porque es un formato muy simple y compacto. (*)Sin embargo, para ser justos, no estamos teniendo en cuenta los 674.940.928 bytes extra de la clave primaria (.MYI), haciendo que la diferencia no sea tan grande. Por otro lado, no estamos teniendo en cuenta que el tamaño de los índices en InnoDB puede crecer de manera bastante significativa cuando estamos usando múltiples claves secundarias (debido al almacenamiento de la clave privada, si ésta es lo suficientemente grande) y las múltiples otras estructuras (tablespace 0, transaction logs) que son necesarios para que InnoDB funciones adecuadamente, compartido con otras tablas. En general, es imposible realizar una comparación justa entre MyISAM e InnoDB porque estamos comparando peras con manzanas.

Lo que está claro es que la compresión (en este caso estamos usando el algoritmo por defecto de InnoDB con el nivel por defecto de compresión-6) está ayudando a reducir el espacio en disco, introduciendo potenciales mejoras en escenarios específicos: más tablas que pueden caber en SSDs, o menos IOPS en una base de datos cuyo cuello de botella es el disco. Por otro lado, la carga inicial aumentó muy significativamente. No quiero mostrar las mediciones de tiempo de las importaciones en las distintas tablas porque no es trivial registrar el tiempo real a disco debido a todo el buffering que ocurre a nivel de base de datos, y simplemente proporcionar el tiempo de ejecución de las sentencias SQL sería injusto. Hablo más sobre tiempos de importación en este post.

Resultados globales

Los tamaños en tabla sólo se mostraron como referencias, nuestro principal objetivo es testear las herramientas disponibles para comprimir el archivo original nodes.csv file. Me limité a mí mismo a algunas de las más populares, y en la siguiente tabla se pueden ver los resultados finales (el análisis, explicación y discusión sigue a continuación):

Tamaño original  3700635579 bytes					
							
método         mediana del tiempo de  tamaño comprimido  ratio de compresión             uso de cpu de la
               compresión (seconds)   (bytes)            (tamaño_nuevo/tamaño_original)  compresión (unix %CPU)
dd               10.146               3700635579         100.00%                          97 -  68
gzip            113.796                614119104          16.59%                         100 -  89
gzip -1          43.219                729259339          19.71%                         100 -  67
gzip -9         266.991                585777285          15.83%                          97 -  77
bzip2           294.568                525839069          14.21%                          95 -  89
bzip2 -1        281.337                508276130          13.73%                         100 -  80
bzip2 -9        295.510                585777285          15.83%                         100 -  95
pigz             27.325                614093952          16.59%                         770 - 547
pigz -1          25.982                728206796          19.68%                         231 - 159
pigz -9          51.821                585756379          15.83%                         773 - 659
pbzip2           74.874                526311578          14.22%                         794 - 663
pbzip2 -1        60.487                508782016          13.75%                         800 - 495
pbzip2 -9*       76.597                526311578          14.22%                         773 - 394
lzip           2138.230                357788948           9.67%                         100 -  70
7za             833.611                380728938          10.29%                         172 - 145
7za "ultra"    1286.044                354107250           9.57%                         178 - 164
plzip           216.942                376484712          10.17%                         768 - 373
plzip -1         50.151                581552529          15.71%                         781 - 738
plzip "ultra"   426.486                354095395           9.57%                         785 - 159
lzop             15.505               1003908508          27.13%                          95 -  50
lzop -1          13.080               1000938935          27.05%                          90 -  63
lzop -9         487.850                782234410          21.14%                          99 -  89
lz4               8.537               1043868501          28.21%                          93 -  65
lz4 -1*           8.574               1043868501          28.21%                          94 -  65
lz4 -9           96.171                816582329          22.07%                          99 -  66

Como se puede ver, evalué varias herramientas en sus modos por defecto, así como un modo de “alta compresión” y un modo “rápido”. Para cada uno de ellos intenté evaluar 3 parámetros, importantes para la creación de archivos comprimidos: el tiempo de ejecución, el tamaño final y los recursos usados. Tened en cuenta que sólo evalué herramientas de compresión, y no de “archivado” (como tar o zip). Estas últimas herramientas pueden utilizar diversos algoritmos de compresión tanto para comprimir cada archivo individualmente como aplicarlo al archivado final.

La primera columna de datos muestra el número de segundos (wall-clock time) que tardó el proceso en escribir el archivo comprimido en una partición diferente del mismo conjunto de discos RAID. Se ejecutaron múltiples veces:

time [application] -c < nodes.csv > /output/nodes.csv.[format]

(excepto por 7z y dd, donde la sintaxis es diferente) y se tomó el valor de la mediana con el objetivo de minimizar errores debido a factores externos. Para cada ejecución, se comprobaba la corrección del archivo final (que el resultado era determinista y que podía extraerse de vuelta a su tamaño original sin errores ni diferencias) y después se borraba. Los resultados no son muy científicos, ya que se hacía uso del sistema de archivos de manera extensiva tanto para lecturas como para escrituras, pero mi objetivo era centrarme precisamente en ese escenario. Se muestra también una ejección de dd (copiado del archivo sin compresión) como valor de control.

Creo que la segunda y tercera columna de datos son autoexplicativas: el tamaño en bytes del archivo comprimido y cómo se compara respecto al archivo original.

La última columna intenta medir el uso de CPU máximo y mínimo, tal y como lo reporta el sistema operativo durante la compresión. Si embargo, debido al planficador de la CPU, y al hecho de que la mayoría de herramientas tienen un periodo de sincronización al principio o al final de la ejecución, unidos con el echo de que se obtuvo mediante polling en intervalos de tiempo, hace que no sea muy significativo excepto para la comprobación del uso del paralelismo en su algoritmo. Valores mayores que 100 indican que más de un core/hilo de ejecución se está usando para la compresión.

No registré el uso de memoria (el otro recurso importante) porque incluso en modos “ultra”, su uso no fue significativo para mi máquina con 32GB (menos de 1/2 GB en todos los casos, la mayoría mucho menos). Consideré que era algo que no debería preocuparnos mucho en un máquina que debería tener suficiente RAM como una base de datos. Lo que sí quizá debería tenerse en cuenta en un escenario real es la reserva de caché por el sistema de archivos, que podría impactar de manera directa al rendimiento de MySQL. Prevenir que las páginas leídas y escritas vayan a la caché del sistema de archivos es algo que se puede hacer jugando con el flag POSIX_FADV_DONTNEED. Me gustaría mencionar también que ciertas herramientas, como bzip, disponen de un modo de bajo consumo de recursos: bzip2 --small.

Los tiempos de descompresión los analizo en la segunda parte de este post.

Los resultados globales se pueden apreciar mucho más claramente dibujados en un grafo bidimensional. He representado los valores de tiempo de compresión en el eje X (menos es mejor) y el ratio de compresión en el eje Y (menos es mejor):

Comparativa de tiempo y ratio de compresión de gzip, bzip2, pigz, pbzip2, lzip, p7zip, plzip, lzop y lz4, con diferentes parámetros y niveles
Sin representación: dd (ratio de compresión del 100%), 7za “ultra” (21+ minutos para la compresión) y lzip (35+ minutos para la compresión).

En general, podemos ver que no hay herramientas mágicas, y que un mejor ratio de compresión requiere mas tiempo (el tamaño final es inversamente proporcional al tiempo de compresión). También he representado la función y = 200/x + 9. Ésta, o algo como y = 200/x+9.5 (es difícil encontrar una buena correlación con tan pocos resultados, la mayoría de ellos sin relación entre sí) parece indicarnos el límite inferior del ratio por unidad de tiempo, sugiriendo que un 9%-9.5% sería el máximo ratio de compresión obtenible para este archivo con las herramientas disponibles en este momento.

Analicemos los puntos fuertes y flacos de cada herramienta y formato de compresión.

Los famosos gzip y bzip2

Si lo que deseas es compatibilidad, gzip y bzip2 son los reyes. No sólo son formatos de compresión altamente reconocidos sino que las herramientas para compresión y descompresión están preinstaladas en la mayoría de sistemas operativos tipo unix. Probablemente Windows es el único sistema operativo que no soporta gzip por defecto. gzip y bzip2 son las únicas compresiones con su propia letra en tar (junto con compress en BSD y xz en GNU).

Si bien la compatibilidad y disponibilidad son los puntos fuertes de estas herramientas, mirando al grafo podemos apreciar que están lejos de la línea que mencionaba como ideal en ratio de tiempo/tamaño. bzip2 proporciona un mayor ratio de compresión que gzip a cambio de más ciclos de cpu, pero ambas herramientas son monohilo y no brillan en ningún aspecto en particular. Sorprendentemente, bzip2 -1 nos proporcionó un tiempo de compresión peor y un mejor ratio que la ejecución estándar de bzip2, y el manual de la versión gnu nos proporciona una explicación para ello:

The --fast and --best aliases are primarily for  GNU  gzip
compatibility.   In  particular,  --fast doesn't make things significantly faster.  And --best
merely selects the default behaviour.

(en español: los alias –fast y –best son primordialmente para compatibilidad con GNU gzip. En particular, –fast no hace las cosas significativamente más rápidas. Y –best simplemente selecciona el comportamiento por defecto)

Probablemente el mejor uso que recomendaría para estas herramientas sería gzip --fast (equivalente a gzip -1) que, aunque no proporcione un nivel de compresión espectacular, lo hace de manera relativamente rápida para una aplicación de un sólo hilo de ejecución. Por lo tanto, puede ser útil en aquellos casos en los que se desee maximizar la velocidad sin utilizar muchos recursos. En otros casos, donde la disponibilidad no es un problema, recomendaría utilizar otras herramientas con una mejor velocidad o nivel de compresión.

Para las pruebas utilicé las versiones GNU de gzip 1.3.12 y bzip2 1.0.6.

Herramientas de compresión paralela: pigz y pbzip2

Las cosas se ponen más interesantes si se usan alguna de las versiones paralelas de gzip o bzip2 en un sistema multi-core. Aunque hay más de una versión, elegí pigz 2.3.1 y pbzip2 1.1.6 para mis tests. Aunque no son parte oficial de los repositorios de Red Hat/CentOS, pueden encontrarse sin problemas en EPEL y Debian.

Ambas herramientas autodetectan el número de cores que dispongo y realizan la compresión en 8 hilos de ejecución, proporcionando ratios de compresión comparables pero en 4 veces menos de tiempo. El desventaja obvia es que en un entorno de alta demanda, como puede ser un servidor de MySQL bajo una carga considerable, puede que no desees/puedas otorgar los recursos completos de la CPU al proceso de backup. Pero si estás realizando la compresión en un servidor dedicado aparte, el paralelismo es algo de lo que deberías tomar ventaja, ya que en general, la CPU es el mayor cuello de botella en un algoritmo de compresión.

De nuevo, alguna cosa a destacar: pigz con los parámetros por defecto proporcionó un buen ratio de compresión (16,89%) en menos de 28 segundos- eso es comprimir a cerca de 130MB/s para mi modesto hardware (eso es más de un tercio de mi velocidad de copia, 350MB/s).

Como nota al margen, aunque pbzip2 acepta un nivel de compresión como parámetro, el nivel de compresión por defecto es -9.

Implementaciones de lzma: lzip, 7zip y plzip

Los siguientes test se realizados fueron distintas implementaciones de lzma, un algoritmo que tiene buena fama de proporcionar muy buenos ratios de compresión.

Comencé por lzip. No está en los repositorios oficiales, así que lo obtuve desde EPEL, instalando lzip 1.7. El ratio de compresión fue, efectivamente, el mejor de todos los algoritmos (cercano al 9.5%) pero tardó 35 minutos and 38 segundos en producir la salida. No sólo el algoritmo tenía la culpa en este caso: usaba un único hilo, de ahí el restraso.

Después de ello, utilizé p7zip 9.20, y en particular la herramienta unix 7za. Esta el la única aplicación de compresión probada que no es compatible con los parámetros de gzip. Tuve qe ejecutarla usando:

time 7za a /output/nodes.csv.7z nodes.csv

Tenga en cuenta que p7zip es una aplicación de archivado, pero hice una excepción para probar una implementación alternativa de lzma.

Los resultados fueron mejores: mientras que la herramienta proporcionó un ratio de compresión peor (10.29%), gracias a algún tipo de ejecución en mútiples hilos, el tiempo se redujo a menos de 14 minutos. También probé un sugerido modo “ultra” que encontré en el manual de 7za, con los siguientes parámetros:

-t7z -m0=lzma -mx=9 -mfb=64 -md=32m -ms=on

En resumen: maximizar el uso de memoria, nivel de compresión y tamaño del diccionario -aparte de forzar el formato de archivado y el algoritmo de compresión. Aunque esto proporcionó un tamaño de archivo más pequeño (pero sólo 25MB más pequeño, menos del 1% del tamaño original), el tiempo se incrementó hasta más de 21 minutos.

Quise también probar una implementación paralela de lzma, y plzip era exactamente eso. No pude encontrar un paquete rpm en ninguna parte, así que descargué e instalé desde código fuente Lzlib 1.5 y plzip 1.2-rc2. Los resultados fueron muy buenos, tal y como esperaba. plzip proporciona resultados comparables a “pigz -9” cuando se ejecuta en “modo rápido”; pero por defecto, en sólo 3m37s obtuve un archivo comprimido de 359MB, o 10.17% del archivo original. Después, intenté emular las opciones “ultra” de p7zip (con -9 -m 64 -s 33554432) y obtuve el ganador en ratio de compresión (9.57%) en sólo 7 minutos y 6.486 seconds.

Obviamente, las mismas restricciones que mencioné para las otras herramientas paralelas se aplican aquí: el uso completo de múltiples cores se desaconseja en un servidor muy ocupado, pero si estás almacenando los backups a largo plazo en un servidor separado, probablemenete quieras contemplar esta posibilidad. En cualquier caso, la mayoría de herramientas paralelas tienen una manera de limitar el número de hilos creados (por ejemplo, con la opción --threads en lzip).

Herramientas de compresión rápida: lzop and lz4

No quise terminar mis pruebas sin echar un vistazo a algunas de las herramientas de compresión de alto ancho de banda, así que elegí 2: lzop and lz4. Aunque tuve que instalar lz4 r119 desde EPEL, lzop v1.02rc1 es parte de los paquetes base de Red Hat/CentOS.

Ambos proporcionan lo que prometen: algoritmos de compresión muy rápidos (en algunos casos, incluso más rápidos que una simple copia de archivo, ya que el cuello de botella no está en la CPU y tienen que escribir menos cantidad de datos) a cambio de peores ratios de compresión (21-30%). Para el archivo de ejemplo, en mi máquina, obtuve un mejor rendimiento con lz4 que con lzop, ofreciendo ratios similares pero en menor tiempo (8.5 vs. 15.5 segundos). Así que, si tuviera que elegir, probablemente usaría lz4 antes que lzop en mi caso particular. Adicionalmente, aunque no se ha probado, lz4 presume de proporcionar mejores velocidades de descompresión.

Como resalte negativo, recomendaría no utilizar nunca lzop -9, ya que en ese caso dispondríamos de herramientas con un mejor ratio de compresión en la mitad de tiempo. lz4 no tuvo un buen rendimiento tampoco con un mayor nivel de compresión, así que recomendaría ceñirse a los parámetros por defecto o con un nivel menor de compresión para estas herramientas (de hecho, lz4 por defecto es equivalente a lz4 -1).

Conclusión

No he probado otras herramientas como compress (Lempel-Ziv), xz (lzma2) o QuickLZ, pero no espero demasiadas desviaciones de los patrones que hemos visto: el tiempo es inversamente proporcional al nivel de compresión. Si lo que quieres es compresión rápida, usa lz4. Si quieres un tamaño de archivo pequeño, utiliza una implementación de lzma, como p7zip. Los formatos bzip y gzip son buenas opciones si la compatibilidad es importante (ej. publicar un fichero), pero cuando sea posible, utiliza una implementación de compresión paralela para mejorar el rendimiento (plzip, pbzip2, pigz). Podemos incluso utilizar una combinación de herramientas para nuestros backups, por ejemplo, exportar nuestras tablas en formato binario usando lz4 para sacarlas del servidor mysql, y luego, en una máquina separada, convertirlos a lzma para almacenamiento a largo plazo.

También te aconsejaría probar los distintos métodos de compresión para tu conjunto de datos en particular y tu propio hardware ya que podrías obtener distintos ratios de compresión y medidas de tiempo, sobre todo dependiendo de la memoria disponible para cache del sistema de archivos, tu(s) CPU(s) y la velocidad de lectura y escritura de tu almacenamiento secundario. Lo que he intentado hacer aquí, sin embargo, es tener un punto de partida desde la cual cada uno pueda sacar sus propias conclusiones.

¿Estás de acuerdo conmigo? ¿Crees que he cometido algún error en algún punto? ¿Echas en falta algo? Escribe un comentario o mándame una respuesta a twitter o mediate email.

Echa una vistazo a la parte II de este artículo para mi análisis de estas herramientas para la descompresión.

¿Qué herramienta de compresión debería usar para las copias de seguridad de mi base de datos? (Parte I: compresión)
Etiquetado en:                                                                    

Un pensamiento en “¿Qué herramienta de compresión debería usar para las copias de seguridad de mi base de datos? (Parte I: compresión)

Los comentarios están cerrados.