Shinken : l'art de l'inspiration

Gabès Jean ( 14 Nov 2009 ) blog Talk /

Le partage des idées

Dans tout ce qui est libre on partage le code bien sûr, mais encore plus important on partage les idées. Deux modifications que j'ai apportées ces derniers temps aux Workers (utilisés par les pollers/reactionners) reflètent cela.

Le problème des workers est double : avoir LE bon nombre de Worker et que chaque worker soit le plus efficace possible. Le tout bien sûr pour réduire leur charge au maximum.

Avoir le bon nombre

Le nombre de Worker n'est pas si simple à déterminer. On peut placer un nombre arbitraire (10, 30, 100 ou même 250) mais il ne sera jamais optimal, sauf à faire de nombreuses tentatives. De plus, mesurer leur utilisation n'est pas chose aisée.

Sur un petit environnement, avoir 4 workers sera plus que suffisant. Sur les très gros, 30 n'est pas du luxe. En effet, si on prends un check simple qui fait toujours la même chose, il est relativement simple de savoir combien il en faut. Mais avec des checks qui mettent un temps aléatoire pour s'éxécuter, ça devient tout de suite plus dur.

Après une petite réflexion, leur nombre optimal est venu:

temps_moyen_checks*checks à traiter

Bon le nombres de checks à traiter est plus que facile à obtenir. Le temps moyen des checks un petit peu moins. Je suis parti d'un temps moyen depuis le lancement du daemon mais le problème est que le nombre de checks à la seconde n'est pas tout le temps le même, la nuit ça peut être plus calme par exemple. J'ai utilisé un calculun calcul similaire au load average Unix (une moyenne mobile exponentielle). Ceci permet de ne pas avoir de lisser un peu le nombres de workers demandés le tout en ayant une réactivité intéressante car j'ai pris un load à une minute.

Ceci n'aurait pas été possible si les auteurs d'Unix d'auraient pas utilisés ce type de moyenne et que Linux m'ait montré le code (tout simple en plus..). Au passage j'ai cré une Class spéciale pour ce type de calcul afin de pouvoir le réutiliser dans d'autres parties du code.

Pour l'instant c'est la partie montante du nombre de worker qui est utilisé. Il faut que je m'occupe de la partie descendante (où comment flinguer intelligemment les workers).

Attendre le temps qu'il faut

Revenons un peu sur les temps des checks. Ils sont aléatoires. Souvenez vous : j'ai changé le code du lancement des checks sous Unix par la variante Windows (avec un joli gain de perfs à la clé et une dépendance de moins). MAIS cette technique repose sur une boucle sur poll (pas de select malheureusement :( ) avec un sleep au milieu. OK, il suffit d'attendre le temps du checks... Ah flûte, on ne sait pas a priori combien de temps il va attendre. Attendre trop et c'est de la latence et un worker qui ne fai rien (donc plus de workers nécessaires). Attendre trop pu, c'est une latence minimale, mais un poll lancé trop souvent et donc de la charge inutile...

Bref, il faut attendre le BON temps, ou a défaut le bon ordre de grandeur. Là on peut s'inspirer du TCP.

Oulala, quel est le rapport entre le TCP et une boucle sur le poll? Et bien le slow start de TCP bien sûr! TCP est fait pour tout type de connexions, rapides, lentes, moyennes. Il doit optimiser le débit. Pour cela il augmente dès le début de la connexion le débit envoyé à chaque envoi. Fois 2 à chaque envois. Dès que ça ne passe plus, il a trouvé l'ordre de grandeur, et passe par une augmentation linéaire.

On va utiliser un système similaire. Nous voulons l'ordre de grandeur (une fois qu'on a trouvé, c'est fini). On va donc partir d'une boucle toute simple :

wait_time = 0.0001
while process.poll() is None:
time.sleep(wait_time)

Et la changer en :

wait_time = 0.0001
while process.poll() is None:
wait_time = min(wait_time*2, 0.1)
time.sleep(wait_time)

En une ligne on a une recherche de l'ordre de grandeur. Simple non? Ici j'ai borné à 0.1s. 10 appels systèmes par secondes sont tout à fait acceptable, 10000 beaucoup moins. On change donc un nombre d'appel en O(n) en O(log2(n)).

Il est marrant de voir à quel point les algo les plus remarquables sont toujours d'une simplicité étonnante...

NOTE 2021: c'est sûrement l'algo de Shinken qui est passé le plus inaperçu, mais qui a sauvé le plus de cycle CPU sur ces
           10 dernières années. Comme quoi ^^

Archives