Compresión de datos en Apache Ignite. La experiencia de Sber.

Compresión de datos en Apache Ignite. La experiencia de Sber.Cuando se trabaja con grandes volúmenes de datos, a veces puede surgir el problema de la falta de espacio en disco. Una forma de solucionar este problema es la compresión, gracias a la cual, en un mismo equipo, puedes permitirte aumentar los volúmenes de almacenamiento. En este artículo, veremos cómo funciona la compresión de datos en Apache Ignite. Este artículo describirá únicamente los métodos de compresión de disco implementados en el producto. Otros métodos de compresión de datos (a través de la red, en la memoria), ya sea que se implementen o no, quedarán fuera del alcance.

Entonces, con el modo de persistencia habilitado, como resultado de los cambios en los datos en las cachés, Ignite comienza a escribir en el disco:

  1. Contenido de cachés
  2. Registro de escritura anticipada (en adelante, simplemente WAL)

Ha existido un mecanismo para la compresión WAL desde hace bastante tiempo, llamado compactación WAL. El Apache Ignite 2.8 lanzado recientemente introdujo dos mecanismos más que le permiten comprimir datos en el disco: compresión de páginas de disco para comprimir el contenido de las cachés y compresión de instantáneas de páginas WAL para comprimir algunas entradas WAL. Más detalles sobre estos tres mecanismos a continuación.

Compresión de página de disco

¿Cómo funciona esto

Primero, echemos un vistazo breve a cómo Ignite almacena datos. La memoria de página se utiliza para el almacenamiento. El tamaño de la página se establece al inicio del nodo y no se puede cambiar en etapas posteriores; además, el tamaño de la página debe ser una potencia de dos y un múltiplo del tamaño del bloque del sistema de archivos. Las páginas se cargan en la RAM desde el disco según sea necesario; el tamaño de los datos en el disco puede exceder la cantidad de RAM asignada. Si no hay suficiente espacio en la RAM para cargar una página desde el disco, las páginas antiguas que ya no se utilizan serán expulsadas de la RAM.

Los datos se almacenan en el disco de la siguiente forma: se crea un archivo separado para cada partición de cada grupo de caché; en este archivo, las páginas aparecen una tras otra en orden de índice ascendente. El identificador de página completa contiene el identificador del grupo de caché, el número de partición y el índice de página del archivo. Por lo tanto, utilizando el identificador de página completa, podemos determinar de forma única el archivo y el desplazamiento en el archivo para cada página. Puede leer más sobre la memoria de paginación en el artículo de Apache Ignite Wiki: Ignite Persistent Store: debajo del capó.

El mecanismo de compresión de páginas del disco, como se puede adivinar por el nombre, funciona a nivel de página. Cuando este mecanismo está habilitado, los datos en la RAM se procesan tal cual, sin ningún tipo de compresión, pero cuando las páginas se guardan de la RAM en el disco, se comprimen.

Pero comprimir cada página individualmente no es una solución al problema; es necesario reducir de alguna manera el tamaño de los archivos de datos resultantes. Si el tamaño de la página ya no es fijo, ya no podremos escribir páginas en el archivo una tras otra, ya que esto puede crear una serie de problemas:

  • Usando el índice de la página, no podremos calcular el desplazamiento por el cual se ubica en el archivo.
  • No está claro qué hacer con las páginas que no están al final del archivo y cambian su tamaño. Si el tamaño de la página disminuye, el espacio liberado desaparece. Si el tamaño de la página aumenta, debe buscarle un nuevo lugar en el archivo.
  • Si una página se mueve una cantidad de bytes que no es un múltiplo del tamaño del bloque del sistema de archivos, entonces para leerla o escribirla será necesario tocar un bloque más del sistema de archivos, lo que puede provocar una degradación del rendimiento.

Para evitar resolver estos problemas a su propio nivel, la compresión de páginas de disco en Apache Ignite utiliza un mecanismo de sistema de archivos llamado archivos dispersos. Un archivo disperso es aquel en el que algunas regiones rellenas con ceros se pueden marcar como "agujeros". En este caso, no se asignarán bloques del sistema de archivos para almacenar estos huecos, lo que permitirá ahorrar espacio en el disco.

Es lógico que para liberar un bloque del sistema de archivos, el tamaño del agujero debe ser mayor o igual al bloque del sistema de archivos, lo que impone una limitación adicional en el tamaño de la página y en Apache Ignite: para que la compresión tenga algún efecto, el tamaño de la página debe ser 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, entonces nunca podremos liberar un solo bloque, ya que para liberar un solo bloque la página comprimida debe ocupar 0 bytes. 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 está comprimida al menos al 50% o 75%, respectivamente.

Así, la descripción final de cómo funciona el mecanismo: Al escribir 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 escribe en forma comprimida y se hace un "agujero" en lugar de los bloques liberados (se ejecuta una llamada al sistema fallocate() con la bandera perforada). Si el tamaño de la página comprimida no permite liberar los bloques, la página se guarda tal cual, sin comprimir. Todos los desplazamientos de página 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 es necesario reubicar las páginas por su cuenta. Los desplazamientos de página, al igual que sin compresión, caen dentro de los límites de los bloques del sistema de archivos.

Compresión de datos en Apache Ignite. La experiencia de Sber.

En la implementación actual, Ignite solo puede funcionar con archivos dispersos en el sistema operativo Linux; en consecuencia, la compresión de páginas del disco solo se puede habilitar cuando se usa Ignite en este sistema operativo.

Algoritmos de compresión que se pueden utilizar para la compresión de páginas de disco: ZSTD, LZ4, Snappy. Además, existe un modo operativo (SKIP_GARBAGE), en el que 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 enumerados anteriormente.

Impacto en el rendimiento

Desafortunadamente, no realicé mediciones de rendimiento reales en stands reales, ya que no planeamos utilizar este mecanismo en la producción, pero teóricamente podemos especular dónde perderemos y dónde ganaremos.

Para hacer esto, debemos recordar cómo se leen y escriben las páginas cuando se accede a ellas:

  • Al realizar una operación de lectura, primero se busca en la RAM; si la búsqueda no tiene éxito, la página se carga en la RAM desde el disco mediante el mismo hilo que realiza la lectura.
  • Cuando se realiza una operación de escritura, la página en la RAM se marca como sucia, pero el subproceso que realiza la escritura no guarda físicamente la página en el disco inmediatamente. Todas las páginas sucias se guardan en el disco más adelante en el proceso del punto de control en subprocesos separados.

Entonces el impacto en las operaciones de lectura es:

  • Positivo (disco IO), debido a una disminución en el número de bloques de lectura del sistema de archivos.
  • Negativo (CPU), debido a la carga adicional que requiere el sistema operativo para trabajar con archivos dispersos. También es posible que aquí aparezcan implícitamente operaciones IO adicionales para guardar una estructura de archivos dispersos más compleja (desafortunadamente, no estoy familiarizado con todos los detalles de cómo funcionan los archivos dispersos).
  • Negativo (CPU), debido a la necesidad de descomprimir páginas.
  • No hay impacto en las operaciones de escritura.
  • Impacto en el proceso del punto de control (todo aquí es similar a las operaciones de lectura):
  • Positivo (disco IO), debido a una disminución en la cantidad de bloques escritos del sistema de archivos.
  • Negativo (CPU, posiblemente disco IO), debido a que se trabaja con archivos dispersos.
  • Negativo (CPU), debido a la necesidad de compresión de la página.

¿Qué lado de la balanza inclinará la balanza? Todo esto depende en gran medida del entorno, pero me inclino a creer que la compresión de la página del disco probablemente provocará una degradación del rendimiento en la mayoría de los sistemas. Además, las pruebas en otros DBMS que utilizan un enfoque similar con archivos dispersos muestran una caída en el rendimiento cuando se habilita la compresión.

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 admite el sistema operativo Linux. Habilite y configure de la siguiente manera:

  • Debe haber un módulo de compresión de encendido en la ruta de clases. De forma predeterminada, se encuentra en la distribución de Apache Ignite en el directorio libs/optional y no está incluido en la ruta de clases. Simplemente puede mover el directorio hacia arriba un nivel a libs y luego, cuando lo ejecute a través de ignite.sh, se habilitará automáticamente.
  • La persistencia debe estar habilitada (habilitada a través de DataRegionConfiguration.setPersistenceEnabled(true)).
  • El tamaño de la página debe ser mayor que el tamaño del bloque del sistema de archivos (puede configurarlo usando DataStorageConfiguration.setPageSize() ).
  • Para cada caché cuyos datos deben comprimirse, debe configurar 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 esto

¿Qué es WAL y por qué es necesario? Muy brevemente: este es un registro que contiene todos los eventos que finalmente cambian el almacenamiento de la página. Es necesario principalmente para poder recuperarse en caso de caída. Cualquier operación, antes de ceder el control al usuario, primero debe registrar un evento en WAL, de modo que en caso de falla, se pueda reproducir en el registro y restaurar todas las operaciones para las cuales el usuario recibió una respuesta exitosa, incluso si estas operaciones no tuvo tiempo de reflejarse en el almacenamiento de páginas en el disco (ya se describió anteriormente que la escritura real en el almacén de páginas se realiza en un proceso llamado "puntos de control" con cierto retraso mediante subprocesos separados).

Las entradas en WAL se dividen en lógicas y físicas. Los booleanos son las claves y los valores en sí. Físico: refleja cambios en las páginas del almacén de páginas. Si bien los registros lógicos pueden ser útiles para otros casos, los registros físicos solo se necesitan para la recuperación en caso de un fallo y los registros solo se necesitan desde el último punto de control exitoso. Aquí no entraremos en detalles ni explicaremos por qué funciona de esta manera, pero aquellos interesados ​​pueden consultar el artículo ya mencionado en Apache Ignite Wiki: Ignite Persistent Store: debajo del capó.

Generalmente hay varios registros físicos por registro lógico. Es decir, por ejemplo, una operación de colocación en caché afecta a varias páginas en la memoria de páginas (una página con los datos en sí, páginas con índices, páginas con listas libres). En algunas pruebas sintéticas, encontré que los registros físicos ocupaban hasta el 90% del archivo WAL. Sin embargo, son necesarios por muy poco tiempo (de forma predeterminada, el intervalo entre puntos de control es de 3 minutos). Sería lógico deshacerse de estos datos después de perder su relevancia. Esto es exactamente lo que hace el mecanismo de compactación WAL: elimina los registros físicos y comprime los registros lógicos restantes usando zip, mientras que el tamaño del archivo se reduce de manera muy significativa (a veces decenas de veces).

Físicamente, WAL consta de varios segmentos (10 por defecto) de un tamaño fijo (64 MB por defecto), que se sobrescriben de forma circular. Tan pronto como se completa el segmento actual, el siguiente segmento se asigna como actual y el segmento completado se copia al archivo mediante un hilo separado. La compactación WAL ya funciona con segmentos de archivo. Además, como hilo separado, monitorea la ejecución del punto de control y comienza la compresión en segmentos de archivo para los cuales ya no se necesitan registros físicos.

Compresión de datos en Apache Ignite. La experiencia de Sber.

Impacto en el rendimiento

Dado que la compactación WAL se ejecuta como un subproceso independiente, no debería haber ningún impacto directo en las operaciones que se realizan. Pero todavía supone una carga de fondo adicional en la CPU (compresión) y el disco (lectura de cada segmento WAL del archivo y escritura de los segmentos comprimidos), por lo que si el sistema se ejecuta a su capacidad máxima, también provocará una degradación del rendimiento.

Cómo habilitar y configurar

Puede habilitar la compactación WAL usando la propiedad WalCompactionEnabled в DataStorageConfiguration (DataStorageConfiguration.setWalCompactionEnabled(true)). Además, utilizando el método DataStorageConfiguration.setWalCompactionLevel(), puede establecer el nivel de compresión si no está satisfecho con el valor predeterminado (BEST_SPEED).

Compresión de instantáneas de página WAL

¿Cómo funciona esto

Ya hemos descubierto que en WAL los registros se dividen en lógicos y físicos. Para cada cambio en cada página, se genera un registro WAL físico en la memoria de la página. 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 transferimos de un estado limpio a un estado sucio, se almacena una copia completa de esta página en WAL (registro de instantánea de página). Incluso si cambiamos solo un byte en WAL, el registro será un poco más grande 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 completa. Dado que el restablecimiento del estado de las páginas de sucia a limpia se realiza durante el proceso del punto de control, inmediatamente después del inicio del punto de control, casi todos los registros físicos consistirán solo en instantáneas de las páginas (ya que todas las páginas inmediatamente después del inicio del punto de control están limpias). , luego, a medida que nos acercamos al siguiente punto de control, la fracción del registro delta comienza a crecer y se reinicia nuevamente al comienzo del siguiente punto de control. Las mediciones realizadas 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áginas WAL es comprimir instantáneas de páginas utilizando una herramienta de compresión de páginas ya preparada (consulte compresión de páginas de disco). Al mismo tiempo, en WAL, los registros se guardan secuencialmente en modo de solo agregar y no es necesario vincular 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 del disco, no necesitamos archivos dispersos en todo; en consecuencia, este mecanismo funcionará no solo en el sistema operativo Linux. Además, ya no nos importa cuánto pudimos comprimir la página. Incluso si liberamos 1 byte, esto ya es un resultado positivo y podemos guardar datos comprimidos en WAL, a diferencia de la compresión de páginas de disco, donde guardamos la página comprimida solo si liberamos más de 1 bloque del sistema de archivos.

Las páginas son datos altamente comprimibles, su participación en el volumen total de WAL es muy alta, por lo que sin cambiar el formato del archivo WAL podemos obtener una reducción significativa en su tamaño. La compresión, incluidos los registros lógicos, requeriría un cambio de formato y una pérdida de compatibilidad, por ejemplo, para los consumidores externos que puedan estar interesados ​​en registros lógicos, pero no daría lugar a una reducción significativa del tamaño del archivo.

Al igual que con la compresión de páginas de disco, la compresión de instantáneas de páginas WAL puede utilizar algoritmos de compresión ZSTD, LZ4, Snappy, así como el modo SKIP_GARBAGE.

Impacto en el rendimiento

No es difícil notar que habilitar directamente la compresión de instantáneas de páginas WAL solo afecta a los subprocesos que escriben datos en la memoria de la página, es decir, aquellos subprocesos que cambian datos en la caché. La lectura de registros físicos de WAL ocurre solo una vez, en el momento en que el nodo se levanta después de una caída (y solo si cae durante un punto de control).

Esto afecta a los subprocesos que cambian datos 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 (disco IO) debido a una disminución en la cantidad de datos escritos. En consecuencia, aquí todo es simple: si el rendimiento del sistema está limitado por la CPU, obtenemos una ligera degradación, si está limitado por la E/S del disco, obtenemos un aumento.

Indirectamente, la reducción del tamaño de WAL también afecta (positivamente) a las secuencias que volcan segmentos WAL en el archivo y a las secuencias de compactación de WAL.

Las pruebas de rendimiento reales en nuestro entorno utilizando datos sintéticos mostraron un ligero aumento (el rendimiento aumentó entre un 10% y un 15%, la latencia disminuyó entre un 10% y un 15%).

Cómo habilitar y configurar

Versión mínima de Apache Ignite: 2.8. Habilite y configure de la siguiente manera:

  • Debe haber un módulo de compresión de encendido en la ruta de clases. De forma predeterminada, se encuentra en la distribución de Apache Ignite en el directorio libs/optional y no está incluido en la ruta de clases. Simplemente puede mover el directorio hacia arriba un nivel a libs y luego, cuando lo ejecute a través de ignite.sh, se habilitará automáticamente.
  • La persistencia debe estar habilitada (habilitada a través de DataRegionConfiguration.setPersistenceEnabled(true)).
  • El modo de compresión debe configurarse utilizando el método DataStorageConfiguration.setWalPageCompression(), la compresión está deshabilitada de forma predeterminada (modo DESHABILITADO).
  • Opcionalmente, puede establecer el nivel de compresión utilizando el método DataStorageConfiguration.setWalPageCompression(), consulte el javadoc para conocer el método y conocer los valores válidos para cada modo.

Conclusión

Los mecanismos de compresión de datos considerados en Apache Ignite se pueden utilizar de forma independiente, pero también es aceptable cualquier combinación de ellos. Comprender cómo funcionan le permitirá determinar qué tan adecuados son para sus tareas en su entorno y qué tendrá que sacrificar al usarlos. La compresión de páginas de disco está diseñada para comprimir el almacenamiento principal y puede proporcionar una relación de compresión media. La compresión de instantáneas de páginas WAL proporcionará un grado promedio de compresión para archivos WAL y probablemente incluso mejorará el rendimiento. La compactación WAL no tendrá un efecto positivo en el rendimiento, pero reducirá el tamaño de los archivos WAL tanto como sea posible eliminando registros físicos.

Fuente: habr.com

Añadir un comentario