Puesta en funcionamiento de Snowpark Python: primera parte

Dec 06 2022
En la primera parte de esta publicación, describiremos algunos de los desafíos únicos asociados con la puesta en producción del código Snowpark Python, analizaremos los componentes con más detalle y describiremos algunos principios de diseño de código a seguir al compilar con Snowpark Python. En una segunda parte futura, analizaremos los conceptos relevantes de CI/CD para los desarrolladores de Python que trabajan con Snowpark Python.

En la primera parte de esta publicación, describiremos algunos de los desafíos únicos asociados con la puesta en producción del código Snowpark Python, analizaremos los componentes con más detalle y describiremos algunos principios de diseño de código a seguir al compilar con Snowpark Python.

En una segunda parte futura, analizaremos los conceptos relevantes de CI/CD para los desarrolladores de Python que trabajan con Snowpark Python.

Desafíos principales

¿Por qué la gestión del código Snowpark en producción es diferente de la gestión del código tradicional/heredado en producción? En muchos sentidos, no lo es. De hecho, si ya está familiarizado con la creación de aplicaciones y capacidades que funcionan con Snowflake y utilizó cosas como procedimientos almacenados, funciones definidas por el usuario, etc. (ya sea SQL o Snowpark en un idioma diferente), entonces administrar el código Python de Snowpark es no es fundamentalmente diferente de cómo está haciendo el mismo trabajo hoy. Existen algunas restricciones ambientales para el tiempo de ejecución de Python del lado del servidor en Snowflake, pero son similares a cómo Javascript y las capacidades de Java están restringidas en Snowpark, por lo que se aplicarán los mismos principios y prácticas que está siguiendo para sus capacidades existentes impulsadas por Snowflake. Pitón del parque de nieve.

Las diferencias y los desafíos se vuelven más evidentes para los equipos y las aplicaciones que se basan en Python, pero no en Snowflake. Incluso si Snowflake actúa como una fuente de datos y/o un sumidero dentro de su arquitectura existente, las capacidades de Python que se ejecutan externamente a Snowflake (en máquinas virtuales sin servidor o alojadas en la nube, etc.) frente a las que se crean en Snowpark Python tienen algunas diferencias clave para ser consciente de. Fundamentalmente, las capacidades integradas en Snowpark son solo código de Python, y muchas de las mismas prácticas y herramientas que el equipo usa hoy para administrar sus bases de código existentes siguen siendo relevantes. Las consideraciones principales asociadas con la creación de Snowpark se derivan del hecho de que el código final y la aplicación se implementan en una plataforma SaaS completamente administrada. y, por lo tanto, la infraestructura y los marcos informáticos en los que debe funcionar su código no están completamente abiertos a su control y discreción. Además, la aplicación está completamente integrada con su plataforma de datos, lo que brinda muchos beneficios, pero es un paradigma diferente al que se desarrolla para la mayoría de las aplicaciones, por lo que los patrones de diseño recomendados difieren. La implementación del código de Python en un entorno de SQL primero también impone limitaciones sobre qué tipos de datos y cómo se pueden pasar datos entre módulos, funciones, etc. Las aplicaciones de Snowpark también son diferentes de otras aplicaciones que pueden estar diseñadas para estar en funcionamiento continuamente, 24 /7. Snowpark es más adecuado para aplicaciones y trabajos bajo demanda y/o programados/orquestados.

Componentes del parque de nieve

El término amplio Snowpark Python se refiere a dos componentes fundamentalmente diferentes, pero también estrechamente acoplados. Una es una API de marco de datos de cliente, que está disponible como un paquete de Python y se puede instalar en cualquier entorno de Python (la compatibilidad con Python 3.8 es GA a partir del 7 de noviembre de 2022), aprovechada por cualquier aplicación de Python, etc. para impulsar las transformaciones de marco de datos en el cálculo de copo de nieve. En contraste con eso, Snowpark también tiene un tiempo de ejecución del lado del servidor que le permite implementar código Python más arbitrario en un tiempo de ejecución administrado por Snowflake, para que se ejecute junto con sus datos. Hay varios mecanismos diferentes sobre cómo se puede implementar y ejecutar el código Python en el tiempo de ejecución del lado del servidor, que se analizará con más detalle a continuación. Esta distinción es importante porque, Si bien existen desafíos compartidos que deben abordarse para Snowpark Python en términos generales, cada uno de estos componentes también tiene desafíos específicos y únicos que intentaremos abordar a lo largo de este documento. En general, nos referiremos a Snowpark Python para abarcar ambos componentes en general, el cliente o la API de marco de datos para hacer referencia a la biblioteca de Python instalable, y el tiempo de ejecución del lado del servidor para hacer referencia al entorno de ejecución de Python administrado por Snowflake. En esta publicación resumiremos los componentes principales, pero debe consultar el y el tiempo de ejecución del lado del servidor para hacer referencia al entorno de ejecución de Python administrado por Snowflake. En esta publicación resumiremos los componentes principales, pero debe consultar el y el tiempo de ejecución del lado del servidor para hacer referencia al entorno de ejecución de Python administrado por Snowflake. En esta publicación resumiremos los componentes principales, pero debe consultar elSnowpark Developer Guide for Python para obtener detalles adicionales, ejemplos de código, documentación y más.

API de marco de datos del cliente

La API de marco de datos del cliente de Snowpark es una biblioteca de Python que se puede instalar en cualquier entorno en el que pueda ejecutar un kernel de Python y tiene conectividad con su cuenta de Snowflake (nota: actualmente, la API de marco de datos solo es compatible con Python 3.8). La API de marco de datos proporciona una sintaxis de Python con estilo de marco de datos para realizar consultas, transformaciones y más en el entorno informático administrado de Snowflake.

Fundamentalmente, la API de Snowpark proporciona métodos de Python correspondientes para operaciones SQL comunes, en una sintaxis familiar para la API de marco de datos de PySpark, con una superposición considerable en la funcionalidad de lo que puede hacer en PySpark hoy. Es importante tener en cuenta que Snowpark utiliza un motor de cómputo de back-end fundamentalmente diferente en comparación con PySpark, pero sintáctica y funcionalmente se verá y se sentirá muy similar a PySpark Dataframe API. La API opera en Snowpark Dataframes, que son punteros a tablas, vistas, etc. dentro de Snowflake. Las operaciones correspondientes para una llamada a la API de Snowpark se ejecutan con pereza en un almacén virtual de Snowflake. Esto significa que noLas operaciones de marco de datos de Snowpark se ejecutan directamente en el entorno informático del cliente de Python, por lo que los requisitos informáticos del cliente pueden ser extremadamente bajos. Snowpark también proporciona métodos convenientes para traer marcos de datos de Snowpark en la memoria del cliente como marcos de datos de pandas, y viceversa (escribir marcos de datos de pandas nuevamente en Snowflake). La API de Snowpark requiere que los datos de origen estén ubicados en Snowflake para realizar transformaciones de inserción en un almacén virtual de Snowflake. Además de los métodos correspondientes a las operaciones de SQL, la API de Snowpark contiene otras funciones auxiliares, junto con la capacidad de invocar objetos Snowpark Python del lado del servidor (UD(T)Fs y Sprocs, que se describen con más detalle a continuación) desde un cliente de Python. tiempo de ejecución

UD(T)Fs y procedimientos almacenados

El tiempo de ejecución del lado del servidor de Snowpark Python hace posible escribir procedimientos almacenados de Python y funciones definidas por el usuario (tabla) (UD(T)F) que se implementan en Snowflake, disponibles para invocar desde cualquier interfaz de Snowflake y ejecutar en un entorno seguro. Sandbox de Python en los almacenes virtuales de Snowflake.

Los UDF y UDTF de Python escalan el procesamiento asociado con el código de Python subyacente para que se produzca en paralelo en todos los subprocesos y nodos que componen el almacén virtual en el que se ejecuta la función. Hay tres mecanismos diferentes de tipo UDF con Snowpark Python:

  • Las funciones definidas por el usuario (UDF) son operaciones escalares uno a uno: para una fila de datos de entrada que se pasan a la función, se produce una única salida. Las filas de datos se procesan en paralelo en los procesos de Python en cada nodo dentro de un almacén virtual.
  • Las UDF vectorizadas/por lotes son operaciones escalares uno a uno similares a las UDF descritas anteriormente. La diferencia es que las UDF paralelizan las operaciones de la UDF en filas individuales de datos. UDF vectorizados paralelizan la operación UDF en lotesde datos (múltiples filas). Funcionalmente, las UDF vectorizadas todavía producen una sola salida para cada fila de datos de entrada, sin embargo, los datos se procesan por lotes en instancias individuales de la UDF (muchas filas pasan simultáneamente y muchos lotes se procesan en paralelo). La razón de esto es que muchas operaciones de Python basadas en Pandas, Numpy, scipy, etc. que pueden usarse en las UDF de Python están optimizadas para ejecutarse como operaciones de estilo vectorial. Cuando se procesan filas individuales, como en una UDF estándar, la UDF no aprovecha al máximo las optimizaciones basadas en matrices que están integradas en las bibliotecas de Python subyacentes. Las UDF vectorizadas le permiten hacer esto; son funcionalmente iguales a los UDF de Python normales, sin embargo, los datos se procesan por lotes y se operan de forma masiva para aprovechar las optimizaciones basadas en matrices en varias bibliotecas de Python comunes.
  • Las funciones de tabla definidas por el usuario (UDTF) son funciones de Python que requieren operaciones con estado en lotes de datos. Los UDF vectorizados agrupan aleatoriamente los datos para un procesamiento más óptimo y acelerado; sin embargo, no permiten que el usuario/desarrollador determine qué datos se agrupan y cómo se procesa el lote completo de datos: solo filas individuales. Las UDTF permiten el procesamiento de filas de muchos a muchos y de muchos a uno. Cada UDTF tiene un método de proceso y un método de partición final. El método de proceso define qué trabajo se realiza como filas individualesen un lote se procesan (puede o no devolver algún tipo de salida por fila, dependiendo de la funcionalidad subyacente). El método endPartition define qué trabajo se realiza en todo el lote de datos después del procesamiento de todas las filas individuales, y puede incluir algún tipo de trabajo con estado que se haya acumulado a medida que se procesó el lote. Cuando se invocan las UDTF, la partición por expresión le permite especificar qué campos de los datos subyacentes se utilizan para agrupar los datos, es decir, si realiza la partición por PAÍS , todos los registros con PAÍS=US se procesan en el mismo lote, todos los registros con COUNTRY=CHINA se procesan en el mismo lote, etc.

Además de UD(T)Fs, el tiempo de ejecución del lado del servidor de Snowpark Python brinda soporte para los procedimientos almacenados de Python. Los procedimientos almacenados se pueden considerar como un script más arbitrario que se ejecuta en un almacén virtual de Snowflake. Una diferencia clave es que los procedimientos almacenados son de un solo nodo; por lo tanto, para realizar transformaciones o análisis de datos a escala dentro de un procedimiento almacenado, los procesos almacenados deben aprovechar la API del marco de datos del cliente u otros UD(T)F implementados para escalar la computación correspondiente en todos los nodos de un almacén virtual. Esto contrasta con extraer todos los datos de una consulta ael procedimiento almacenado y manipularlo, por ejemplo, con pandas. En su lugar, escale el cálculo en el almacén virtual desde un procedimiento almacenado aprovechando la API de marco de datos y los UD(T)F para un trabajo más intensivo desde el punto de vista informático. Esto se ilustra con más detalle en el siguiente principio de diseño de código: debe evitar extraer datos directamente a un procedimiento almacenado en la mayor medida posible (con algunas excepciones: esto se detalla más adelante en este artículo). Sin embargo, lo que proporcionan los procedimientos almacenados es una forma sencilla de implementar un flujo de programa similar a un script en el tiempo de ejecución del lado del servidor de Snowpark, que se puede "iniciar" como un trabajo a través de tareas o declaraciones directas de SQL "CALL sproc".

Diseño de código

Las capacidades básicas de Snowpark Python se pueden considerar en cinco grupos.

Criterios de diseño

  1. El principio rector para desarrollar capacidades operativas con Snowpark Python es no extraer datos de Snowflake y procesarlos en un entorno de cliente siempre que sea posible . En su lugar, aproveche la API de marco de datos del cliente de Snowpark para la inserción de operaciones similares a SQL y el tiempo de ejecución del lado del servidor para implementar más código arbitrario como UD(T)F que pueden escalar en los almacenes virtuales de Snowflake y procesar los datos de manera más eficiente. Esto se puede considerar como una práctica de programación similar a impulsar SQL a través de JDBC en otras herramientas o aplicaciones.
  2. Utilice la API de marco de datos del cliente Snowpark en sus aplicaciones al consultar y transformar datos, en lugar de obtener todos los datos en una aplicación cliente y procesarlos allí. Esto puede escalar efectivamente sus transformaciones de datos, independientemente de si toda la aplicación se ejecuta dentro del entorno de tiempo de ejecución del lado del servidor Snowpark o en un entorno externo a Snowflake Python. Vale la pena señalar que puede usar SQL directamente en lugar de usar la API de marco de datos, sin embargo, a muchos desarrolladores les resulta engorroso intentar crear y usar SQL dentro de una aplicación escrita en otro idioma, mientras que la API de marco de datos proporciona una sintaxis Pythonic familiar para lograr los mismos resultados a escala.
  3. El código que manipula, analiza o transforma datos debe, en la mayor medida posible, construirse como UD(T)F (la decisión entre UDF y UDTF se reduce en gran medida a lo que el código o la transformación realmente está haciendo funcionalmente) o debe ser implementado utilizando la API de marco de datos del cliente de Snowpark. Esto se debe a que la API del marco de datos y los UD(T)F le permiten escalar y paralelizar el trabajo computacional que se realiza en el almacén virtual.
  4. Python Sprocs son los más adecuados para el flujo de control de los programas de Python. Piense en el sproc como la secuencia de comandos principal o la aplicación: inicializa objetos, mantiene el estado, etc. Dentro del sproc, cualquier cálculo intensivo de datos debe llamar a la API del marco de datos o usar UD(T)F que se hayan creado e implementado por separado. De manera similar al principio anterior de "no extraer datos para el cliente", por lo general, debe evitar extraer datos en la memoria del procedimiento almacenado, ya que esto limita su capacidad de escalar. En su lugar, envíe el trabajo realizado en el sproc al almacén virtual mediante la API del marco de datos y los UD(T)F. El sproc se puede considerar como una "unidad" de trabajo dentro de su aplicación, que puede orquestar mediante tareas o un servicio de orquestación externo (p. ej., Airflow).
  5. Solo para volver a enfatizar: debe evitar extraer datos directamente al Sproc en la mayor medida posible. Los sprocs están restringidos a ejecutarse en un solo nodo en el almacén virtual y, por lo tanto, están restringidos a los recursos computacionales de ese nodo. Los almacenes optimizados para Snowpark (ahora en versión preliminar pública) ampliarán la capacidad de los sproc para realizar más trabajo intensivo en memoria/datos, pero como regla general, los sproc no deben encargarse de realizar cálculos significativos por sí mismos y, dentro de un sproc, debe descargar trabajo a la API de Snowpark y/o UD(T)Fs.
  6. La excepción al principio 5 es el cálculo que no se puede realizar de manera distribuida y requiere acceso a la totalidad de un conjunto de datos (o una muestra grande) para poder realizarlo. Por ejemplo, el entrenamiento del modelo de aprendizaje automático de un solo nodo generalmente se debe realizar en el contexto de un procedimiento almacenado, pero este es un ejemplo raro en el que se debe realizar un cálculo intensivo de datos en un procedimiento almacenado. Sin profundizar en demasiados detalles, puede haber casos de uso en torno al entrenamiento de modelos específicamente donde la paralelización a través de UD(T)F tiene sentido, pero este es un conjunto pequeño y específico de casos de uso.
  7. Se deben seguir los principios de diseño de código estándar de Python en torno a la modularidad del código, la reutilización, etc. Es una buena práctica traer utilidades de uso común en su aplicación como una dependencia de paquete personalizado de terceros, de modo que el código se pueda desarrollar/mantener una vez y potencialmente aprovechado en todo su ecosistema de aplicaciones de Snowpark.
  8. Las clases y los objetos personalizados que utiliza en su aplicación deben evaluarse para determinar su compatibilidad con UD(T)F. Por ejemplo: suponga que tiene una parte de su aplicación que toma una gran cantidad de datos, los analiza y luego construye un objeto de clase de Python personalizado basado en el resultado de su análisis. Esto es potencialmente una buena opción para una UDTF; sin embargo, no admitimos la devolución de objetos de Python arbitrarios desde las funciones de Snowpark. Como tal, deberá considerar métodos de serialización/deserialización de su objeto para tipos de datos SQL admitidos, por ejemplo, implementar constructores/serializadores to_json() y from_json() para que pueda inicializar sus objetos de clase a partir de los datos de la tabla Snowflake. También puede considerar la serialización binaria; independientemente del enfoque, esto debe tenerse en cuenta.
  9. Las instancias de UD(T)F se reutilizarán dentro de un único conjunto de consultas. Puede aprovechar esto moviendo el código de inicialización o las variables/estado temporal que se pueden reutilizar en ejecuciones fuera del método de función y en la parte global/estática del código. Esto significará que en ejecuciones posteriores esas variables o estado temporal podrían reutilizarse sin tener que reiniciar todas y cada una de las ejecuciones. Esto será especialmente importante a medida que estén disponibles cosas como el acceso externo, en el que querrá colocar clientes HTTP o grupos de conexiones fuera de la declaración de función (consulte AWS Lambda/Azure Functions para obtener patrones de diseño similares y prácticas recomendadas).
  10. Los UDF vectorizados (por lotes) le permiten realizar las mismas operaciones que los UDF, mientras aprovecha las optimizaciones internas basadas en matrices de Python en Pandas y objetos de tipo Numpy (esto se describe con más detalle arriba). Como regla general, generalmente es una buena práctica implementar las UDF como UDF vectorizadas si alguna de las operaciones subyacentes en los datos se basa en Numpy/Pandas o se implementa mediante operaciones vectoriales. Hacerlo simplemente optimiza la distribución de datos al tiempo de ejecución del lado del servidor y permite que Snowpark aproveche las optimizaciones integradas de Python.
  11. Además de las propias capas de almacenamiento en caché de Snowflake, el código Python de Snowpark generalmente debe seguir las mejores prácticas de almacenamiento en caché de Python, con la excepción de la necesidad de almacenar en caché los resultados de las consultas (ya que Snowflake lo hará por usted en varias capas de la arquitectura). Pero, por ejemplo, si tiene una UDF que realiza la tokenización de texto en su aplicación, y para usarla necesita cargar un tokenizador integrado desde el escenario, debe envolver una función que cargue el tokenizador en la UDF usando cachetools, para evitar repetidamente cargando el tokenizador desde el escenario a la UDF. Este es el caso de uso más común para el almacenamiento en caché dentro del código Snowpark, cuando un artefacto debe cargarse en un UD(T)F desde el escenario.

Portar aplicaciones existentes a Snowpark

Muchos clientes han estado preguntando cómo pueden portar las capacidades y aplicaciones existentes para que se ejecuten en Snowpark, tanto para mejorar la escalabilidad, el rendimiento, la gobernanza y la seguridad como para simplificar su arquitectura y eliminar la complejidad asociada con la propiedad y administración de la infraestructura. Más allá de la evaluación inicial de "Snowpark puede admitir esta aplicación y/o código", debe haber una amplia discusión y conversación sobre cómo mover el código de la manera más óptima. En el caso de migrar aplicaciones de PySpark específicamente a Snowpark (frente a otras aplicaciones de Python más genéricas), Snowflake se ha asociado con Mobilize.netpara proporcionar una herramienta gratuita de análisis de código PySpark a Snowpark que puede ayudar a determinar si su base de código existente es un buen candidato para la migración a Snowpark. Además, los equipos de servicios profesionales de Snowflake y los socios de SI pueden brindar soporte de migración completo.

En el caso de aplicaciones de Python más genéricas, los scripts de Python podríansimplemente se colocan en Python sprocs y es probable que "funcionen" más o menos, pero es probable que esta sea una implementación extremadamente ineficiente, a menos que el script en particular sea computacionalmente liviano. En particular, es probable que estas aplicaciones existentes extraigan datos de Snowflake, que, como enfatizamos anteriormente, no es el patrón recomendado para crear aplicaciones en Snowpark. Las dos preguntas principales que se deben tener en cuenta son (1) cómo se debe refactorizar el código que extrae datos de Snowflake para aprovechar una mayor potencia de cómputo. Además, (2) las aplicaciones intensivas en datos deben evaluarse especialmente para determinar qué funciones, métodos, clases, etc. se atienden mejor aprovechando Python UD(T)Fs y aprovechando la escalabilidad y el rendimiento en los almacenes virtuales.

Más allá del análisis de código puro, otro enfoque para realizar esta evaluación de migración es comenzar con una base de código y producir un diagrama de flujo lógico de la aplicación/secuencia de comandos, dividido en componentes computacionales individuales. Es posible que muchos equipos ya tengan disponible este tipo de diseño de software/diagrama de arquitectura, pero si no es así, es un buen comienzo para comprender cómo se distribuyen los datos y el trabajo computacional en su aplicación. Este diagrama también puede incluir objetos de clase personalizados, etc. Para cada función, clase, método que se utiliza, debe evaluar qué datos se deben proporcionar y qué salida se produce. ¿La salida es compatible con los tipos de datos de Snowflake? ¿Qué consumirá la salida y cómo se utilizará? ¿El cómputo realizado es lo suficientemente intensivo como para garantizar que se realice en un grupo de nodos de cómputo en un almacén virtual? ¿Cuánta configuración personalizada se requiere en cada iteración del uso de la función/objeto?

Al completar este diagrama, comenzará a quedar claro qué debe existir en la capa de flujo lógico de la aplicación (que puede ser una buena opción para un sproc Snowpark Python) y qué debe descargarse a un UD(T)F , y por lo tanto potencialmente refactorizado. Esto también le indicará qué clases y objetos personalizados deben tener métodos de serialización para escribir o inicializar desde la estructura de la tabla Snowpark, como se describe en los principios de diseño anteriores.

A partir de este punto, puede comenzar a comprender qué código se puede levantar y cambiar, en comparación con lo que debe refactorizarse, reimplementarse o modificarse. Además, Snowflake Professional Services ofrece más asistencia práctica con la migración de aplicaciones a Snowpark que va más allá de los principios y las mejores prácticas.

Conclusión

En esta publicación, detallamos qué es Snowpark Python (API de marco de datos del cliente y tiempo de ejecución del lado del servidor) y cómo se puede usar mejor en las aplicaciones de Python. Describimos los principios básicos de diseño que deben seguirse al diseñar o migrar aplicaciones para Snowpark Python. En la segunda parte, veremos cómo incorporar estas nuevas capacidades en las prácticas existentes de CI/CD y DevOps, incluida la forma en que Snowpark Python puede ser similar y bastante diferente de otros marcos de desarrollo.