Technical Delivery Manager
Snowflake es una plataforma avanzada de datos que se consume en modalidad SaaS 100% en cloud. El principal factor diferenciador de Snowflake es que proporciona capacidades avanzadas para todas las necesidades de datos de las compañías (Almacenamiento, procesamiento, explotación y soluciones de analítica avanzada) de una manera más flexible y sencilla que las soluciones de Datawarehouse tradicionales.
El motor de queries y procesamiento de Snowflake está basado 100% en SQL para facilitar el acceso a la mayoría de los profesionales de datos, aunque Snowflake está haciendo esfuerzos por ampliar las posibilidades de desarrollo (Por ejemplo, recientemente ha sacado Snowpark, una API que permite a los desarrolladores que estén habituados a trabajar con Spark tanto en Scala cómo en Java y recientemente en Python, a poder migrar sus códigos de forma sencilla a Snowflake). Además, dispone de conectores nativos con una serie de partners que abarca todas las fases de la ingeniería de datos, cómo por ejemplo partners de integración de datos tan importantes cómo Matillion, Informatica, DBT o DataStage; de Business Intelligence cómo Domo, Cognos o Looker; o de Machine Learning cómo Alteryx, Dataiku o AWS Sagemaker.
La otra ventaja diferenciadora de Snowflake es que tiene unas capacidades de optimización que no requieren apenas de mantenimiento y cubren un abanico muy amplio de casos de uso, entre las que se podrían destacar la clusterización automática, el cacheo y el search optimization service, elementos en los que ahondaremos en detalle en futuros artículos, ya que en éste nos vamos a centrar sobre todo en las capacidades de almacenamiento.
Principales características diferenciadoras de Snowflake:
La arquitectura de Snowflake está basada en tres principales capas:
Vamos a entender en profundidad cómo funciona Snowflake en la capa de almacenamiento. A grandes líneas, veremos:
Esto será la base para entender varias de las funcionalidades diferenciales que ofrece Snowflake:
El almacenamiento en Snowflake se basa en la generación de ficheros comprimidos con un tamaño máximo aproximado de 16MB y que se almacenan en un repositorio orientado a objetos tipo el S3 de AWS. Estos ficheros son inmutables, y cualquier operación de inserción-borrado-actualización siempre se hace generando un nuevo fichero de datos y actualizando los metadatos para saber cuáles son los ficheros que están activos en cada momento, además de otros metadatos que veremos más adelante en profundidad para eficientar la cantidad de bytes escaneados a la hora de ejecutar una query.
La forma en la que almacena los datos Snowflake está enfocada a dos objetivos principales:
Compresión columnar: Snowflake analiza y comprime automáticamente los datos durante la carga de la tabla, agrupándolos por columnas. En función del tipo de datos de cada una de las columnas, selecciona el esquema de compresión más óptimo para cada una de ellas: Cada columna puede tener su propio esquema de compresión y aumentar-reducir de forma independiente. Gracias a esta eficiencia en la compresión, se obtiene una mejora significativa en los rendimientos al reducir la cantidad de datos a escanear, además de un ahorro en costes de almacenamiento, ya que Snowflake factura por la cantidad almacenada ya comprimida.
Microparticiones: Son unidades de almacenamiento contiguo en las que Snowflake va almacenando los datos en el orden de la ingesta. A diferencia de otros motores de bases de datos, en Snowflake no es necesario declarar una forma de particionar los datos por una o más columnas, sino que él ya lo hace de manera automática de la siguiente forma: Por un lado, va insertando los datos según le llegan en bloques de almacenamiento que oscilan entre los 50 y los 500MB antes de compresión (16MB aprox comprimidos). Cuándo se llena un bloque, pasa al siguiente, y así sucesivamente hasta que todos los datos son insertados. Snowflake también encripta tanto en tránsito cómo en destino todos los datos.
Cada una de estas particiones son inmutables: en el caso en el que haya una actualización en alguna de las microparticiones, lo que se hace es crear una nueva versión de la misma, y se mantienen las versiones antiguas por el tiempo parametrizado en el time travel (propiedad DATA_RETENTION_TIME_IN_DAYS en la tabla Snowflake). La inmutabilidad permite cosas cómo por ejemplo poder acceder a versiones de los datos en diferentes momentos del tiempo o hacer clonados de tablas sin tener que replicar los datos.
Para cada micropartición, Snowflake genera una metadata con la siguiente información:
A nivel columna
A nivel tabla
Partiendo de los siguientes datos de ejemplo:
Ordenados por fecha. Al insertarlos en Snowflake, para ilustrar este ejemplo se supone que se generan dos microparticiones, que se van llenando en el orden en el que entran los datos:
Si por ejemplo, hacemos la siguiente query:
Select Fecha, sum(importe)
From ventas
Where fecha = ‘01/01/2022’
Snowflake recorrería los siguientes datos:
Si se buscan las ventas de un cliente específico:
Select sum(importe)
From ventas
Where cliente = ‘C2’
En este ejemplo, recorre las dos microparticiones, ya que C2 está dentro del rango de valores de ambas, aunque realmente C2 no está en la micropartición 1. Esto es lo que se comentaba en el apartado anterior de la posible dependencia que puede haber en la búsqueda de rangos en cada micropartición de cómo están distribuidos los datos.
Para ver cómo funcionan las principales operaciones de DML en Snowflake, hemos reproducido el siguiente experimento: Creamos una nueva tabla, partiendo de una tabla origen que tiene las ventas de varios días de 60 call centers, seleccionando solamente los Call Center 1 y 20. Lo que haremos será operaciones atómicas de inserción, actualización y borrado para ver cómo se gestionan tanto los datos cómo los metadatos.
Para verificar esto, partiendo del ejemplo anterior hemos lanzado una consulta que actualice los call center 10 y 11 a 15 por ejemplo. Comprobamos que efectivamente Snowflake solamente recorre esa partición, y genera un nuevo fichero con los nuevos valores, archivando el anterior:
Si se actualiza alguno de los otros dos call center, el número de particiones recorridas sería mayor, lo cuál implica que el coste de las operaciones DML también se ve afectado por la manera en que estén organizados los datos.
La importancia de entender cómo gestiona Snowflake estas operaciones es por las implicaciones que tiene a nivel rendimiento y almacenamiento. Sobre todo en el segundo caso, hay que tener en cuenta que si tenemos un alto número de días de retención en tablas (DATA_RETENTION_TIME_IN_DAYS) que se modifican frecuentemente, estaremos archivando muchas versiones de los datos que pueden incrementar considerablemente nuestro almacenamiento.
La principal ventaja es que Snowflake se encarga de todo este complejo mantenimiento siendo la gestión del almacenamiento transparente para el usuario.
En estos casos, para eficientar el almacenamiento es fundamental conocer los tres tipos principales de tablas que pone a nuestra disposición Snowflake, así cómo el concepto de Fail-Safe y Time-Travel:
Time-Travel: Periodo que, en función de la edición de Snowflake, (hasta un día en Standard y hasta 90 días en tablas permanentes a partir de edición Enterprise) permite almacenar todas las versiones por las que pasa una tabla, y habilita funcionalidades cómo poder restaurar datos en cualquier punto dentro de ese periodo, o hacer queries sobre un estado específico de los datos.
Fail-Safe: período de siete días durante el cuál se almacena cada versión de los datos en la que ha expirado su DATA_RETENTION_TIME_IN_DAYS y que permite la restauración de los mismos durante ese periodo pero solamente a través del soporte de Snowflake (Los usuarios no tienen acceso directo al Fail-Safe). Este periodo no es configurable y solamente está disponible en las tablas permanentes, cómo veremos a continuación.
Con estos dos conceptos claros, pasamos a describir los tres tipos principales de tablas en Snowflake:
Por la naturaleza de cada una de las tablas, vemos que por ejemplo debemos tener en cuenta que si nuestra tabla se puede ver afectada por continuas operaciones DML de actualización-inserción, en el caso que tengamos una tabla permanente con un alto número de días de Time-Travel, nuestros costes de almacenamiento pueden verse incrementados.
La recomendación general para optimizar el almacenamiento es que se utilicen tablas temporales para tablas que simplemente utilicemos cómo tablas intermedias o staging, las transitorias para tablas permanentes que puedan ser fácilmente reproducibles desde fuera, y las permanentes para tablas críticas que tengan que estar siempre disponibles y que el coste de reprocesamiento en caso de desastre sería elevado.
Primero vemos que se escanean las 49.448 microparticiones, lo cuál es lógico ya que no hemos incluído filtro alguno. Por otro lado, se escanean 13,58GB de los 770GB que tiene la tabla. Esto se debe a que en la query hemos incluído una única columna, y ya que Snowflake cómo hemos comentado almacena los datos de forma columnar y comprimida, solamente accede a los datos de la columna que consultamos.
Si aplicamos un filtro sobre la columna Call Center, que es un numérico que toma valores entre 1 y 60, y es un campo por el que no se ha ordenado en la inserción de los datos, y buscamos por ejemplo el call center número 20:
select distinct cr_ship_mode_sk from «SNOWFLAKE_SAMPLE_DATA».»TPCDS_SF100TCL».»CATALOG_RETURNS» where cr_call_center_sk = 20
Vemos que efectivamente, apenas se han podado valores: De las 49,448 microparticiones, 49.447 tenían en su rango de call center el 20, con lo cuál ha habido que recorrerlas igualmente.
Sin embargo, si incluímos en el filtro uno de los campos de clusterizado, por ejemplo el código de cliente:
Vemos que sólo se ha recorrido un 10% aprox de las microparticiones, y el tiempo de query ha bajado de 1 minuto 45 segundos a 12 segundos.
Con esto se puede concluir que el principal factor de rendimiento en las consultas es el número de bytes que tenga que escanear Snowflake el cuál viene principalmente determinado por el número de particiones a escanear, y la cantidad de datos de cada columna, y que si solamente incluimos en el filtro columnas por las que no estén ordenados los datos o no estén incluídos en la cluster key, en tablas de gran tamaño el rendimiento puede verse afectado. Es recomendable incluir en los filtros al menos uno de los campos de ordenación o de las cluster key para que las queries sean eficientes, o de no poder ser así, Snowflake nos proporciona otras alternativas para mejorar el rendimiento cómo las vistas materializadas, el cacheo o el search optimization service.
En estos casos, Snowflake dice que en tablas con tamaños por debajo de 1TB la organización automática de datos debe ser suficiente para obtener buen rendimiento en las consultas.
La tabla que se ha utilizado para estas pruebas contiene 100 millones de registros y seis columnas, dónde los datos se han distribuido en 49 particiones ocupando un total de 708MB (unos 14,5MB de media por micropartición). Los datos están ordenados por un campo de fecha.
Comentar que para estas pruebas, se ha utilizado la herramienta de Profiling de Snowflake, que está disponible desde el historial de queries. Hemos encontrado esta herramienta muy completa e intuitiva, y permite de un solo vistazo encontrar dónde se están generando los cuellos de botella en las queries, todo el plan de ejecución por el que pasa una query, así cómo las filas que salen de cada paso (lo cuál nos permite por ejemplo detectar cosas habituales de mal rendimiento cómo joins explosivos) y las microparticiones que se van podando en cada estado. Gracias a esta herramienta, hemos podido entender qué es lo que pasaba exactamente en cada una de las situaciones que hemos querido investigar y entender la gestión de Snowflake del almacenamiento.
Esta herramienta de profiling está disponible en el menú History de la UI, pinchando en la query que queramos analizar.
El objetivo de estas pruebas es entender la forma en la que Snowflake selecciona las microparticiones a recorrer y cómo de importante es la forma en la que se insertan los datos para mejorar el rendimiento en nuestras consultas, así cómo las columnas por las que se filtre.
En la tabla existe una columna, Call Center, dónde hay diferentes valores entre el 1 y el 60 pero con saltos (no están todos los posibles valores). Si hacemos una búsqueda por un call center específico de los que están:
Apreciamos que sea cuál sea el Call Center que incluyamos en el filtro siempre se recorren todas las microparticiones. La explicación es que Snowflake para determinar las microparticiones a recorrer, mira en la metadata de la columna Call Center si el valor buscado está dentro del rango, y en este caso, dónde los datos están ordenados por fecha, siempre se cumple que el valor está dentro del rango, por lo que tiene que recorrer todas las microparticiones.
Probamos a meter un nuevo registro de un Call Center con ID 11 que se sabe no aparece en los datos. Tras la inserción, el número de microparticiones se mantiene en 49, por lo que Snowflake ha debido generar un nuevo archivo que incluye el nuevo registro, y ha archivado la versión anterior de la micropartición.
Hacemos una búsqueda por ese Call Center, que a priori está en una única micropartición, y al revisar el Profile:
Se aprecia que Snowflake ha tenido que escanear las 49 microparticiones aunque se sabe que el valor 11 está en una micropartición específica. Esto confirma que Snowflake busca en base a rangos de valores por columna, y no conoce los valores específicos de una columna que hay en cada micropartición.
Para evidenciar aún más este hecho, insertamos un nuevo registro de Call Center que esté fuera del posible rango de búsqueda: Call Center con ID 61. Tras la inserción, verificamos que el número de particiones se mantiene, pero cuando se hace una búsqueda por ese valor:
Únicamente ha escaneado una micropartición. Esto se debe a que el 61 es un valor que está fuera del rango de la metadata del resto de las microparticiones, con lo cuál, ha podido saber que el Call Center 61 estaba en una única micropartición.
La siguiente comprobación es ver cómo Snowflake ejecuta la búsqueda de un valor de la columna Call Center que no está en los datos, pero sí en los posibles rangos de valores de la columna en las microparticiones. Por ejemplo, tenemos Call Centers 10, 11 y 13, pero no el 12. Si buscamos por el 12:
Cómo era de esperar, recorre todas las microparticiones, ya que el 12 entra en todos los posibles rangos de valores.
Para terminar de confirmar si Snowflake busca exclusivamente por rangos de valores, se crea una nueva tabla únicamente con los Call Center 1, 10 y 11. Esta nueva tabla tiene 8 microparticiones.
Si buscamos por el Call Center 5 (dentro de rango), recorre las 8 microparticiones aunque el Call Center no exista.
Si buscamos por el Call Center 12, directamente la metadata devuelve que ese Call Center no existe, y por tanto, no recorre ninguna micropartición.
Pero ahora, si buscamos por el valor 11, que recordemos fue una nueva inserción que metimos y justo está en el final del rango, en este caso Snowflake sí es capaz de podar el resto de microparticiones dónde no está el valor:
El motivo está en que se sabe que el resto de microparticiones tienen un rango 1-10, con lo cuál, la única que cumple estar en rango 1-11 es dónde verdaderamente está el valor. Sin embargo, en la otra tabla dónde era altamente probable que todas las microparticiones en la columna Call Center estuviesen en rango 1-60, ahí sí que tuvo que recorrerlas todas para saber dónde estaba el Call Center 11.
Conclusión de las pruebas:
Cuándo tengamos bajo rendimiento en consultas, hay dos indicadores principales a revisar en el profiling: Número de particiones escaneadas y cantidad de datos procesados.
Para mejorar la consulta, el objetivo es reducir el número de ambas: Para recorrer menos particiones hay que añadir filtros por campos en base a los cuáles se estén ordenando los datos (generalmente fechas o id’s numéricos) o replantearnos si ese campo es importante a la hora de filtrar, que los datos estén ordenados por dicho campo. Por supuesto, revisar también si las columnas que utilizamos en la consulta se pueden reducir.
Si esto no es posible, tendríamos que plantearnos otras estrategias de optimización, cómo clusterizar la tabla en base a ese campo, utilización de cachés, ver si el caso de uso se ajusta a la utilización del search optimization service, o la utilización de vistas materializadas que pueden a su vez estar clusterizadas o no. El detalle de estas estrategias queda fuera del alcance de este artículo.
Entendiendo cómo Snowflake gestiona el almacenamiento a nivel inserción, actualización y borrado de datos y cómo se gestionan estos datos a la hora de realizar consultas, estaríamos en disposición de dar el siguiente paso que es entender todas las funciones avanzadas que proporciona Snowflake a nivel de optimización, compartición y seguridad-resiliencia en los datos. Éste será el objetivo de siguientes artículos.
Documentación oficial de Snowflake https://docs.snowflake.com/en/
Technical Delivery Manager
Patrono
Patrocinador
© 2024 Bluetab Solutions Group, SL. All rights reserved.