Al trabajar con grandes volúmenes de datos, a menudo surge el problema de la falta de espacio en los discos. Una de las soluciones a este problema es la compresión, que permite aumentar la capacidad de almacenamiento en el mismo hardware. En este artículo, veremos cómo funciona la compresión de datos en Apache Ignite. Se describirán únicamente los métodos de compresión implementados dentro del producto. Otros métodos de compresión de datos (por red, en memoria), tanto implementados como no, quedarán fuera del alcance.
Así que, con el modo de persistencia activado, como resultado de los cambios de datos en las cachés, Ignite comienza a escribir en disco:
- El contenido de las cachés
- Registro de escritura anticipada (Write Ahead Log, en adelante simplemente WAL)
Desde hace tiempo existe un mecanismo para la compresión del WAL, llamado WAL compaction. En la reciente versión de Apache Ignite 2.8, se han introducido otros dos mecanismos que permiten comprimir datos en disco: la compresión de páginas de disco para comprimir el contenido de las cachés y la compresión de instantáneas de páginas WAL para comprimir ciertos registros de WAL. A continuación se detallan estos tres mecanismos.
Compresión de páginas de disco
¿Cómo funciona?
Para comenzar, haremos un breve resumen de cómo Ignite almacena los datos. Se utiliza memoria paginada para el almacenamiento. El tamaño de la página se establece al iniciar el nodo y no puede ser cambiado en etapas posteriores; además, el tamaño de la página debe ser una potencia de dos y múltiplo del tamaño del bloque del sistema de archivos. Las páginas se cargan en RAM desde el disco según sea necesario, y el tamaño de los datos en disco puede exceder la cantidad de RAM asignada. En caso de que falte espacio en RAM para cargar una página desde el disco, las páginas antiguas que ya no se utilizan serán expulsadas de la RAM.
En el disco, los datos se almacenan de la siguiente manera: se crea un archivo separado para cada partición de cada grupo de caché, en este archivo, las páginas se disponen una tras otra en orden creciente de índice. El identificador completo de cada página contiene el identificador del grupo de caché, el número de partición y el índice de la página en el archivo. De esta manera, mediante el identificador completo de la página, podemos determinar de manera unívoca el archivo y el offset en el archivo para cada página. Puedes leer más sobre la estructura de la memoria paginada en el artículo de Apache Ignite Wiki: .
El mecanismo de compresión de páginas de disco, como se puede deducir por su nombre, funciona a nivel de página. Al activar este mecanismo, el trabajo con los datos en RAM se realiza tal cual, sin ningún tipo de compresión, pero en el momento de guardar las páginas de RAM en el disco, se ejecuta su compresión.
Sin embargo, comprimir cada página de manera individual no es la solución al problema; hay que encontrar la forma de reducir el tamaño de los archivos finales con los datos. Si el tamaño de la página deja de ser fijo, ya no podemos escribir las páginas en el archivo una tras otra, ya que esto podría generar una serie de problemas:
- No podremos calcular el offset donde se encuentra la página en el archivo usando el índice de la página.
- No está claro qué hacer con las páginas que no están al final del archivo y que cambian de tamaño. Si el tamaño de la página se reduce, el espacio que ha liberado se pierde. Si el tamaño de la página aumenta, hay que buscar un nuevo lugar en el archivo para ella.
- Si la página se desplaza en un número de bytes que no es múltiplo del tamaño del bloque del sistema de archivos, entonces para leer o escribir, se necesitará tocar un bloque del sistema de archivos más, lo que puede llevar a una degradación del rendimiento.
Para no enfrentar estos problemas a su nivel, la compresión de páginas de disco en Apache Ignite utiliza un mecanismo del sistema de archivos llamado archivos dispersos. Un archivo disperso es aquél en el que algunas regiones, llenas de ceros, pueden ser marcadas como "hoys". De este modo, no se asignan bloques del sistema de archivos para almacenar estos hoyos, logrando así un ahorro de espacio en disco.
Lógicamente, para liberar un bloque del sistema de archivos, el tamaño del hoyo debe ser mayor o igual al bloque del sistema de archivos, lo que impone una restricción adicional al tamaño de la página en Apache Ignite: para que la compresión tenga algún efecto, es necesario que el tamaño de la página sea estrictamente mayor que el tamaño del bloque del sistema de archivos. Si el tamaño de la página es igual al tamaño del bloque, nunca podremos liberar un solo bloque, ya que para liberar un bloque único, la página comprimida debería ocupar 0 bytes. Sin embargo, si el tamaño de la página es igual al tamaño de 2 o 4 bloques, ya podremos liberar al menos un bloque si nuestra página se comprime al menos hasta un 50% o un 75% respectivamente.
Por lo tanto, la descripción final del funcionamiento del mecanismo es la siguiente: Al grabar una página en el disco, se intenta comprimir la página. Si el tamaño de la página comprimida permite liberar uno o más bloques del sistema de archivos, entonces la página se graba en forma comprimida, y en el lugar de los bloques liberados se crea un "hueco" (se realiza una llamada al sistema con la bandera "punch hole"). fallocate() Si el tamaño de la página comprimida no permite liberar bloques, la página se guarda tal cual, en su forma no comprimida. Todos los offsets de las páginas se calculan de la misma manera que sin compresión, multiplicando el índice de la página por el tamaño de la página. No se requiere reubicación de páginas por cuenta propia. Los offsets de las páginas, al igual que sin compresión, caen en los límites de los bloques del sistema de archivos.

En la implementación actual, Ignite puede trabajar con archivos dispersos solo en sistemas operativos Linux, por lo tanto, la compresión de páginas en disco solo puede habilitarse al usar Ignite en este sistema operativo.
Los algoritmos de compresión que pueden usarse para la compresión de páginas en disco son: ZSTD, LZ4, Snappy. Además, existe un modo de operación (SKIP_GARBAGE) donde solo se descarta el espacio no utilizado en la página sin aplicar compresión a los datos restantes, lo que reduce la carga en la CPU en comparación con los algoritmos mencionados anteriormente.
Impacto en el rendimiento
Desafortunadamente, no he realizado mediciones reales de rendimiento ni en los entornos reales, ya que no planeamos utilizar este mecanismo en producción, pero se puede razonar teóricamente sobre dónde perderíamos y dónde ganaríamos.
Para ello, necesitamos recordar cómo se realizan las lecturas y escrituras de páginas al acceder a ellas:
- Al realizar una operación de lectura, primero se busca en la RAM, y si la búsqueda no tiene éxito, la página se carga en la RAM desde el disco con el mismo hilo que realiza la lectura.
- Al realizar una operación de escritura, la página en la RAM se marca como sucia; sin embargo, la salvaguarda física de la página en el disco no ocurre de inmediato en el hilo que está realizando la escritura. Todas las páginas sucias se guardan en el disco más tarde durante el proceso de chequeo por otros hilos.
Por lo tanto, el impacto en las operaciones de lectura es:
- Positivo (disk IO), debido a la reducción en la cantidad de bloques leídos del sistema de archivos.
- Negativa (CPU), debido a la carga adicional necesaria para que el sistema operativo trabaje con archivos sparse. También es posible que aparezcan en este contexto operaciones IO adicionales para guardar una estructura más compleja de archivos sparse (lamentablemente, no estoy familiarizado con todos los detalles del funcionamiento de los archivos sparse).
- Negativa (CPU), debido a la necesidad de descompresión de páginas.
- No hay influencia en las operaciones de escritura.
- Influencia en el proceso de checkpoint (aquí todo es similar a las operaciones de lectura):
- Positiva (disk IO), gracias a la reducción en la cantidad de bloques escritos en el sistema de archivos.
- Negativa (CPU, posiblemente disk IO), debido al trabajo con archivos sparse.
- Negativa (CPU), debido a la necesidad de compresión de páginas.
¿Qué balanza pesará más? Todo depende mucho del entorno, pero tiendo a pensar que la compresión de páginas de disco llevará a una degradación del rendimiento en la mayoría de los sistemas. Especialmente considerando que las pruebas en otras bases de datos que utilizan un enfoque similar con archivos sparse muestran una caída en el rendimiento con la compresión activada.
Cómo habilitar y configurar
Como se mencionó anteriormente, la versión mínima de Apache Ignite que admite la compresión de páginas de disco es 2.8 y solo se soporta en el sistema operativo Linux. La habilitación y configuración se realiza de la siguiente manera:
- En el class-path debe estar el módulo ignite-compression. Por defecto, se encuentra en el distribuidor de Apache Ignite en el directorio libs/optional y no se incluye en el class-path. Se puede simplemente mover el directorio un nivel hacia arriba dentro de libs y entonces al ejecutar a través de ignite.sh se incluirá automáticamente.
- La persistencia debe estar habilitada (se habilita con
DataRegionConfiguration.setPersistenceEnabled(true)). - El tamaño de página debe ser mayor que el tamaño del bloque del sistema de archivos (se puede establecer usando
DataStorageConfiguration.setPageSize()). - Para cada caché cuyos datos deben ser comprimidos, es necesario en la configuración establecer el método de compresión y (opcionalmente) el nivel de compresión (métodos
CacheConfiguration.setDiskPageCompression(), CacheConfiguration.setDiskPageCompressionLevel()).
Compactación WAL
¿Cómo funciona?
¿Qué es WAL y para qué se necesita? En resumen: es un registro en el que se registran todos los eventos que modifican en última instancia el almacenamiento de páginas. Su principal finalidad es la posibilidad de recuperación en caso de caída. Cualquier operación, antes de devolver el control al usuario, debe primero registrar el evento en WAL, para poder reproducir el registro en caso de caída y restaurar todas las operaciones por las que el usuario recibió una respuesta exitosa, incluso si estas operaciones no se habían reflejado aún en el almacenamiento de páginas en el disco (como se describió anteriormente, la grabación real en el almacenamiento de páginas se realiza en un proceso llamado
Las entradas en WAL se dividen en lógicas y físicas. Las lógicas son las propias claves y valores. Las físicas reflejan los cambios en las páginas del almacenamiento de páginas. Si las entradas lógicas pueden ser útiles en otros casos, las entradas físicas son necesarias únicamente para la recuperación en caso de caída y se necesitan registros desde el último chequeo exitoso. Aquí no entraremos en detalles ni explicaremos por qué funciona de esta manera, pero quienes estén interesados pueden consultar el artículo mencionado en Apache Ignite Wiki: .
A menudo, una entrada lógica corresponde a varias entradas físicas. Es decir, por ejemplo, una operación de put en la caché afecta a varias páginas en la memoria de páginas (la página con los propios datos, las páginas con los índices, las páginas con listas libres). En algunas pruebas sintéticas, descubrí que las entradas físicas ocupaban hasta el 90% del volumen del archivo WAL. Sin embargo, solo son necesarias durante un breve período (por defecto, el intervalo entre chequeos es de 3 minutos). Sería lógico eliminar estos datos después de que pierdan su relevancia. Precisamente esto es lo que hace el mecanismo de compactación de WAL, deshaciéndose de las entradas físicas y comprimiendo las entradas lógicas restantes con zip, reduciendo significativamente el tamaño del archivo (a veces en decenas de veces).
El WAL físico consta de varios segmentos (por defecto 10) de tamaño fijo (por defecto 64 MB), que se sobrescriben en un bucle. Una vez que el segmento actual está lleno, se le asigna el siguiente segmento, y el segmento lleno se copia en un archivo de archivo mediante un hilo separado. La compactación de WAL ya funciona con los segmentos archivados. También, mediante un hilo separado, supervisa la ejecución del punto de control y comienza la compresión de los segmentos archivados, cuyas grabaciones físicas ya no son necesarias.

Impacto en el rendimiento
Dado que la compactación de WAL funciona en un hilo separado, no debería haber un impacto directo en las operaciones que se están realizando. Sin embargo, causa una carga adicional en segundo plano en la CPU (compresión) y en el disco (lectura de cada segmento WAL del archivo y escritura de los segmentos comprimidos), por lo que si el sistema está funcionando al límite de sus capacidades, también llevará a una degradación del rendimiento.
Cómo habilitar y configurar
Se puede habilitar la compactación de WAL mediante la propiedad WalCompactionEnabled en DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). Además, con el método DataStorageConfiguration.setWalCompactionLevel() se puede establecer el nivel de compresión, si no se está satisfecho con el valor predeterminado (BEST_SPEED).
Compresión de instantáneas de página WAL
¿Cómo funciona?
Anteriormente, ya hemos determinado que los registros WAL se dividen en lógicos y físicos. Por cada cambio en cada página en la memoria de páginas se forma un registro físico WAL. Los registros físicos, a su vez, también se dividen en 2 subtipos: registro de instantánea de página y registro delta. Cada vez que cambiamos algo en una página y la convertimos de un estado limpio a sucio, se guarda una copia completa de esta página en WAL (registro de instantánea de página — instantánea de la página). Incluso si solo hemos cambiado un byte, en WAL se guardará un registro que ocupa poco más que el tamaño de la página. Si cambiamos algo en una página que ya está sucia, se forma un registro delta en WAL, que refleja solo los cambios en comparación con el estado anterior de la página, pero no la página entera. Dado que el restablecimiento de las páginas de sucias a limpias se ejecuta durante el proceso de punto de control, prácticamente todos los registros físicos consistirán solo en instantáneas de páginas (ya que todas las páginas están limpias justo después de comenzar el punto de control); luego, a medida que nos acercamos al siguiente punto de control, la proporción de registros delta comienza a crecer y se restablece al comienzo del siguiente punto de control. Las mediciones en algunas pruebas sintéticas mostraron que la proporción de instantáneas de páginas en el volumen total de registros físicos alcanza el 90%.
La idea de la compresión de instantáneas de página WAL consiste en comprimir las instantáneas de las páginas utilizando una herramienta de compresión de páginas ya existente (ver compresión de páginas en disco). En este caso, los registros WAL se almacenan secuencialmente en modo solo de anexado y no es necesaria la vinculación de los registros a los límites de los bloques del sistema de archivos, por lo que aquí, a diferencia del mecanismo de compresión de páginas en disco, no necesitamos archivos dispersos, lo que significa que este mecanismo funcionará no solo en sistemas operativos Linux. Además, ya no es importante cuánto logremos comprimir la página. Incluso si liberamos 1 byte, ya es un resultado positivo y podemos guardar en WAL los datos comprimidos, a diferencia de la compresión de páginas en disco, donde guardamos la página comprimida solo si liberamos más de 1 bloque del sistema de archivos.
Las páginas son datos que se comprimen bien, y su proporción en el volumen total de WAL es muy alta, por lo que sin cambiar el formato del archivo WAL, podemos lograr una reducción significativa de su tamaño. La compresión de los registros lógicos requeriría cambiar el formato y perder la compatibilidad, por ejemplo, para los consumidores externos que pueden estar interesados en los registros lógicos, y no traería una disminución significativa en el volumen del archivo.
Al igual que para la compresión de páginas de disco, se pueden utilizar algoritmos de compresión ZSTD, LZ4, Snappy, así como el modo SKIP_GARBAGE para la compresión de instantáneas de páginas WAL.
Impacto en el rendimiento
Como puede notarse, la inclusión directa de la compresión de instantáneas de páginas WAL afecta solo a los flujos que escriben datos en la memoria de páginas, es decir, a aquellos flujos que modifican datos en las cachés. La lectura de los registros físicos WAL ocurre solo una vez, en el momento de la recuperación del nodo tras una caída (y solo en caso de que haya una caída durante el proceso de checkpoint).
Para los flujos que modifican datos, esto afecta de la siguiente manera: obtenemos un efecto negativo (CPU) debido a la necesidad de comprimir la página cada vez antes de escribir en el disco y un efecto positivo (disk IO) debido a la reducción de la cantidad de datos escritos. Por lo tanto, aquí todo es simple: si el rendimiento del sistema se ve limitado por la CPU, experimentamos una ligera degradación; si es por las entradas/salidas del disco, obtenemos un aumento.
Indirectamente, la reducción del tamaño de WAL también afecta (positivamente) a los flujos que archivan segmentos WAL y a los flujos de compactación de WAL.
Las pruebas de rendimiento en nuestro entorno con datos sintéticos mostraron un pequeño aumento (el throughput aumentó entre un 10% y un 15%, y la latencia disminuyó entre un 10% y un 15%).
Cómo habilitar y configurar
La versión mínima de Apache Ignite: 2.8. La activación y configuración se realiza de la siguiente manera:
- En el class-path debe estar el módulo ignite-compression. Por defecto, se encuentra en el distribuidor de Apache Ignite en el directorio libs/optional y no se incluye en el class-path. Se puede simplemente mover el directorio un nivel hacia arriba dentro de libs y entonces al ejecutar a través de ignite.sh se incluirá automáticamente.
- La persistencia debe estar habilitada (se habilita con
DataRegionConfiguration.setPersistenceEnabled(true)). - Se debe establecer el modo de compresión mediante el método
DataStorageConfiguration.setWalPageCompression(), de manera predeterminada la compresión está desactivada (modo DISABLED). - Opcionalmente, se puede establecer el grado de compresión mediante el método
DataStorageConfiguration.setWalPageCompression(), los valores permitidos para cada uno de los modos se pueden ver en la javadoc del método.
Conclusión
Los mecanismos de compresión de datos en Apache Ignite discutidos pueden usarse de manera independiente entre sí, pero también se permiten todas sus combinaciones. Comprender los principios de su funcionamiento permitirá determinar qué tan adecuados son para sus tareas en su entorno y qué sacrificios deberán hacerse al utilizarlos. La compresión de páginas en disco está diseñada para comprimir el almacenamiento principal y puede proporcionar un grado medio de compresión. La compresión instantánea de páginas WAL ofrecerá un grado medio de compresión de los archivos WAL, y probablemente mejorará el rendimiento. La compactación WAL no afectará positivamente al rendimiento, pero reducirá al máximo el tamaño de los archivos WAL eliminando registros físicos.
Fuente: habr.com
