UNIQUE_ID
est définie pour identifier chaque requête. Les identificateurs uniques ont une grande utilité pour diverses raisons qui sont expliquées dans ce document.
Faisons d'abord une brève récapitulation sur la façon dont le serveur Apache fonctionne sur des machines Unix. Cette fonctionnalité n'es en effet pas supportée sous Windows NT. Sous Unix, Apache crée un certain nombre de processus fils, chacun d'entre eux ayant la charge de traiter une requête unique. Chacun des enfants peut cependant être amené à servir successivement plusieurs requêtes pendant sa durée de vie. Pour les besoins de cet exposé, les enfants ne sontpas sensés partager des données. Nous appelerons ces enfants "processus httpd".
Un site Web peut s'appuyer sur une ou plusieurs machines sous la même autorité administrative, nous appelerons cet ensemble de machines un "cluster". Chaque machine peut en outre exécuter plusieurs instances d'Apache. Toutes ces instances confondues seront désignées comme "l'univers", et avec certains présupposés, nous montrerons qu'il est possible d'attribuer un identifiant unique pour chaque requête traitée dans cet "univers", sans pour autant générer un trafic important entre chaque machine du "cluster".
Les machines dans le "cluster" doivent néanmoins satisfaire les conditions suivantes. (Même si vous ne disposez que d'une machine, il faudra synchroniser son horloge avec NTP).
Pour autant que le système d'exploitation le permette, nous supposerons que les PID (identificateurs de processus) sont sur 32 bits. Si un système d'exploitation code les PID sur plus de 32 bits, la modification est très facile à faire, mais doi être faite dansle code source du module.
Une fois ces présupposés acquis, il est possible de discriminer, à un instant donné, chaque processus httpd sur une quelconque machines du cluster vis-à-vis de tous les autres processus httpd. L'adresse IP et le PID du processus httpd nous donnent une clef suffisante pour ce faire. Donc, afin de pouvoir générer des identifiants uniques pour chacune des requêtes, nous devons simplement trouver un moyen de différencier plusieurs instants distincts dans la trame temporelle.
Pour noter cet instant, nous utiliserons les étiquettes temporelles Unix (définies comme les secondes écoulées depuis le 1er Janvier, 1970 UTC à 0h00), combinées à un compteur 16 bits. L'étiquette temporelle n'a en effet qu'une granularité d'une seconde, et le compteur est utilisé pour descendre la granularité jusqu'au 65536 ème de seconde. Le quadruplet ( ip_addr, pid, time_stamp, counter ) est suffisant pour énumérer 65536 requêtes par seconde par processus httpd. Une attention doit être cependant portée au fait que le même PID peut être réutilisé au bout d'un certain temps, et le compteur permet de contourner ce problème.
Lorsqu'un processus httpd fils est créé, le comteur est initialisé avec une valeur ( la microseconde courante de l'horloge divisée par 10 ) modulo 65536 (cette formule a été choisie pour éliminer certains problèmes de variation sur les bits de poids faible de l'horloge à la "microseconde" sur certains systèmes). Lorsqu'un identificateur unique est généré, l'étiquette temporelle utilisée est celle marquant le moment auquel la requête est arrivée au serveur Web. The compteur est incrémenté à chaque fois qu'un identificateur est généré (et à l'autorisation de reboucler au zéro).
Le kernel génère un PID pour chacun des processus issus d'un fork() du serveur, et peuvent eux-aussi reboucler (ils sont définis sur 16 bits sur de nombreux systèmes Unix, mais les nouvelles version ont étendu ces PID à 32 bits). Il parait donc logique de penser qu'un même pid sera réutilisé au bout d'un certain temps. Cependant, à moins qu'il ne soit réutilisé dans la même seconde, ce rebouclage ne détruit pas l'unicité du quadruplet. C'est à dire que nous supposons que le système ne lancera pas plus de 65536 processus par seconde (sur certains systèmes cette limite théorique descend à 32768, mais il est très peu probable que cela se produise).
Supposons que le temps lui même se répète pour une raison quelconque. C'est à dire, nous supposons ici que l'horloge système est recalée, revisitant ainsi une portion de temps du passé (ou si elle est trop avancée, puis réinitialisée correctement, revisitant ainsi une portion de temps future). Dans ce cas, nous pourrions facilement démontrer que nous pourrions avoir répétition du PID et de l'étiquette temporelle. Le choix d'initialisation du compteur a été fait pour nous permettre d'amoindrir ce risque. Notez que nous souaiterions en fait disposer d'un véritable nombre aléatoire pour initialiser le compteur, mais de nombreux systèmes n'en fournissent pas de satisfaisant (c'est-à-dire que vous ne pouvez pas utiliser rand() car il vous faut initialiser le générateur aléatoire, et ne pouvez non plus le réinitialiser par le temps, car ce dernier n'a qu'une seconde de résolution, et s'est répété). La ligne de défense n'est pas parfaite.
De quelle qualité est notre ligne de défense actuelle ? Supposons qu'une de vos machines serve dans les 500 requêtes par seconde (ce qui semble être une limite supérieure très raisonnable au moment ou nous écrivons ces lignes, car les systèmes ne font pas que servir des documents statiques à l'heure actuelle). Pour ce faire, vous aurez besoin de processus fils en quantité suffisante pour servir les accès clients concurrents. Nous allons prendre une hytpothèse pessimiste et supposer qu'un processus fils unique peut servir 500 requêtes par seconde. Il y a effectivement 1000 valeurs de départ du compteur pour lesquelles les deux séquences de 500 requêtes se recouvrent. Il y a donc 1.5% de chances que si le temps se répète (deuxième niveau de probable) ce processus répête une valeur de compteur, et provoque la chute du principe d'unicité de l'identificateur unique. Il s'agissait d'un exemple effectivement très pessimiste, et la perte d'unicité est en situation réelle une situation de loin beaucoup moins probable. Si votre système est tel que vous craignez que cela puisse néanmoins arriver, alors vous souhaiterez peut-être élargir le compteur à 32 bits (en modifiant le code source).
Vous serez probablement ennuyé au moment des passages à l'heure d'été. Ce n'est pas réellement un problème dans la mesure où la réféérence de temps utilisée est le temps UTC, qui elle, n'est pas modifiée. Notez que des systèmes Unix tournant sur des architectures x86 peuvent nécessiter une configuration particulière pour que ceci reste vrai -- Il doit leur être précisé que la carte mère utilise bien une horloge UTC et effectue la correction appropriée. Même dans ce cas, si vous utilisez NTP, votre temps UTC ne sera stabilisé qu'après une brève période après le redémarrage système.
La variable d'environnement UNIQUE_ID
est construite en encodant les quadruplets 112 bits (32 bits d'adresse IP, 32 bits du PID, 32 bits du temps, et 16 bits pour le compteur) alphabétiquement ([A-Za-z0-9@-]
) d'une manière similaire à celle effectuée par l'encodage en base64 du MIME, ce qui produit 19 caractères.
L'alphabet base64 du MIME correspond actuellement à la définition [A-Za-z0-9+/]
. Cependant, les signes +
et /
ont besoin d'un encodage spécial dans les URL, ce qui les rend indésirables. Toutes les valeurs sont encodées dans l'ordre "réseau"
de sorte que l'encodage est compatible entre des architectures utilisant des agencements d'octets d'ordres différents. L'ordre effectif de l'encodage est le suivant : l'étiquette temporelle vient en premier, suivie de l'adresse IP, du PID, et enfin du compteur. Cet ordre a un certain but, mais il doit être souligné ici qu'il n'est pas prévu ni conseillé que les applicatifs dissèquent cet encodage. Les applications devront traiter cet identificateur unique UNIQUE_ID
comme une clef opaque, dans le seul but de la comparer à d'autres variables UNIQUE_ID
.
Cet ordre a été choisi de façon à pouvoir chager le codage dansle futur sans avoir à se soucier des collisions possibles avec une base de données d'identificateurs UNIQUE_ID
existante. Les nouveaux encodages garderont toujours l'étiquette temporelle commepremier élément, et pourront utiliser le même alphabet et longueur de champs. Comme ces étiquettes constituent normalement une séquence croissante, ce marqueur temporel à la seconde est suffisant pour indiquer à toutes les machines d'un cluster à partir de quel moment elles doivent arrêter de servir des requêtes pour changer de format de clef, après quoi elles se remettent à servir des nouvelles reqêtes qui seront marquées des nouvelles clefs.
Nous pensons qu'il s'agit la d'une solution relativement portable à notre problème. Ce système peut être étendu à des systèmes multitaches telsque Windows NT, et pourra évoluer selon les besoins futurs. Les identificateurs générés ont une durée de vie virtuellement infinie du fait qu'une évolution du format peut rallonger ces identificateurs autant que nécessaire. Pratiquement aucune communication n'est nécessaire entre les différentes machines du cluster (à part la synchronisation NTP, qui consomme très peu de bande passante), pas plus que de connexion spéciale entre les différents processus httpd (cette communication est implicite par l'affectation de la valeur de PID par le kernel). Dans des situations très particulières, l'identificateur pourra être raccourci, mais ceci implique de faire des suppositions supplémentaires (par exemple de ne pas y annoter l'adresse de sous-réseau si toutes les machines sont sur le même réseau local, mais le système perd en portabilité).
mod_unique_id
n'a pas de directives.
Version française © Valery Fremaux / EISTI 1998