Conception d’UDFs Python

Ce sujet vous aide à concevoir des UDFs Python.

Dans ce chapitre :

Note

Les UDFs Python vectorisées vous permettent de définir des fonctions Python qui reçoivent des lots de lignes d’entrée sous forme de DataFrames Pandas et renvoient des lots de résultats sous forme de tableaux ou de séries Pandas. L’interface lots permet d’obtenir de bien meilleures performances avec les scénarios d’inférence de machine learning. Pour plus d’informations, voir UDFs Python vectorisées.

Choisir vos types de données

Avant d’écrire votre code :

  • Choisissez les types de données que votre fonction doit accepter comme arguments et le type de données que votre fonction doit retourner.

  • Tenez compte des problèmes liés aux fuseaux horaires.

  • Décidez comment traiter les valeurs NULL.

Pour plus d’informations sur la façon dont Snowflake fait correspondre les types de données Python et SQL, voir Mappages des types de données SQL-Python.

Valeurs et fuseaux horaires TIMESTAMP_LTZ

Une UDF Python est largement isolée de l’environnement dans laquelle elle est appelée. Cependant, le fuseau horaire est hérité de l’environnement d’appel. Si la session de l’appelant a défini un fuseau horaire par défaut avant d’appeler l’UDF Python, alors l’UDF Python a le même fuseau horaire par défaut. Pour plus d’informations sur les fuseaux horaires, voir TIMEZONE.

Valeurs NULL

Pour tous les types Snowflake, à l’exception de Variant, un argument SQL NULL à une UDF Python se traduit par la valeur None Python et une valeur None Python retournée se traduit à nouveau par une valeur NULL en SQL.

Une valeur de type Variant peut être : SQL NULL ou un VARIANT JSON null. Pour des informations sur les données VARIANT NULL Snowflake, voir Valeurs NULL.

  • Un VARIANT JSON null est traduit en une valeur None en Python.

  • Une valeur NULL SQL est traduite en un objet Python, qui possède l’attribut is_sql_null.

Pour un exemple, voir Traitement des NULL dans les UDFs Python.

Concevoir des UDFs Python qui respectent les contraintes imposées par Snowflake

Pour assurer la stabilité dans l’environnement Snowflake, Snowflake impose les contraintes suivantes aux UDFs Python. Sauf indication contraire, ces limitations sont appliquées lorsque l’UDF est exécutée, et non lors de sa création.

La formation de modèles de machine learning (ML) peut parfois être très exigeante en ressources. Les entrepôts optimisés pour Snowpark sont un type d’entrepôt virtuel Snowflake qui peut être utilisé pour les charges de travail qui nécessitent une grande quantité de mémoire et de ressources de calcul. Pour des informations sur les modèles de machine learning et sur Snowpark Python, voir Formation de modèles de machine learning avec Snowpark Python.

Mémoire

Évitez de consommer trop de mémoire.

  • Les grandes valeurs de données peuvent consommer une grande quantité de mémoire.

  • Une profondeur de pile excessive peut consommer une grande quantité de mémoire.

Les UDFs renvoient une erreur si elles consomment trop de mémoire. La limite spécifique est sujette à changement.

Si des UDFs échouent parce qu’elles consomment trop de mémoire, envisagez d’utiliser Entrepôts optimisés par Snowpark.

Durée

Évitez les algorithmes qui prennent beaucoup de temps par appel.

Si une UDF prend trop de temps pour se terminer, Snowflake arrête l’instruction SQL et renvoie une erreur à l’utilisateur. Cela limite l’impact et le coût d’erreurs telles que les boucles infinies.

Conception du module

Lorsqu’une instruction SQL appelle votre UDF Python, Snowflake appelle une fonction Python que vous avez écrite. Votre fonction Python est appelée « fonction de gestionnaire » ou « gestionnaire » en abrégé. Le gestionnaire est une fonction mise en œuvre dans un module fourni par l’utilisateur.

Comme toute fonction Python, votre fonction doit être déclarée comme faisant partie d’un module.

Le gestionnaire est appelé une fois pour chaque ligne transmise à l’UDF Python. Le module qui contient la fonction n’est pas réimporté pour chaque ligne. Snowflake peut appeler la fonction de gestion du même module plus d’une fois.

Pour optimiser l’exécution de votre code, Snowflake part du principe que l’initialisation peut être lente, alors que l’exécution de la fonction du gestionnaire est rapide. Snowflake fixe un délai plus long pour l’exécution de l’initialisation (y compris le temps de chargement de votre UDF et le temps nécessaire à l’initialisation du module) que pour l’exécution du gestionnaire (le temps d’appel de votre gestionnaire avec une ligne d’entrée).

Des informations supplémentaires sur la conception du module se trouvent dans Création d’UDFs Python.

Optimisation de l’initialisation et contrôle de l’état global dans des UDFs scalaires

La plupart des UDFs scalaires doivent suivre les directives ci-dessous :

  • Si vous devez initialiser un état partagé qui ne change pas d’une ligne à l’autre, initialisez-le dans le module plutôt que dans la fonction du gestionnaire.

  • Rédigez votre fonction de gestionnaire de manière à la sécuriser des threads.

  • Évitez de stocker et de partager l’état dynamique entre les lignes.

Si vos UDF ne peuvent pas suivre ces directives, sachez que Snowflake s’attend à ce que les UDFs scalaires soient traitées indépendamment. S’appuyer sur l’état partagé entre les invocations peut entraîner un comportement inattendu, car le système peut traiter les lignes dans n’importe quel ordre et répartir ces invocations sur plusieurs instances. En outre, il peut y avoir plusieurs exécutions de la même fonction de gestionnaire dans le même interpréteur Python sur plusieurs threads.

Les UDFs doivent éviter de s’appuyer sur un état partagé entre les appels vers la fonction du gestionnaire. Cependant, il existe deux situations dans lesquelles vous pourriez vouloir qu’une UDF stocke un état partagé :

  • Du code qui contient une logique d’initialisation coûteuse que vous ne voulez pas répéter pour chaque ligne.

  • Du code qui exploite l’état partagé entre les lignes, comme un cache.

Lorsqu’il est nécessaire de maintenir un état global qui sera partagé entre les invocations de gestionnaires, vous devez protéger l’état global contre les courses de données en utilisant les primitives de synchronisation décrites dans threading - Thread-based parallelism.

Optimisation de l’échelle et des performances

Utiliser des UDFs Python vectorisées avec les bibliothèques de science des données

Lorsque votre code utilisera des bibliothèques de machine learning ou de Data Science, utilisez les UDFs Python vectorisées pour définir des fonctions Python qui reçoivent des lignes d’entrée par lots sur lesquels ces bibliothèques sont optimisées pour fonctionner.

Pour plus d’informations, voir UDFs Python vectorisées.

Écrire des gestionnaires UDF à thread unique

Écrivez des gestionnaires UDF qui sont à thread unique. Snowflake se chargera de partitionner les données et de mettre à l’échelle les UDF sur les ressources informatiques de l’entrepôt virtuel.

Placer l’initialisation coûteuse dans le module

Placez le code d’initialisation coûteuse dans la portée du module. Là, il sera exécuté une fois lors de l’initialisation de l’UDF. Évitez de réexécuter le code d’initialisation coûteuse à chaque invocation du gestionnaire d’UDF.

Gestion des erreurs

Une fonction Python utilisée comme UDF peut utiliser les techniques normales de gestion des exceptions de Python pour récupérer les erreurs dans la fonction.

Si une exception se produit à l’intérieur de la fonction et n’est pas récupérée par celle-ci, Snowflake génère une erreur qui inclut la trace de la pile pour l’exception. Lorsque l’enregistrement des exceptions non gérées est activé, Snowflake enregistre les données relatives aux exceptions non gérées dans une table d’événements.

Vous pouvez explicitement lancer une erreur sans la capturer afin de terminer la requête et de produire une erreur SQL. Par exemple :

if (x < 0):
  raise ValueError("x must be non-negative.");
Copy

Lors du débogage, vous pouvez inclure des valeurs dans le texte du message d’erreur SQL. Pour ce faire, placez un corps de fonction Python entier dans un bloc try-catch ; ajoutez des valeurs d’argument au message de l’erreur capturée ; et lancez une erreur avec le message étendu. Pour éviter de révéler des données sensibles, supprimez les valeurs des arguments avant déploiement dans un environnement de production.

Respecter les bonnes pratiques de sécurité

Pour vous assurer que votre gestionnaire fonctionne de manière sécurisée, consultez les meilleures pratiques décrites dans Pratiques de sécurité pour UDFs et procédures.