Ressources
- Ressources partagées dans le temps (CPU, IO, slots de requête) - gèrent les requêtes de ressources mises en file d’attente aux feuilles de la hiérarchie d’ordonnancement. Les requêtes sont ordonnancées selon les politiques et les contraintes définies par la hiérarchie. Les requêtes de ressources sont créées lorsqu’une requête accède à la ressource correspondante. Par exemple, lorsqu’une requête lit des données sur le disque ou utilise le CPU pour le traitement, des requêtes de ressources sont créées pour chaque quantum de travail effectué ou pour chaque quantité d’octets envoyés ou reçus via un socket.
- Ressources partagées dans l’espace (Memory) - gèrent les allocations de ressources aux feuilles de la hiérarchie d’ordonnancement. Les allocations peuvent être actives ou en attente. Les allocations en attente sont bloquées jusqu’à ce que suffisamment d’espace soit libéré ou qu’une autre allocation soit évincée (interrompue de force). Les décisions sont fondées sur les limites et les politiques définies par la hiérarchie. Il existe une correspondance un à un entre les allocations et les requêtes (ou les activités en arrière-plan). Une allocation est créée lorsqu’une requête commence son exécution et elle est libérée lorsqu’elle se termine. Les allocations actives peuvent augmenter ou diminuer leur taille de manière dynamique.
Hiérarchie des workloads
max_* s’appliquent à chaque hôte. Le workload “user” subdivise ses ressources entre les workloads “development” et “production”, “production” disposant de 3 fois plus de ressources que “development” :
SETTINGS workload = 'name'. Consultez Workload markup pour plus de détails.
Pour personnaliser un workload, les paramètres suivants peuvent être utilisés :
priority- (time-shared uniquement) les workloads frères sont servis selon des valeurs statiques (une valeur plus faible signifie une priorité plus élevée). Détermine la préemption.precedence- (space-shared uniquement) les workloads frères sont admis selon des valeurs statiques (une valeur plus faible signifie une préséance plus élevée). Détermine l’éviction et l’admission.weight- les workloads frères ayant la même priorité statique ou la même préséance se partagent les ressources en fonction de leurs poids, de manière équitable. Affecte la préemption, l’éviction et l’admission.max_io_requests- la limite du nombre de requêtes d’E/S concurrentes dans ce workload.max_bytes_inflight- la limite du nombre total d’octets en cours de traitement pour les requêtes concurrentes dans ce workload.max_bytes_per_second- la limite du débit en octets en lecture ou en écriture de ce workload.max_burst_bytes- le nombre maximal d’octets pouvant être traités par le workload sans être bridé (pour chaque ressource indépendamment).max_concurrent_threads- la limite du nombre de threads pour les requêtes dans ce workload.max_concurrent_threads_ratio_to_cores- identique àmax_concurrent_threads, mais normalisé par rapport au nombre de cœurs CPU disponibles.max_cpus- la limite du nombre de cœurs CPU pour servir les requêtes dans ce workload.max_cpu_share- identique àmax_cpus, mais normalisé par rapport au nombre de cœurs CPU disponibles.max_burst_cpu_seconds- le nombre maximal de secondes CPU pouvant être consommées par le workload sans être bridé en raison demax_cpus.max_memory- la limite de la mémoire totale réservée à ce workload.
max_bytes_per_second = '10Mi' aura une limite de bande passante de 10 MB/s pour chaque ressource de lecture et d’écriture, indépendamment. Si une limite commune pour la lecture et l’écriture est requise, envisagez d’utiliser la même ressource pour l’accès READ et WRITE.
Il n’existe aucun moyen de spécifier des hiérarchies de workloads différentes pour différentes ressources. Mais il existe un moyen de spécifier une valeur de workload setting différente pour une ressource spécifique :
CREATE OR REPLACE WORKLOAD.
Les paramètres du workload sont convertis en un ensemble approprié de nœuds d’ordonnancement. Pour plus de détails sur les mécanismes sous-jacents, consultez la description des types et options des nœuds d’ordonnancement.
Marquage des workloads
workload pour distinguer différents workloads. Si workload n’est pas défini, la valeur “default” est utilisée. Notez que vous pouvez également spécifier une autre valeur à l’aide de profils de paramètres. Des contraintes sur les paramètres peuvent être utilisées pour rendre workload constant si vous souhaitez que toutes les requêtes d’un utilisateur soient marquées avec une valeur fixe du paramètre workload.
workload aux activités en arrière-plan. Les merges et les mutations utilisent respectivement les paramètres serveur merge_workload et mutation_workload. Ces valeurs peuvent également être redéfinies pour des tables spécifiques à l’aide des paramètres MergeTree merge_workload et mutation_workload.
Ordonnancement du CPU
- Master thread — le premier thread qui commence à travailler sur une requête ou une activité en arrière-plan comme une merge ou une mutation.
- Worker thread — les threads supplémentaires que le master peut créer pour travailler sur des tâches gourmandes en CPU.
max_threads sont utilisées. Les requêtes entrantes doivent alors se bloquer et attendre qu’un slot CPU se libère pour que leurs master threads puissent commencer l’exécution. Pour éviter cela, la configuration suivante peut être utilisée :
cpu_slot_preemption. S’il est activé, chaque thread renouvelle périodiquement son slot CPU (selon le paramètre serveur cpu_slot_quantum_ns). Un tel renouvellement peut bloquer l’exécution si le CPU est surchargé. Lorsque l’exécution reste bloquée pendant une durée prolongée (voir le paramètre serveur cpu_slot_preemption_timeout_ms), la requête réduit alors sa capacité, et le nombre de threads exécutés en parallèle diminue dynamiquement. Notez que l’équité du temps CPU est garantie entre les workloads, mais qu’entre les requêtes d’un même workload, elle peut ne pas être respectée dans certains cas limites.
La déclaration d’une ressource CPU désactive l’effet des paramètres
concurrent_threads_soft_limit_num et concurrent_threads_soft_limit_ratio_to_cores. À la place, le workload setting max_concurrent_threads est utilisé pour limiter le nombre de CPU alloués à une charge de travail spécifique. Pour retrouver le comportement précédent, créez uniquement la ressource WORKER THREAD, définissez max_concurrent_threads pour la charge de travail all sur la même valeur que concurrent_threads_soft_limit_num, puis utilisez le paramètre de requête workload = "all". Cette configuration correspond au paramètre concurrent_threads_scheduler défini sur la valeur “fair_round_robin”.Threads vs. CPU
- Limite du nombre de threads :
max_concurrent_threadsetmax_concurrent_threads_ratio_to_cores - Bridage du CPU :
max_cpus,max_cpu_shareetmax_burst_cpu_seconds
max_threads. La seconde bride la consommation de CPU de la charge de travail à l’aide de l’algorithme du seau à jetons. Elle n’affecte pas directement le nombre de threads, mais bride la consommation totale de CPU de tous les threads de la charge de travail.
Le bridage par seau à jetons avec max_cpus et max_burst_cpu_seconds signifie ce qui suit. Sur tout intervalle de delta secondes, la consommation totale de CPU de toutes les queries de la charge de travail ne doit pas dépasser max_cpus * delta + max_burst_cpu_seconds secondes CPU. Cela limite la consommation moyenne à max_cpus sur le long terme, mais cette limite peut être dépassée à court terme. Par exemple, avec max_burst_cpu_seconds = 60 et max_cpus=0.001, il est possible d’exécuter soit 1 thread pendant 60 secondes, soit 2 threads pendant 30 secondes, soit 60 threads pendant 1 seconde sans bridage. La valeur par défaut de max_burst_cpu_seconds est de 1 seconde. Des valeurs plus faibles peuvent entraîner une sous-utilisation des cœurs autorisés par max_cpus en présence de nombreux threads concurrents.
Lorsqu’il détient un slot CPU, un thread peut se trouver dans l’un de ces trois états principaux :
- Running: consomme effectivement des ressources CPU. Le temps passé dans cet état est pris en compte par le bridage du CPU.
- Ready: attend qu’un CPU devienne disponible. Le temps passé dans cet état n’est pas pris en compte par le bridage du CPU.
- Blocked: effectue des opérations d’IO ou d’autres appels système bloquants (par exemple, en attente d’un mutex). Le temps passé dans cet état n’est pas pris en compte par le bridage du CPU.
max_cpu_share, d’une limite de 70 % des ressources CPU totales. Quant à l’ingestion, si elle conserve une garantie d’au moins 0.8 * 0.25 = 20 %, elle n’a pas de limite supérieure.
Si vous souhaitez maximiser l’utilisation du CPU sur votre serveur ClickHouse, évitez d’utiliser
max_cpus et max_cpu_share pour le workload racine all. Définissez plutôt une valeur plus élevée pour max_concurrent_threads. Par exemple, sur un système avec 8 CPU, définissez max_concurrent_threads = 16. Cela permet à 8 threads d’exécuter des tâches CPU pendant que 8 autres threads peuvent gérer des opérations d’E/S. Des threads supplémentaires créeront une pression sur le CPU, ce qui garantit l’application des règles d’ordonnancement. À l’inverse, définir max_cpus = 8 ne créera jamais de pression sur le CPU, car le serveur ne peut pas dépasser les 8 CPU disponibles.Réservations de mémoire
La planification des réservations de mémoire est expérimentale. Elle ne prend effet que lorsqu’une ressource
MEMORY RESERVATION existe, et sa syntaxe SQL ainsi que son comportement peuvent évoluer dans les versions futures. Elle n’est pas encore prise en charge pour les merges et les mutations, et l’éviction d’une requête en cours s’effectue au mieux : elle prend effet au prochain point de synchronisation mémoire de la requête, et non instantanément.reserve_memory supérieur à zéro, l’allocation est alors créée à l’état en attente. Une allocation en attente réserve la quantité de mémoire demandée dans la hiérarchie des workloads. S’il n’y a pas assez de mémoire disponible, l’allocation reste en attente jusqu’à ce qu’une quantité suffisante de mémoire soit libérée ou que d’autres allocations soient évincées (tuées). Lorsque l’allocation est acceptée, elle passe à l’état d’exécution. Une allocation en cours d’exécution peut augmenter ou diminuer dynamiquement en taille en fonction de la consommation mémoire de la requête. Le cycle de vie d’une allocation peut être représenté par le diagramme d’état suivant :
Les allocations en attente d’un workload feuille sont admises selon l’ordre FIFO. Lorsque plusieurs workloads ont des allocations en attente, elles sont admises selon les paramètres de préséance et de pondération. Les workloads de préséance plus élevée sont servis en premier. Les workloads frères ayant la même préséance se partagent la mémoire selon leurs pondérations, de façon équitable selon le principe max-min, ce qui signifie que le workload dont l’utilisation mémoire normalisée est la plus faible (utilisation actuelle plus augmentation demandée, divisée par la pondération) est servi en premier. La logique inverse s’applique lors de l’éviction. Lorsqu’il faut libérer de la mémoire, les workloads de préséance plus faible et dont l’utilisation mémoire normalisée est plus élevée sont évincés en premier.
Notez que les ressources partagées dans le temps utilisent la priorité, tandis que les ressources partagées dans l’espace utilisent la préséance. Ce sont des paramètres indépendants, qui peuvent donc être définis sur des valeurs différentes. Une priorité plus élevée implique une préemption non destructive (retard ou limitation), tandis qu’une préséance plus élevée peut impliquer une éviction destructive (arrêt avec une erreur). Un workload peut avoir une priorité élevée pour l’ordonnancement CPU, tout en conservant la même préséance pour la réservation mémoire afin d’éviter d’évincer d’autres workloads et de perdre le travail qu’ils ont déjà effectué.
Tout workload avec une limite max_memory garantit que la mémoire totale allouée dans son sous-arbre ne dépasse pas cette limite. Si une allocation en attente ou en cours d’augmentation dépasse cette limite, une procédure d’éviction est lancée pour libérer de la mémoire. La procédure d’éviction sélectionne une victime à tuer. Le workload correspondant au plus proche ancêtre commun du killer et de la victime empêche l’éviction dans les situations suivantes :
- Une allocation en attente ne peut pas évincer des allocations en cours dans le même workload. (Les workloads du killer et de la victime coïncident).
- Une allocation en attente de préséance plus faible ne tue jamais un workload de préséance plus élevée.
- Une allocation en attente ne peut pas tuer une allocation de même préséance. Notez que des allocations en cours de même préséance peuvent s’évincer mutuellement en fonction de l’utilisation mémoire normalisée. Si l’éviction est empêchée ou ne libère pas assez de mémoire, la nouvelle allocation est bloquée jusqu’à ce qu’une quantité suffisante de mémoire soit libérée. Ces règles permettent la mise en file d’attente des requêtes excessives en fonction de la pression mémoire et offrent un moyen pratique d’éviter les erreurs MEMORY_LIMIT_EXCEEDED.
Les limites de workload sont indépendantes des autres moyens de limiter la consommation de mémoire, comme le paramètre de requête max_memory_usage. Elles peuvent être utilisées ensemble pour mieux contrôler la consommation de mémoire. Il est possible de définir des limites mémoire indépendantes en fonction des utilisateurs (et non des workloads). Cette approche est moins flexible et n’offre pas de fonctionnalités comme la réservation de mémoire et la mise en file d’attente des requêtes en attente. Voir Memory overcommit
max_waiting_queries limite le nombre d’allocations en attente pour le workload. Lorsque la limite est atteinte, le serveur renvoie une erreur SERVER_OVERLOADED. Notez que max_waiting_queries n’est pas hérité par les workloads enfants et n’a de sens que pour les workloads feuille.
L’ordonnancement de la réservation de mémoire n’est pas encore pris en charge pour les fusions et les mutations.
Seules les requêtes dont le paramètre reserve_memory est supérieur à zéro peuvent être bloquées dans l’attente d’une réservation de mémoire. Cependant, les requêtes pour lesquelles reserve_memory vaut zéro sont elles aussi prises en compte dans l’empreinte mémoire de leur workload, et elles peuvent être évincées si nécessaire afin de libérer de la mémoire pour d’autres allocations en attente ou en augmentation. Les requêtes sans marquage de workload approprié ne sont pas soumises à l’ordonnancement de la réservation de mémoire et ne peuvent pas être évincées par le planificateur.
Pour fournir à une requête une réservation de mémoire non élastique, définissez les paramètres de requête reserve_memory et max_memory_usage à la même valeur. Dans ce cas, la requête réservera une quantité fixe de mémoire et ne pourra pas augmenter dynamiquement son allocation. Notez qu’une réservation de mémoire élastique peut être augmentée au-delà de reserve_memory, jusqu’à max_memory_usage, sans être arrêtée, sauf en cas de pression mémoire. En revanche, elle ne peut pas être réduite en dessous de reserve_memory, même lorsque la consommation réelle est inférieure.
Prenons un exemple de configuration :
Ordonnancement des slots de requête
max_concurrent_queries limite le nombre de requêtes concurrentes pouvant s’exécuter simultanément pour un workload donné. Il s’agit de l’équivalent du paramètre de requête max_concurrent_queries_for_all_users et du paramètre du serveur max_concurrent_queries. Les requêtes async insert et certaines requêtes spécifiques, comme KILL, ne sont pas comptabilisées dans cette limite.
Les paramètres de workload max_queries_per_second et max_burst_queries limitent le nombre de requêtes pour le workload à l’aide d’un mécanisme de limitation de débit de type token bucket. Cela garantit que, sur tout intervalle de temps T, pas plus de max_queries_per_second * T + max_burst_queries nouvelles requêtes ne commenceront à s’exécuter.
Le paramètre de workload max_waiting_queries limite le nombre de requêtes en attente pour le workload. Lorsque la limite est atteinte, le serveur renvoie une erreur SERVER_OVERLOADED. Notez que max_waiting_queries n’est pas hérité par les workloads enfants et n’a de sens que pour les workloads feuilles.
Les requêtes bloquées attendent indéfiniment et n’apparaissent pas dans
SHOW PROCESSLIST tant que toutes les contraintes ne sont pas satisfaites.Stockage des workloads et des ressources
CREATE WORKLOAD et CREATE RESOURCE, sont stockées de manière persistante soit sur le disque dans workload_path, soit dans ZooKeeper dans workload_zookeeper_path. Le stockage dans ZooKeeper est recommandé pour garantir la cohérence entre les nœuds. Sinon, la clause ON CLUSTER peut être utilisée avec le stockage sur disque.
Charges de travail et ressources définies par configuration
Format de la configuration
CREATE WORKLOAD et CREATE RESOURCE. Toutes les requêtes doivent être valides.
Recommandations d’utilisation
- Définir le workload racine et les ressources d’E/S réseau dans la configuration afin de fixer les limites de l’infrastructure
- Définir
throw_on_unknown_workloadpour faire respecter ces limites - Créer un
CREATE WORKLOAD default IN allpour appliquer automatiquement les limites à toutes les requêtes (puisque la valeur par défaut du paramètre de requêteworkloadest ‘default’) - Autoriser les utilisateurs à créer des workloads supplémentaires dans la hiérarchie configurée
Accès strict aux ressources
throw_on_unknown_workload. S’il est défini sur true, chaque requête doit utiliser un paramètre de requête workload valide, faute de quoi l’exception RESOURCE_ACCESS_DENIED est levée. S’il est défini sur false, une telle requête n’utilise pas l’ordonnanceur de ressources, c’est-à-dire qu’elle bénéficie d’un accès illimité à n’importe quelle RESOURCE. Le paramètre de requête ‘use_concurrency_control = 0’ permet à une requête de contourner l’ordonnanceur CPU et d’obtenir un accès illimité au CPU. Pour imposer l’ordonnancement CPU, créez une contrainte de paramètre afin que ‘use_concurrency_control’ reste une valeur constante en lecture seule.
Ne définissez pas
throw_on_unknown_workload sur true tant que CREATE WORKLOAD default n’a pas été exécuté. Cela peut entraîner des problèmes au démarrage du serveur si une requête sans paramètre workload explicite est exécutée pendant le démarrage.Hiérarchie des nœuds d’ordonnancement
inflight_limit(contrainte) - bloque si le nombre de requêtes simultanément en vol dépassemax_requests, ou si leur coût total dépassemax_cost; doit avoir un seul enfant.bandwidth_limit(contrainte) - bloque si le débit actuel dépassemax_speed(0 signifie illimité) ou si la rafale dépassemax_burst(par défaut, égal àmax_speed) ; doit avoir un seul enfant.fair(politique) - sélectionne la prochaine requête à traiter dans l’un de ses nœuds enfants selon l’équité max-min ; les nœuds enfants peuvent spécifierweight(la valeur par défaut est 1).priority(politique) - sélectionne la prochaine requête à traiter dans l’un de ses nœuds enfants selon des priorités statiques (une valeur plus faible signifie une priorité plus élevée) ; les nœuds enfants doivent spécifierpriority(la valeur par défaut est 0).fifo(file d’attente) - feuille de la hiérarchie capable de contenir des requêtes qui dépassent la capacité des ressources.
limit- garantit que l’allocation totale de l’enfant ne dépasse jamais une limite et déclenche, si nécessaire, une procédure d’éviction dans un sous-arbre ; doit avoir un seul enfant.fair_allocation- applique l’éviction selon l’équité max-min ; une allocation en attente n’évince jamais une allocation en cours d’exécution ; les nœuds enfants peuvent spécifierweight(la valeur par défaut est 1).precedence_allocation- applique l’éviction selon une précédence statique (une valeur plus faible signifie une précédence plus élevée) ; une allocation en attente de précédence plus élevée évince les allocations de précédence plus faible ; les nœuds enfants doivent spécifierprecedence(la valeur par défaut est 0).queue- feuille de la hiérarchie capable de contenir des allocations en cours d’exécution et en attente.
Configuration XML obsolète
storage_configuration du serveur :
Pour activer la planification des E/S pour un disque spécifique, vous devez spécifier read_resource et/ou write_resource dans la configuration de stockage. Cela indique à ClickHouse quelle ressource utiliser pour chaque requête de lecture et d’écriture sur le disque concerné. Les ressources de lecture et d’écriture peuvent faire référence au même nom de ressource, ce qui est utile pour les SSD locaux ou les HDD. Plusieurs disques différents peuvent également faire référence à la même ressource, ce qui est utile pour les disques distants si vous souhaitez permettre une répartition équitable de la bande passante réseau entre, par exemple, les workloads “production” et “development”.
Exemple :
Voir aussi
- system.scheduler
- system.workloads
- system.resources
- merge_workload paramètre de MergeTree
- merge_workload paramètre global du serveur
- mutation_workload paramètre de MergeTree
- mutation_workload paramètre global du serveur
- workload_path paramètre global du serveur
- workload_zookeeper_path paramètre global du serveur
- cpu_slot_preemption paramètre global du serveur
- cpu_slot_quantum_ns paramètre global du serveur
- cpu_slot_preemption_timeout_ms paramètre global du serveur