- À l’aide de SQL
- À l’aide de la console et de votre propre code
- À l’aide de la Cloud API (Beta)
- À l’aide de Terraform (Beta)
Fonctions SQL définies par l’utilisateur
Les UDF SQL peuvent être créées à l’aide de l’instructionCREATE FUNCTION à partir d’une expression lambda.
Dans cet exemple, nous allons créer une simple fonction exécutable définie par l’utilisateur, isBusinessHours.
La fonction vérifiera si un timestamp donné correspond aux heures ouvrées habituelles et renverra true si c’est le cas, sinon false.
- Connectez-vous à Cloud Console et ouvrez la console SQL
- Écrivez la SQL query suivante pour créer la fonction
isBusinessHours:
- Exécutez ce qui suit pour tester l’UDF que vous venez de créer :
- Vous pouvez utiliser la commande
DROP FUNCTIONpour supprimer l’UDF que vous venez de créer :
- Les paramètres de session (définis via l’instruction
SET) ne sont pas transmis au contexte d’exécution des UDFs - Les paramètres du profil utilisateur ne sont pas hérités par les UDFs
- Les paramètres au niveau de la requête ne s’appliquent pas lors de l’exécution des UDFs
Fonctions définies par l’utilisateur exécutables
Les UDF exécutables exécutent votre code dans un processus isolé (sandbox), aux côtés de chaque réplique de votre service. Vous pouvez téléverser un script Python, qui s’exécute sur le runtimepython3.11, ou un binaire précompilé, qui s’exécute sur le runtime Native.
Dans cet exemple, nous allons créer la même fonction définie par l’utilisateur exécutable simple, isBusinessHours, qui vérifie si un timestamp donné se situe dans les heures ouvrées habituelles.
Nous l’avons précédemment créée en SQL, mais cette fois, nous allons la créer en Python et la configurer via la console.
1
Créez le fichier Python
Créez localement un nouveau fichier L’environnement d’exécution Python est
main.py :python3.11. Si votre script Python importe des paquets tiers, indiquez-les dans un fichier requirements.txt : ClickHouse Cloud les installera pour vous, pour les deux architectures du CPU sur lesquelles ClickHouse Cloud fonctionne (x86-64 et ARM64). Vous pouvez aussi intégrer les dépendances directement dans l’archive ZIP, mais vous devez alors y inclure les paquets mis en cache pour les deux architectures du CPU ; il est donc plus simple d’utiliser requirements.txt. Par exemple :ClickHouse Cloud s’attend à trouver
main.py dans le fichier zip que vous téléverserez via la console à l’étape suivante.
Si vous nommez le fichier autrement, une erreur se produira.2
Regrouper les dépendances et les fichiers locaux
Pour ce tutoriel, qui ne comporte ni dépendances ni fichiers supplémentaires, l’archive n’a besoin de contenir que Pour inclure des paquets de dépendances et tout autre fichier local (par exemple Vous pouvez faire référence au répertoire de base du path local de l’archive dans votre code Python à l’aide de Cela s’avère utile lorsque vous devez :
main.py :requirements.txt, des fichiers wheel, des fichiers de configuration ou des fichiers de données), placez-les dans le même répertoire que votre fichier main.py, puis exécutez zip depuis ce répertoire afin que main.py se trouve à la racine de l’archive :os.path.dirname(os.path.abspath(__file__)). Cette expression renvoie le path absolu du répertoire contenant votre fichier main.py dans l’archive ZIP, ce qui vous permet d’accéder aux autres fichiers qu’elle contient :- Accéder aux fichiers de configuration fournis avec votre UDF
- Charger des paquets wheel pour des dépendances personnalisées
- Référencer des scripts ou des fichiers de données supplémentaires
3
Créez une UDF depuis la console
- Depuis la page d’accueil de la Cloud console, cliquez sur le nom de votre organisation dans le menu en bas à gauche.
- Sélectionnez User-defined functions dans le menu.
- Sur la page des fonctions définies par l’utilisateur, cliquez sur Set up a UDF. Un panneau de configuration s’ouvre sur la droite de l’écran.
- Saisissez un nom de fonction. Pour cet exemple, utilisez
isBusinessHours. - Choisissez un type de fonction, Executable pool ou Executable :
- Executable pool : un pool de processus de longue durée est démarré une seule fois, puis réutilisé d’un bloc et d’une requête à l’autre. Recommandé pour la quasi-totalité des charges de travail.
- Executable : un nouveau processus est démarré pour chaque bloc de données traité. N’utilisez ce mode que si le processus ne doit conserver aucun état entre les appels.
- Sélectionnez un Runtime type. Pour cet exemple, conservez
python3.11. Pour téléverser plutôt un binaire précompilé, sélectionnez Native ; consultez Runtime natif. - Pour cet exemple, conservez les paramètres par défaut. Pour la liste complète des paramètres de configuration et de leurs valeurs par défaut dans Cloud, consultez Référence de configuration.
- Cliquez sur Browse File pour téléverser le fichier
.zipcréé au début de ce tutoriel. - Ajoutez un nouvel argument. Pour cet exemple, ajoutez un argument
timestampde typeDateTime. - Sélectionnez un type de retour. Pour cet exemple, sélectionnez
Bool. - Cliquez sur Create UDF. Une boîte de dialogue affiche l’état d’avancement de la construction.
- En cas de problème, le statut passe à error.
- Sinon, le statut passe de building à provisioning. Votre service doit être actif pour que le provisionnement puisse aboutir. S’il est en veille, cliquez sur Wake Up Service dans le panneau UDF details, à côté du nom du service.
- Une fois l’opération terminée, le statut passe à deployed.
4
Testez votre UDF
- revenez à la page d’accueil de la SQL Console en cliquant sur Settings - return to your service view en haut à gauche de la page
- cliquez sur SQL Console dans le menu de gauche
- saisissez la requête suivante :
5
Créer une nouvelle version
Pour modifier le code ou les paramètres d’une UDF, créez une nouvelle version. Les versions sont immuables. Le panneau Edit sert uniquement à gérer les services auxquels une UDF est assignée ; téléverser un fichier depuis ce panneau ne remplace pas le code déployé.
- Depuis la page d’accueil de la Cloud console, cliquez sur le nom de votre organisation dans le menu en bas à gauche.
- Sélectionnez User-defined functions dans le menu.
- Cliquez sur les trois points sous Actions pour l’UDF
isBusinessHours, puis cliquez sur Create new version - Téléversez une archive zip contenant le code modifié ou modifiez les paramètres, sélectionnez les services qui doivent recevoir la nouvelle version, puis cliquez sur Create new version
executable_pool, les processus persistants du pool continuent d’exécuter la version précédente tant que le pool n’a pas été rechargé. Utilisez l’action Reload UDF à côté du service dans le panneau de l’UDF. Elle exécute SYSTEM RELOAD FUNCTION ON CLUSTER default isBusinessHours sur ce service.Vous avez ajouté votre première fonction définie par l’utilisateur via la console, vérifié qu’elle s’exécute correctement et découvert comment en créer une nouvelle version si nécessaire.Runtime Native (binaires compilés)
Le runtime Native vous permet de téléverser un binaire précompilé au lieu d’un script Python. Définissez Type de runtime sur Native lors de la création de l’UDF (runtime: native dans l’API). Rien n’est installé pour vous dans le sandbox : tout ce dont le binaire a besoin doit être intégré lors de la compilation.
Actuellement pris en charge : Rust, Go, C++ et JavaScript compilé en binaire avec Bun. Bun a été testé avec les versions 1.4.0 et 1.4.2 ; le fonctionnement avec d’autres versions n’est pas garanti.
L’archive doit contenir un exécutable par architecture du CPU, chacun dans son propre dossier :
main et est lancé sans argument ; transmettez plutôt un mode ou une sous-commande sous la forme d’un argument de fonction ordinaire.
Les binaires liés statiquement sont vivement recommandés : ils ne dépendent pas des bibliothèques système de l’hôte, dont les appels système peuvent varier d’une version de serveur à l’autre, et la politique d’appels système du sandbox est optimisée pour eux. Les binaires liés dynamiquement ne fonctionnent que s’ils sont liés exclusivement à glibc et ont été compilés avec glibc 2.35 ou une version antérieure. Le JavaScript compilé avec Bun est toujours lié dynamiquement.
Les UDF natives ne peuvent pas disposer d’un accès réseau ; l’accès réseau sortant est réservé au runtime Python. Consultez Accès réseau.
Compilez le binaire pour les deux architectures. En Rust, ajoutez une fois pour toutes les deux cibles, puis compilez pour chacune d’elles :
amd64/main et arm64/main avant de compresser les dossiers au format zip.
Le wire protocol est identique à celui du runtime Python : le processus lit les arguments depuis stdin et écrit les résultats sur stdout dans le format configuré. Consultez Formats et en-têtes de fragments. Le dépôt llm-token-udf contient des UDF natives en Rust et en Go, accompagnées de scripts de construction.
Référence de configuration
Les paramètres suivants sont exposés dans la console et dans l’API. Les valeurs par défaut indiquées sont celles de ClickHouse Cloud et diffèrent par endroits de celles de la version open source.
Les paramètres open source suivants sont gérés par ClickHouse Cloud et ne peuvent pas être modifiés UDF par UDF :
command_termination_timeout, lifetime, stderr_reaction, check_exit_code et execute_direct. La valeur par défaut open source de pool_size est 16, contre 3 dans Cloud. La liste complète des paramètres open source figure dans Fonctions définies par l’utilisateur exécutables.
Les noms de fonction doivent respecter le motif ^[A-Za-z][A-Za-z0-9_]*$, sont uniques au sein de l’organisation et ne peuvent pas reprendre le nom d’une fonction intégrée de ClickHouse ; l’API rejette les noms réservés. Chaque argument doit porter un nom respectant le même motif ; l’API et Terraform l’exigent quel que soit le format. Les archives contenant des liens symboliques sont rejetées.
Formats et en-têtes de fragment
TabSeparated est le format par défaut et le plus simple. Il impose l’échappement des tabulations, des sauts de ligne et des antislashs des deux côtés. RowBinary prend en charge les données binaires sans risque et évite le formatage et l’analyse de texte côté ClickHouse ; privilégiez-le pour les chaînes de caractères volumineuses.
Lorsque Send chunk header est activé, ClickHouse écrit avant chaque fragment le nombre de lignes sous forme de texte décimal suivi de \n, puis les lignes dans le format configuré. Le processus doit lire exactement ce nombre de lignes, écrire autant de résultats, puis effectuer un seul vidage. Sans cette option, un processus qui lit un format orienté lignes tel que TabSeparated, RowBinary ou JSONEachRow et qui met sa sortie en tampon n’a aucun moyen de savoir où se termine un bloc, et la requête reste bloquée jusqu’à l’expiration du délai de lecture. Activez-la pour toute UDF de ce type qui n’effectue pas de vidage après chaque ligne. Le format Native intègre déjà les limites de bloc : une UDF Native peut donc lire un bloc, écrire un bloc et effectuer un vidage sans recourir à l’en-tête.
Accès réseau
L’accès réseau n’est disponible que pour le runtime Python ; il ne peut pas être activé pour les UDF natives. Les UDF Python créées dans la console sont générées avec l’accès réseau activé, et aucun paramètre de la console ne permet de le modifier. Dans l’API,sandboxType permet de choisir entre netenable, qui autorise les connexions sortantes, et basic, qui les interdit ; le Terraform provider propose le même choix via sandbox_type.
L’accès réseau autorise les connexions sortantes vers des endpoints accessibles sur l’Internet public. Les connexions vers des réseaux privés, des adresses link-local et des adresses internes sont bloquées. Le sandbox ne dispose d’aucune identity cloud : ni rôle d’instance, ni endpoint de métadonnées, ni variables d’environnement héritées. Une UDF ne dispose donc que des identifiants que vous lui fournissez. L’accès réseau est appliqué lors de la génération d’une version d’UDF : pour une version existante créée sans cet accès, il faut donc générer une nouvelle version.
Il n’existe pas encore de magasin de secrets pour les UDF. Incluez un fichier de configuration dans l’archive zip et lisez-le via un chemin relatif à __file__ ; ne placez jamais de clés dans le code SQL.
Limite de mémoire
Limite de mémoire définit la quantité maximale de mémoire, en MiB, dont dispose chaque processus du sandbox. Si elle n’est pas définie, la valeur par défaut du sandbox, soit 4 GiB, s’applique. La limite porte sur l’espace d’adressage virtuel, et non sur la mémoire résidente : les environnements d’exécution qui réservent de vastes plages d’adresses ont donc besoin d’une marge nettement supérieure à leur utilisation réelle. Go, par exemple, réserve bien plus de 1 GiB d’espace d’adressage au démarrage. Lorsqu’un processus dépasse la limite, l’allocation échoue au sein même du processus : une UDF Python se termine avec une trace d’appelsMemoryError, et la requête échoue en incluant cette sortie stderr dans le message d’erreur.
Fonctions déterministes et cache de requêtes
Par défaut, une UDF est considérée comme non déterministe. Une requête qui en appelle une avecuse_query_cache = 1 échoue avec l’erreur QUERY_CACHE_USED_WITH_NONDETERMINISTIC_FUNCTIONS, sauf si le paramètre query_cache_nondeterministic_function_handling est modifié.
N’activez déterministe que si la fonction renvoie toujours le même résultat pour les mêmes arguments. Le cache de requêtes peut alors stocker et servir les résultats des requêtes qui l’appellent. Les autres fonctions non déterministes présentes dans la même requête, telles que now, today ou rand, continuent d’empêcher la mise en cache avec le comportement par défaut throw ; avec query_cache_nondeterministic_function_handling = 'save', ces requêtes sont tout de même mises en cache, tandis qu’avec 'ignore', elles sont exécutées sans mise en cache.
Redémarrages du service
Supervision
À partir de ClickHouse 26.6, l’utilisation des ressources par les UDF exécutables est enregistrée dans huit compteurs, tous préfixés parExecutableUserDefinedFunction. Par requête, ils apparaissent sous forme d’entrées dans la colonne de type map ProfileEvents de system.query_log. À l’échelle du serveur, ils apparaissent sous forme de lignes dans system.events, avec des valeurs cumulées depuis le démarrage du serveur, ainsi que sous forme de colonnes ProfileEvent_<name> dans system.metric_log, avec des valeurs échantillonnées au fil du temps. Les suffixes des compteurs sont les suivants :
system.asynchronous_metrics contient ExecutableUserDefinedFunctionProcesses et ExecutableUserDefinedFunctionMemoryResidentBytes. Cette dernière correspond à la somme des valeurs VmRSS de tous les processus UDF actifs et constitue une borne supérieure, car les pages partagées sont comptabilisées une fois pour chaque processus.
system.query_log.memory_usage ne prend en compte que le processus du serveur ClickHouse et n’inclut pas les processus des UDF.Recommandations de performance
Utilisezexecutable_pool. Avec executable, un nouveau processus est lancé pour chaque bloc de données : le coût de démarrage est donc payé à chaque bloc.
Chaque invocation a un coût fixe. Si ExecutableUserDefinedFunctionInvocations est très élevé par rapport au nombre de lignes traitées, augmentez preferred_block_size_bytes pour cette requête afin que chaque bloc contienne davantage de lignes.
Une même requête utilise au maximum max_threads processus du pool simultanément ; une taille de pool supérieure à max_threads n’apporte donc aucun gain à cette requête. Commencez avec une taille réduite et augmentez-la lorsque ExecutableUserDefinedFunctionPoolWaitMicroseconds devient non négligeable.
Chaque ligne franchit une frontière de processus. Les UDF sont destinées à la logique que SQL ne peut pas exprimer, et non aux traitements triviaux par ligne qu’une fonction intégrée ou une UDF SQL peut prendre en charge.
Dépannage
Une UDF qui échoue fait échouer la requête avec le code d’erreur754 (UDF_EXECUTION_FAILED). La sortie stderr du processus enfant étant incluse dans le message, écrivez-y vos diagnostics et terminez avec un code de sortie non nul :
- La requête se bloque, puis échoue sur un timeout : le processus a mis sa sortie en tampon dans un format orienté lignes sans Send chunk header. Activez cette option et effectuez un vidage à chaque fragment, ou à chaque bloc si vous utilisez le format
Native. - Code de retour 1 avec une trace d’appels
MemoryError(Python) : la limite de mémoire a été dépassée. Augmentez la limite de mémoire ou réduisez la mémoire utilisée par processus. - Échecs intermittents avec le type
executablesous forte charge : passez àexecutable_pool. - Le statut de construction indique une erreur :
main.py(Python) ouamd64/mainetarm64/main(Native) sont absents de la racine du zip, ou l’archive contient des liens symboliques. - Impossible de créer la fonction : son nom entre en conflit avec celui d’une fonction intégrée.
Gérer les UDF avec l’API Cloud
Les opérations de cycle de vie de la console sont également accessibles par programmation via l’API ClickHouse Cloud. Les endpoints UDF vous permettent d’automatiser le téléversement d’archives sources, la création de fonctions et de versions, leur association à des services et leur détachement, ainsi que la suppression de versions et de fonctions. L’API ne propose aucune opération de rechargement : après avoir associé une nouvelle version d’une UDFexecutable_pool à un service, exécutez SYSTEM RELOAD FUNCTION ON CLUSTER default <name> en SQL sur ce service afin que les processus du pool prennent en compte la nouvelle version.
Ces endpoints sont en bêta et le contrat de l’API est susceptible d’évoluer.
- Créez une URL de téléversement afin d’obtenir une URL présignée pour téléverser un fichier
application/zip, puis téléversez-y votre archive ZIP. Chaque ID de téléversement ne peut être utilisé que pour une seule tentative de création ou de création de version ; demandez une nouvelle URL de téléversement lors d’une nouvelle tentative. - Créez l’UDF à partir de l’archive téléversée, en spécifiant le nom de la fonction, le runtime, les arguments et le type de retour.
- Associez l’UDF à un service. Si la version est omise, la dernière version prête est associée. Le service doit être en cours d’exécution ; les services inactifs peuvent d’abord être réactivés.
L’ensemble des endpoints :
Consultez la référence de l’API UDF pour les schémas de requête et de réponse.
Gérer les UDF avec Terraform
Le provider Terraform ClickHouse officiel inclut deux ressources pour gérer les UDF en tant qu’infrastructure as code :clickhouse_udfgère la fonction elle-même. Cette ressource accepte une archive ZIP contenant le code source de la fonction et publie une nouvelle version chaque fois que le hash de l’archive change, en attendant la fin de la construction.clickhouse_udf_attachmentassocie une version d’UDF à un service. Un service ne peut utiliser qu’une seule version d’une fonction à la fois. Vous pouvez épingler un numéro de version fixe ou référencerclickhouse_udf.<name>.versionpour mettre automatiquement à jour les services vers la dernière version.
Ces ressources sont disponibles à partir de la version 3.24.0 du provider. Elles sont au statut bêta et leur comportement peut changer dans les prochaines versions du provider.
isBusinessHours de l’exemple précédent :
clickhouse_udf accepte format, pool_size, max_command_execution_time, command_read_timeout, command_write_timeout, send_chunk_header, sandbox_type, sandbox_version, return_name et fail_on_build_error ; consultez la documentation de la ressource pour obtenir le schéma complet.
Pour tester une nouvelle version sur un service de développement avant son passage en production, faites en sorte qu’une association suive la dernière version et épinglez l’autre à une variable. L’association de développement suit automatiquement chaque nouvelle construction, tandis que l’association de production n’évolue que lorsque vous modifiez var.prod_udf_version :
Le provider n’expose pas encore
deterministic ni la limite de mémoire. Ces deux paramètres sont définis lors de la création d’une version ; les versions publiées par le provider ne peuvent donc pas en bénéficier. Pour les utiliser, créez plutôt ces versions via la console ou l’API.executable_pool, exécutez SYSTEM RELOAD FUNCTION ON CLUSTER default <name> sur le service après tout changement de version d’une association. La suppression d’une ressource clickhouse_udf supprime toutes les versions de la FUNCTION et la détache de tous les services.