Hoy en día, asegurar un buen rendimiento y respuesta dentro de nuestras soluciones analíticas constituye un requisito indispensable con el que todo sistema debe contar. Para ello, dentro del mundo de la analítica e ingesta de datos con Spark y Delta Lake, contamos con diversas estrategias a la hora de configurar nuestras tablas y procesos como, tradicionalmente, incorporar el uso de particiones y aplicación del algoritmo Z-ORDER (no confundir con el V-ORDER propio de Fabric) que bien (conjuntamente o de forma separada), nos permiten optimizar el rendimiento de nuestras consultas.
No obstante, estos mecanismos, aunque muy útiles (tanto juntos como separados), no están exentos de algunas desventajas así como pueden no ser viables para todos los casos. Por un lado, el particionado sobre tablas ordenadas mediante Z-ORDER nos da un mayor control sobre la organización de datos, tiene mejor soporte para escenarios de escritura en paralelo así como permite optimizar de forma más granular particiones específicas (ordenando por distintos criterios cada una por ejemplo).
Sin embargo, debemos tener en cuenta que:
- Para particionar nuestros datos, deberemos escoger una columna con baja cardinalidad(una columna de tipo fecha normalmente), que asegure un volumen de datos de al menos 1 GB por partición y que evite crear un número muy elevado de particiones (recomendable mantenerlo por debajo de 10.000). De lo contrario, podemos encontrarnos con posibles problemas de rendimiento al tener particiones demasiado pequeñas y con una distribución poco uniforme
- En lo que respecta al Z-ORDER, su uso está más orientado a columnas con alta cardinalidad, pudiendo hacer uso de más de una columna aunque cada columna añadida resta potencia a su rendimiento. Además, no puede reordenar ficheros ya ordenados, por lo que si es necesario cambiar las claves escogidas, será necesario reescribir la tabla/partición en cuestión. Esta operación aún será más costosa si lo que queremos cambiar es el campo/s por los que particionamos
- Ambos requieren conocer previamente los patrones de consulta empleados por los usuarios para poder aprovechar al máximo sus funcionalidades, requiriendo que las columnas estén presentes en las consultas realizadas para poder hacer file skipping
- El particionado no suele recomendarse para tablas con un tamaño inferior a 1 TB
Dados los requerimientos para cada uno de ellos, ya sea juntos o separados, es difícil poder darles salida de forma íntegra dentro de nuestra solución. Sin embargo, ya no son la única opción.
Con la llegada del nuevo runtime de Spark a Fabric, el 1.3, la versión de Delta Lake incluida en él, nos posibilita la aplicación de una nueva característica que simplifica mucho de estos requerimientos y posibilita su aplicación sobre un espectro más amplio de casos, Liquid Clustering.
Liquid Clustering es un nuevo algoritmo de distribución para las tablas delta que cuenta con una mayor flexibilidad y cobertura, no siendo necesario particionar los datos y pudiendo dar salida a escenarios donde los patrones de consulta no sean conocidos previamente y/o puedan cambiar en el tiempo, Con él, es posible redefinir las columnas empleadas sin necesidad de reescribir los datos así como simplificar la implementación de nuestras soluciones reemplazando al particionado y al Z-ORDER (con los que no es compatible).
Permite usar hasta cuatro columnas como claves por las que clusterizar, que pueden definirse en cualquier orden. Para sacarle el máximo partido, se recomienda utilizar columnas empleadas habitualmente como filtros de consulta y/u operación (como un MERGE para actualizar los datos) así como en escenarios donde nuestras tablas:
- Cuenten con filtros sobre columnas con alta cardinalidad
- Tengan una distribución de datos asimétrica/no balanceada o donde una clave de particionado tenga como resultado muchas o muy pocas particiones
- Que crezcan rápidamente, requiriendo mantenimiento y ajustes
- Con patrones de acceso/consulta que cambien en el tiempo
Poniéndolo en práctica
Para poder utilizar Liquid Clustering, tal como hemos comentado anteriormente, será imprescindible contar con el runtime 1.3 configurado dentro del área de trabajo:
Posteriormente, desde un cuaderno y mediante Spark SQL, podremos crear la tabla clusterizada gracias a la opción CLUSTER BY, donde especificaremos las columnas por las que deseamos clusterizar la tabla:
Una vez creada la tabla, es importante establecer en la versión adecuada las propiedades de delta.minReaderVersion y delta.maxReaderVersion respectivamente, para que la tabla pueda ser reconocida correctamente por el lakehouse una vez implementada. De lo contrario, la tabla presentará errores y no podrá visualizarse correctamente mediante el explorador.
Para poder configurar dichas propiedades, lo haremos nuevamente a través de Spark SQL:
IMPORTANTE: Esta operación es irreversible
Podemos comprobar el resultado final gracias al comando DESCRIBE DETAIL:
Conclusión
Con Liquid Clustering, obtenemos una forma de optimización más fácil y flexible de aplicar que puede contribuir a mejorar el rendimiento de nuestras soluciones, bien de forma individual o en conjunto con otras estrategias citadas anteriormente, de forma que podamos obtener el mejor resultado posible.
Cabe recordar que si bien Liquid Clustering no es compatible con el particionado y/o la aplicación de Z-ORDER, esto no implica que sea completamente excluyente, pudiendo optar por una estrategia específica de optimización para cada una de las tablas de nuestra solución según sus características.
Como norma general, podemos tener presentes las siguientes recomendaciones para su aplicación:
- Para tablas con un tamaño inferior a 10 GB, será más recomendable emplear Liquid Clustering
- Para tablas con un tamaño entre 10 GB y 10 TB, ambas aproximaciones pueden ser correctas, siendo el factor decisor las necesidades y características de cada tabla
- Para tablas con tamaño superior a 10 TB, es más recomendable utilizar particiones en conjunción con Z-ORDER
En cualquier caso, es más que aconsejable, si es viable, testear todas las aproximaciones para decidir la mejor opción en cada escenario, tratando de buscar el mejor encaje para nuestros datos y uso de estos, contribuyendo así a una mejora del rendimiento en las consultas y mayor eficiencia en nuestras operaciones.