0

Planification dans Adaptive Server

twitterlinkedinmail

Un petit post sur la planification de tâches sur Adaptive Server.

Globalement, ASE utilise des threads pour supporter toutes les tâches qu’il exécute: requêtes, IO réseau ou disque, connexions utilisateur, tâches internes… en conjonction avec son propre planificateur de tâche.

Le rôle du planificateur de tâche sur tout système est de partager le temps d’un CPU entre plusieurs unités d’exécution (tâches). Il existe deux grandes familles de planificateurs, les préemptifs, et les coopératifs.

Le planificateur préemptif a le pouvoir de planifier et d’interrompre les tâches selon des règles qui lui sont propres et sans tenir compte du type de tâche en cours d’exécution. One size fits all, comme on dit. Ce mécanisme, qui garantit qu’aucune tâche ne va monopoliser les ressources CPU, permet à chaque programme de pouvoir avoir la main sur la CPU à un moment où un autre. Sur la plupart des OS (linux, UNIX System V et Windows NT à partir de la 4.0), le planificateur alloue au thread un bail d’exécution ou un quantième. Si la tâche n’a pas terminé son travail dans le temps imparti, elle est interrompue. Lorsqu’il interrompt la tâche en cours, le planificateur sauvegarde son contexte dans un coin (c’est à dire les structures process + thread qui la représentent en mémoire), et planifie une nouvelle tâche sur la CPU. Ce qu’on appelle un changement de contexte.

Le planificateur coopératif, lui, va au contraire laisser à la tâche le soin de se terminer toute seule. C’est là la plus grosse différence, il n’interrompt pas une tâche qui n’a pas terminé son travail. C’est à la charge de la tâche de rendre la main pour laisser de la place aux autres, c’est donc à elle de prévoir un mécanisme de relâche (to yield = “céder”). Windows 3.1 employait un scheduler coopératif, les Windows 98 et 95 aussi pour assurer la compatibilité avec les programmes 16 bits.

Maintenant, on imagine facilement le risque d’employer un tel système de planification: une tâche qui part en vrille ne libèrera jamais la CPU, ça n’a donc plus été employé pour faire des operating systems.

Micheal Stonebraker avait publié un article de quelques pages au début des années 80* expliquant en gros que les ‘services’ apportés par UNIX system V n’étaient pas adaptés au monde de la base de données, et notamment en matière de planification. Les tâches typiques issues des SGBD prennent plus de temps à s’effectuer car il ne s’agit pas de calcul mais d’IOs, et le mode préemptif n’est pas adapté car il ‘coupe’ la dynamique et l’efficacité du SGBD en produisant de nombreux changements de contexte.

Donc les développeurs chez Sybase ont répondu en créant un scheduler coopératif ‘on top‘ **, propre à Adaptive Server et par dessus le planificateur de l’OS, de telle manière à ce qu’il masque les choses vis à vis de celui-ci et limite au maximum les changements de contexte.

– Les tranches de temps:
ASE va donc fonctionner comme un OS, mais avec ses règles à lui: il gère sa propre planification, son propre partage du temps CPU, ses propres valeurs de quantième. Car bien que coopératif, il doit pouvoir stopper une tâche qui ne répond plus et éviter la saturation d’un engine. Lorsqu’il planifie une tâche, il va lui allouer un bail. Ce bail n’est pas une valeur temporelle, c’est un nombre de ticks d’horloge qui est décrémenté. Il est calculé en divisant le paramètre ‘timeslice’ par ‘sql server clock tick length’. Comme les deux valeurs sont à 100 ms par défaut, le nombre de ticks d’horloge autorisé est de 1. Dans le déroulement de son exécution, une tâche va passer par des parties de code qu’on appelle des yield points, au cours desquels elle va prendre une seconde pour vérifier qu’elle n’a pas dépassé son bail. Si elle découvre que le bail est excédé (bail < 0), elle va demander à ASE de lui accorder une grace supplémentaire (cpu grace time), qui peut aller jusqu’à 500 ticks (soit 50 secondes). Si elle n’a pas terminé au delà de cette valeur, alors ASE sort les gros moyens, termine la tâche, annule ses transactions et affiche une stacktrace dans l’errorlog:

00:00000:00005:2007/04/02 22:09:24.07 kernel  timeslice -501, current process infected
00:00000:00005:2007/04/02 22:09:24.75 kernel  ************************************
00:00000:00005:2007/04/02 22:09:24.77 kernel  curdb = 1 tempdb = 2 pstat = 0x200
00:00000:00005:2007/04/02 22:09:24.77 kernel  lasterror = 0 preverror = 0 transtate = 1
00:00000:00005:2007/04/02 22:09:24.77 kernel  curcmd = 0 program =
...

Tout ceci se passe toujours sans que l’OS ne sache rien, car de son côté, le processus dataserver est toujours en exécution. C’est le but: l’OS ne doit pas interrompre le processus dataserver puisque celui-ci semble toujours travailler.

– Runnable Process search count:
Toujours dans le souci de préserver son exécution vis à vis de l’OS, lorsqu’il a terminé d’exécuter des tâches, et plutôt que de rendre la main tout de suite, Adaptive Server va entrer dans une boucle de vérification des IOs en attente côté réseau puis côté disque. S’il ne trouve rien, il va tourner (spinning) pour rechercher de nouvelles tâches en attente d’exécution, puis de nouvelles IOs réseau, puis de nouvelles IOs disques, etc… tout ça 2000 fois par défaut (runnable process search count), même s’il n’y a rien à traiter dans aucune file d’attente. Ce qui donne une apparente sensation d’hyperactivité côté OS, alors que côté Adaptive Sever, l’électro-encéphalo est plutôt plat.

Pour illustrer tout ça, une petite explication de la section ‘kernel’ de sp_sysmon:

Kernel Utilization
------------------

Your Runnable Process Search Count is set to 5000
and I/O Polling Process Count is set to 10

Engine Busy Utilization        CPU Busy   I/O Busy       Idle
------------------------       --------   --------   --------
Engine 0                       80.0 %      7.5 %     12.5 %
Engine 1                       80.8 %      6.0 %     13.2 %
Engine 2                       82.5 %      5.7 %     11.9 %
Engine 3                       84.0 %      6.7 %      9.3 %
Engine 4                       79.0 %      6.8 %     14.2 %
Engine 5                       80.0 %      7.3 %     12.7 %
Engine 6                       79.6 %      8.3 %     12.0 %
------------------------       --------   --------   --------
Summary           Total         565.9 %     48.4 %     85.7 %
Average                         80.8 %      6.9 %     12.2 %

Avant de commencer, il faut bien rappeler le cheminement de la boucle:

Chaque engine tient le compte de ticks reçus de l’OS (par le biais de signaux SIGALRM) tous les 100ms, et les répertorie selon trois catégories:

CPU Busy: représente le temps (en fait le nombre de ticks ramené en pourcentage) passé par l’engine à exécuter une tâche sur l’intervalle.
Idle: représente le temps passé par l’engine à n’exécuter aucune tâche sur l’intervalle.
I/O Busy: est une soustraction de Idle. Il s’agit du temps passé par l’engine à n’exécuter aucune tâche (donc Idle) mais où il y avait au moins une IO disque en attente dans la file. Elle représente donc la quantité de temps où l’engine n’a pas pu exécuter une tâche parce qu’il y avait au moins une IO disque en attente.

On peut retrouver ces valeurs brutes en interrogeant les variables globales @@cpu_busy, @@idle, et @@io_busy. La durée d’un tick peut être retrouvée avec @@timeticks.

A+. [ David B. ]

*: Michael Stonebraker, Operating System Support for Database Management, CACM 24(7), p412-418 (1981).
**: et Microsoft fit de même
en dotant SQL Server 7.0 de son propre scheduler coopératif (UMS), toujours bien présent en version 2008 sous une autre dénomination (SQLOS).

Continuez votre lecture sur le blog :

twitterlinkedinmail

David Baffaleuf

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.