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):
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.
Pingback:¿Qué herramienta de compresión debería usar para las copias de seguridad de mi base de datos? (Parte II: descompresión) | Contrate un MySQL DBA
Pingback:¿Qué herramienta de compresión debería usar para las copias de seguridad de mi base de datos? (Parte I: compresión)
dejar por el camino xz es un error, la velocidad es similar a bzip2 y su ratio de compresión es mayor. A la hora de descomprimir es como un orden de magnitud más rápido.
Además la mayoría de distros ya lo traen de serie
Lo he probado también, he dejado unas impresiones rápidas aquí: http://barrapunto.com/comments.pl?sid=91204&cid=1363631 La implementación estándar es no paralela, lo que me hace no recomendarla para grandes conjuntos de datos. En general, se aplica a xz todo lo que he dicho sobre lzip.
Pingback:Probando la manera más rápida de importar una tabla en MySQL (y unos resultados interesantes del rendimiento de 5.7) | Contrate un MySQL DBA