Skip to main content
Lorsque ClickHouse exécute plusieurs requêtes simultanément, elles utilisent des ressources partagées (CPU, mémoire et E/S). Des contraintes et des politiques d’ordonnancement peuvent être appliquées pour réguler l’utilisation et le partage des ressources entre différents workloads. Une hiérarchie d’ordonnancement commune peut être configurée pour toutes les ressources. La racine de cette hiérarchie représente les ressources partagées, tandis que les feuilles correspondent à des workloads spécifiques, contenant les demandes de ressources et les allocations propres à certaines requêtes et activités en arrière-plan.

Ressources

Par défaut, l’ordonnancement des charges de travail est désactivé. Pour l’activer, vous devez créer des ressources qui seront utilisées pour la planification, ainsi qu’au moins un workload. Toutes les ressources sont indépendantes et peuvent être combinées librement. Pour activer la planification CPU, vous devez créer une ressource CPU pour les threads MASTER ou WORKER (voir planification CPU pour plus de détails) :
Pour activer la réservation de mémoire pour les workloads, vous devez créer la ressource MEMORY (voir Réservations de mémoire pour plus de détails) :
Pour activer l’ordonnancement des slots de requête, vous devez créer une ressource QUERY (voir Ordonnancement des slots de requête pour en savoir plus) :
Pour activer l’ordonnancement des E/S pour un disque spécifique, vous devez créer des ressources en lecture et en écriture pour les accès READ et WRITE :
Une ressource peut être utilisée avec un nombre quelconque de disques, en READ, en WRITE, ou à la fois en READ et en WRITE. Il existe une syntaxe permettant d’utiliser une ressource pour tous les disques :
Les ressources sont classées par mode de partage :
  • 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

ClickHouse fournit une syntaxe SQL pratique pour définir la hiérarchie d’ordonnancement. Toutes les ressources sont réparties dans une hiérarchie WORKLOAD commune. Les règles de répartition peuvent différer sur certains points selon les ressources, mais la hiérarchie reste la même. Chaque WORKLOAD comporte les nœuds d’ordonnancement nécessaires pour chaque ressource. Un workload enfant peut être créé au sein de n’importe quel workload pour construire la hiérarchie. ClickHouse n’impose aucune structure spécifique ou prédéfinie pour la hiérarchie des workloads. Voici un exemple de hiérarchie qui répartit toutes les ressources entre les workloads “user” et “system”, avec une garantie respective de 90 % et 10 %. Notez que les poids définis pour les workloads sont utilisés pour l’équité max-min et ne fournissent donc qu’une garantie minimale au mieux (et non une limite ou un quota maximal). L’ordonnancement est effectué indépendamment sur chaque hôte et, par conséquent, les limites définies par les paramètres 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” :
Le nom d’un workload feuille sans enfants peut être utilisé dans les paramètres de requête 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 de max_cpus.
  • max_memory - la limite de la mémoire totale réservée à ce workload.
Toutes les limites spécifiées via les workload settings sont indépendantes pour chaque ressource. Par exemple, un workload avec 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 :
Notez également qu’un workload ou une ressource ne peut pas être supprimé s’il est référencé par un autre workload. Pour mettre à jour la définition d’un workload, utilisez la requête 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

Les requêtes peuvent être marquées à l’aide du paramètre 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.
Le paramètre de requête workload ne peut faire référence qu’à des workloads terminaux (c’est-à-dire des workloads sans enfants).
Il est possible d’attribuer un paramètre 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

Pour activer l’ordonnancement du CPU pour les workloads, créez une ressource CPU et définissez une limite pour le nombre de threads concurrents :
Lorsque le serveur ClickHouse exécute de nombreuses requêtes concurrentes avec plusieurs threads et que tous les slots CPU sont utilisés, l’état de surcharge est atteint. Dans cet état, chaque slot CPU libéré est réattribué au workload approprié conformément aux politiques d’ordonnancement. Pour les requêtes partageant le même workload, les slots sont alloués en round robin. Pour les requêtes appartenant à des workloads distincts, les slots sont alloués selon les poids, priorités et limites spécifiés pour les workloads. Le temps CPU est consommé par les threads lorsqu’ils ne sont pas bloqués et travaillent sur des tâches gourmandes en CPU. À des fins d’ordonnancement, on distingue deux types de threads :
  • 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.
Il peut être souhaitable d’utiliser des ressources distinctes pour les master threads et les worker threads afin d’obtenir une meilleure réactivité. Un grand nombre de worker threads peut facilement monopoliser les ressources CPU lorsque des valeurs élevées du paramètre de requête 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 :
Cela créera des limites distinctes pour les threads principaux et les threads workers. Même si les 100 slots CPU des workers sont tous occupés, les nouvelles requêtes ne seront pas bloquées tant qu’il restera des slots CPU principaux disponibles. Elles commenceront à s’exécuter avec un seul thread. Ensuite, si des slots CPU workers deviennent disponibles, ces requêtes pourront monter en charge et lancer leurs threads workers. En revanche, une telle approche ne lie pas le nombre total de slots au nombre de processeurs CPU, et l’exécution d’un trop grand nombre de threads concurrents dégradera les performances. Limiter la concurrence des threads principaux ne limitera pas le nombre de requêtes concurrentes. Des slots CPU peuvent être libérés en cours d’exécution d’une requête, puis réattribués à d’autres threads. Par exemple, 4 requêtes concurrentes avec une limite de 2 threads principaux concurrents peuvent toutes s’exécuter en parallèle. Dans ce cas, chaque requête recevra 50 % d’un processeur CPU. Il faut utiliser une logique distincte pour limiter le nombre de requêtes concurrentes, et cela n’est pas actuellement pris en charge pour les workloads. Des limites distinctes de concurrence des threads peuvent être utilisées pour les workloads :
Cet exemple de configuration fournit des pools de slots CPU distincts pour admin et production. Le pool de production est partagé entre l’analytics et l’ingestion. En outre, si le pool de production est surchargé, 9 slots libérés sur 10 seront réattribués aux requêtes analytiques si nécessaire. Les requêtes d’ingestion ne recevraient alors qu’1 slot sur 10 pendant les périodes de surcharge. Cela peut améliorer la latence des requêtes orientées utilisateur. L’analytics a sa propre limite de 60 threads concurrents, ce qui laisse toujours au moins 40 threads pour prendre en charge l’ingestion. En l’absence de surcharge, l’ingestion peut utiliser les 100 threads. Pour exclure une requête de l’ordonnancement CPU, définissez le paramètre de requête use_concurrency_control sur 0. L’ordonnancement CPU n’est pas encore pris en charge pour les merge et les mutations. Pour garantir des allocations équitables entre les workloads, il est nécessaire d’effectuer une préemption et une réduction dynamique pendant l’exécution de la requête. La préemption est activée avec le paramètre serveur 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.
L’ordonnancement des slots permet de contrôler la concurrence des requêtes, mais ne garantit pas une allocation équitable du temps CPU, sauf si le paramètre serveur cpu_slot_preemption est défini sur true ; sinon, l’équité est assurée en fonction du nombre d’allocations de slots CPU entre les workloads concurrents. Cela n’implique pas une quantité égale de secondes CPU, car sans préemption, un slot CPU peut être conservé indéfiniment. Un thread acquiert un slot au début et le libère une fois le travail terminé.
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

Il existe deux façons de contrôler la consommation de CPU d’une charge de travail :
  • Limite du nombre de threads : max_concurrent_threads et max_concurrent_threads_ratio_to_cores
  • Bridage du CPU : max_cpus, max_cpu_share et max_burst_cpu_seconds
Les paramètres de bridage du CPU ne sont actifs que si le paramètre serveur cpu_slot_preemption est activé, et sont ignorés sinon.
La première permet de contrôler dynamiquement le nombre de threads lancés pour une query, en fonction de la charge actuelle du serveur. En pratique, elle abaisse la limite imposée par le paramètre de query 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.
Considérons un exemple de configuration combinant à la fois le bridage du CPU et les limites du nombre de threads :
Ici, nous limitons le nombre total de threads pour toutes les requêtes à 2 fois le nombre de CPU disponibles. Le workload Admin est limité à exactement deux threads au maximum, quel que soit le nombre de CPU disponibles. Admin a une priorité de -1 (inférieure à la valeur par défaut 0) et obtient en premier un slot CPU si nécessaire. Lorsque l’admin n’exécute pas de requêtes, les ressources CPU sont réparties entre les workloads de production et de développement. Les parts garanties de temps CPU sont basées sur des poids (4 pour 1) : au moins 80 % vont à la production (si nécessaire) et au moins 20 % au développement (si nécessaire). Tandis que les poids constituent des garanties, le throttling CPU définit des limites : la production n’est pas limitée et peut consommer 100 %, tandis que le développement a une limite de 30 %, qui s’applique même s’il n’y a pas de requêtes provenant d’autres workloads. Le workload de production n’est pas une feuille ; ses ressources sont donc réparties entre analytics et ingestion selon des poids (3 pour 1). Cela signifie qu’analytics bénéficie d’une garantie d’au moins 0.8 * 0.75 = 60 % et, sur la base de 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.
Pour activer les réservations de mémoire pour les workloads, créez une ressource MEMORY RESERVATION et définissez au moins une limite pour la mémoire totale réservée à l’aide des paramètres de workload :
ClickHouse suit les allocations mémoire de toutes les requêtes et activités en arrière-plan. Le nombre d’octets alloués est agrégé dans la hiérarchie d’ordonnancement jusqu’à la racine. Chaque requête possède une allocation associée dans le workload feuille auquel elle appartient. Si une requête a le paramètre 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
Le paramètre de workload 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 :
Dans cet exemple, la mémoire totale réservée par toutes les requêtes et activités en arrière-plan ne peut pas dépasser 10 GiB. Le workload système bénéficie d’une garantie d’au moins 1 GiB (10 % de 10 GiB), tandis que le workload utilisateur bénéficie d’une garantie d’au moins 9 GiB (90 % de 10 GiB). Au sein du workload utilisateur, les workloads production et staging se partagent la mémoire selon des poids de 3 pour 1, avec une préséance égale de 1. Le workload de test a une préséance de 2, inférieure à celle de production et de staging. Par conséquent, le workload de test ne peut utiliser que la mémoire non utilisée par production et staging. En cas de pression sur la mémoire, les allocations du workload de test seront évincées en premier. Ensuite, s’il faut libérer davantage de mémoire, les allocations du workload staging seront évincées avant celles du workload production si elles dépassent leurs garanties. Notez que les requêtes en attente dans production et staging peuvent évincer des allocations en cours d’exécution dans le workload de test afin de libérer de la mémoire, mais elles ne peuvent pas s’évincer mutuellement, car elles ont la même préséance. En cas de pression sur la mémoire, elles attendront dans des files d’attente, ce qui permet au système d’éviter les erreurs MEMORY_LIMIT_EXCEEDED dues à un trop grand nombre de requêtes exécutées simultanément. Notez que le workload système a une préséance de 0 (par défaut), supérieure à celle des workloads production, staging et test, mais qu’il ne s’agit pas de workloads frères. Leur plus proche ancêtre commun est le workload all, dont les deux enfants ont la même préséance. Ainsi, un workload système en attente ne peut en évincer aucun, et inversement. Cela garantit que les activités système ne peuvent pas être facilement évincées.

Ordonnancement des slots de requête

Pour activer l’ordonnancement des slots de requête pour les workloads, créez la ressource QUERY et définissez une limite sur le nombre de requêtes simultanées ou de requêtes par seconde :
Le paramètre de workload 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

Les définitions de tous les workloads et de toutes les ressources, sous la forme de requêtes 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

En plus des définitions basées sur SQL, les charges de travail et les ressources peuvent être prédéfinies dans le fichier de configuration du serveur. Cela est utile dans les environnements cloud, où certaines restrictions sont imposées par l’infrastructure, tandis que d’autres limites peuvent être modifiées par les clients. Les entités définies par configuration ont priorité sur celles définies en SQL et ne peuvent pas être modifiées ni supprimées à l’aide de commandes SQL.

Format de la configuration

La configuration utilise la même syntaxe SQL que les instructions CREATE WORKLOAD et CREATE RESOURCE. Toutes les requêtes doivent être valides.

Recommandations d’utilisation

Pour les environnements cloud, une configuration typique peut inclure :
  1. Définir le workload racine et les ressources d’E/S réseau dans la configuration afin de fixer les limites de l’infrastructure
  2. Définir throw_on_unknown_workload pour faire respecter ces limites
  3. Créer un CREATE WORKLOAD default IN all pour appliquer automatiquement les limites à toutes les requêtes (puisque la valeur par défaut du paramètre de requête workload est ‘default’)
  4. Autoriser les utilisateurs à créer des workloads supplémentaires dans la hiérarchie configurée
Cela garantit que toutes les activités d’arrière-plan et les requêtes respectent les limites de l’infrastructure, tout en laissant une certaine souplesse pour les politiques d’ordonnancement propres aux utilisateurs. Un autre cas d’usage consiste à utiliser une configuration différente selon les nœuds d’un cluster hétérogène.

Accès strict aux ressources

Pour obliger toutes les requêtes à respecter les politiques d’ordonnancement des ressources, il existe un paramètre du serveur 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

Du point de vue du sous-système d’ordonnancement, chaque ressource correspond à une hiérarchie de nœuds d’ordonnancement. ClickHouse crée automatiquement tous les nœuds d’ordonnancement nécessaires à partir des définitions WORKLOAD et RESOURCE. Les nœuds d’ordonnancement sont des détails d’implémentation de bas niveau, accessibles dans la table system.scheduler.
Types de nœuds à temps partagé :
  • inflight_limit (contrainte) - bloque si le nombre de requêtes simultanément en vol dépasse max_requests, ou si leur coût total dépasse max_cost ; doit avoir un seul enfant.
  • bandwidth_limit (contrainte) - bloque si le débit actuel dépasse max_speed (0 signifie illimité) ou si la rafale dépasse max_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écifier weight (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écifier priority (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.
Types de nœuds à espace partagé :
  • 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écifier weight (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écifier precedence (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

Une autre façon d’indiquer quels disques sont utilisés par une ressource consiste à utiliser le 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 :
Notez que les options de configuration du serveur priment sur la méthode SQL de définition des ressources.

Voir aussi

Dernière modification le 23 juillet 2026