Le c10k problemdans C10k, C abrège le mot connexion et 10k fait référence au système métrique : 10 Kilos, c'est-à-dire . que l'on pourrait traduire en français par le problème des dix mille connexions simultanées, est un code numérique utilisé pour exprimer la limitation que la plupart des serveurs ont en termes de connexions au réseau. Cette limite repose sur le constat que dans les différentes configurations matérielles et logicielles possibles, les grands serveurs actuels ne semblent pas capables de supporter plus de dix mille connexions simultanées. Cette limitation est partiellement imputable à des contraintes liées aux systèmes d'exploitation et à la conception des applications clients-serveurs auxquelles ils prennent part

Dans le contexte particulier des serveurs web, depuis l'identification de ce problème, quelques solutions ont été proposées, mais la plupart des serveurs utilisés ne parviennent pas à dépasser cette limite.

Problème

La démocratisation de l'Internet depuis le début des années 1990 et les millions d'internautes qui l'accompagnent, posent aux serveurs gérant ces services, le problème de leur capacité à prendre en charge cette fréquentation, notamment pour les services à forte fréquentation (moteur de recherche, sites de réseaux sociaux, jeux en ligne, etc.), qui est leur capacité à prendre en charge un nombre important de clients et donc de requêtes simultanées - Microsoft comptabilisait 300 millions de hits par jour sur ses sites web pour 4,1 millions d'utilisateurs.

Ce problème s'est accéléré avec les réseaux haut débit notamment l'ATM transmettant toujours plus rapidement des requêtes aux serveurs frontaux lesquels devant prendre en charge ces requêtes sans générer de goulots d'étranglements. Les infrastructures réseaux ayant apporté des solutions notamment des fonctions de cache afin d'améliorer les performances, le cœur du problème s'est focalisé sur les serveurs eux-mêmes .

Si aujourd'hui les limites physiques liées au matériel sont repoussées, plusieurs éléments de l'architecture d'un système d'exploitation dont la gestion des entrées-sorties et le modèle de communication concurrente peuvent influer et sont, à ce titre, déterminants voire structurants pour la performance de la gestion des accès concurrents.

Modèle de communication concurrente Lorsqu'un serveur web traite une requête HTTP pour laquelle il retourne un simple fichier proposant un contenu HTML, différentes étapes nécessaires à la fourniture de cette réponse vont s’enchaîner successivement : accepter la connexion entrante du client et créer la socket associée ; lire la requête et l'entête HTTP ; rechercher le fichier demandé dans le système de fichiers avec contrôle des droits de lecture ; transmettre l'entête de réponse sur la socket client ; lire le fichier ; et transmettre son contenu sur la socket ouverte. Chacune de ces étapes inclut des opérations de lecture et d'écriture, mais aussi d'ouverture de socket de connexion et de fichier. Et chacune de ces opérations peut générer le blocage (ou du moins la suspension) du processus lorsque les données attendues ne sont pas présentes. Le système d'exploitation des serveurs intercale (entrelace) ces différentes étapes pour les différentes requêtes qu'il reçoit, afin d'optimiser l'utilisation des ressources.

La stratégie mise en œuvre pour gérer cet entrelacement est donc déterminante. On distingue 2 principaux modèles de communication pour la gestion des accès concurrents formalisés pour la première fois en 1978 par Lauer et Needham. Le débat récurrent qui oppose ces 2 modèles sévit depuis des décennies.

Modèle thread-driven (thread-based) thumb|left|upright=0.7|Modèle thread-driven ou Système orienté process.

Alors qu'un processus est un programme en exécution, l’unité de travail dans un système en temps partagé, un thread, également appelé processus léger, est quant à lui l'unité de base d’utilisation du processeur. Il utilise les mêmes ressources et le même espace mémoire que le programme dans lequel il est exécuté, mais possède son propre pointeur d’instructions et sa propre pile d’exécutionBase Definitions volume of IEEE Std 1003.1-2001, Section 3.393.

Le modèle thread-drivenmodèle thread-driven que l'on pourrait traduire en français par modèle piloté par les tâches. s'appuie donc sur un ensemble de threads de contrôle qui vont prendre en charge les différentes tâches d'exécution. Dès lors qu'un thread est bloqué sur une opération d'entrée-sortie, il passera la main à un autre thread éligible au sein du même espace d'adressage. Jusqu'à ce que l'entrée-sortie soit terminée, après quoi, le thread redevient prêt à utiliser. La coopération entre les threads est assurée par des mécanismes de synchronisation matérialisés sous forme de verrous, sémaphores ou autre mutex et qui permettent de gérer les accès à des ressources partagées.

Une optimisation a été apportée en mettant en œuvre un pool de threads qui distribue les tâches d'exécution sur des threads créés, initialisés par avance. Ainsi, le gain se situe au niveau du temps inhérent à la création et à la suppression des threads . Ce modèle est proposé dans les langages de programmation tel que Java .

Par ailleurs, la surcharge due à l'overheadl'overhead désigne le temps passé par un système à ne rien faire d'autre que se gérer lié à la synchronisation des threads peut être contournée par la mise en œuvre de threads coopératifs comme cela a été implémenté dans le paquetage Capriccio.

Modèle event-driven thumb|left|upright=0.7|Modèle Event-driven ou Système orienté message

Le modèle event-drivenmodèle event-driven que l'on pourrait traduire en français par modèle piloté par les événements., qui repose sur un mécanisme de multiplexage des entrées-sorties, est organisé autour de l'exécution d’événements (par exemple arrivée d'une requête pour une page web) et permet ainsi de gérer la concurrence grâce à une boucle à événements qui va répartir l’événement sur un ou plusieurs gestionnaires qui vont prendre en charge cet événement. Ces gestionnaires peuvent à leur tour solliciter un autre gestionnaire d’événements (pour gérer par exemple une demande de lecture sur disque). Les gestionnaires communiquent entre eux par messages ou par événements. Les fonctions devront être courtes afin de rendre la main à la boucle à événements et de ne pas laisser les événements s'accumuler.

Les systèmes event-driven sont organisés autour d'un seul thread (SPED - Single Process Event-Driven) qui gère donc des événements. Quand un programme ne peut terminer une opération car en attente d'un événement, il enregistre un rappel de service au niveau de la boucle à événements qui inspecte les événements en entrées et qui exécutera le rappel de service lorsque l’événement se produira. thumb|center|upright=2|Single Process Event Driven - SPED

Le modèle event-driven est adapté pour les applications hautement concurrentes. Les raisons sont que les systèmes event-driven permettent des optimisations qu'il est difficile de mettre en place dans les modèles threads-driven, des messages peuvent être traités en traitement par lots, un meilleur ordonnancement est possible, le contrôle de flux est plus flexible, et il n'y a pas de basculement de contexte.

Modèle hybride Les deux modèles thread-driven et event-driven présentent cependant l'un et l'autre des limitations.

Le modèle par threads présente quelques difficultés relatives notamment à la gestion de la synchronisation des threads (utilisation de verrou, sémaphore, mutex, variables de condition, waitable timer – la contention de verrou peut engendrer des problèmes de performances en cas d'augmentation du nombre de threads) et des limitations relatives aux nombres de threads gérés par un système.

Le modèle par événements ne prend pas en charge le , ne tirant ainsi pas avantage des architectures multiprocesseurs. D'autre part, la gestion par événements est dépendante de mécanismes pas forcément correctement supportés par le système d'exploitation ou le langage. Enfin les programmes sont souvent plus complexes à développer et à debugger.

Pour essayer de tirer parti des deux modèles, un modèle hybride a été conçu. SEDAproposé par Matt Welsh de l'Université de Berkeley est un framework s'appuyant sur les trois composants qui font la force des modèles Event Driven et Thread Driven que sont les tâches, queues et pool de threads. Ce framework utilise également une queue d’événements, un pool de threads et un gestionnaire d’événements (cf « SEDA (Staged event-driven architecture) »).

Par ailleurs, la bibliothèque logicielle libasyncdéveloppé par Frank Dabek du Laboratoire d'Informatique du MIT supporte le multi-processeurs et permet de s'affranchir de la gestion des verrous et de la coordination des exécutions de threads propre à une gestion par thread.

Le modèle AMPED (Asymmetric Multi-Process Event Driven, en français : modèle piloté par événements asymétriques à processus multiple)proposé par Vivek S; Pai et Peter Druschel de l'Université Rice de Houston met en œuvre un processus principal (event dispatcher) pour le traitement des requêtes et d'autres processus ou threads pour gérer les entrées-sorties disques. Ces threads sont sollicités via un canal IPC de communication interprocess. Cette architecture est utilisée pour le serveur web Flash. thumb|center|upright=2|Asymmetric Multi-Process Event Driven - AMPED

La gestion des entrées-sorties thumb|left|upright=1|Matrice simplifiée des modèles d'Entrée-Sorties basique Vu d’un processus, les communications sont gérées comme des fichiers, les entrées-sorties reposant sur des descripteurs de fichiers (en Anglais : Filehandler). Ainsi une émission est assimilée à une écriture sur un fichier tandis qu'une réception est assimilée à une lecture sur un fichier. À chaque processus est associé une table des descripteurs de fichiers. Il s’agit d’un tableau de pointeurs, chaque pointeur pointant indirectement sur un fichier (v-node) ou sur une communication (socket). Un serveur, web par exemple, devant gérer simultanément un très grand nombre de flux d’entrée et de flux de sortie, le problématique est les attentes actives sur plusieurs descripteurs. Quatre modèles de gestion coexistent sous Linux en fonction de leur caractère bloquant ou non-bloquant et synchrone ou asynchrone (voir Figure « Matrice simplifiée des modèles d'Entrée-Sorties basique »).

thumb|right|upright=1|Séquence de lecture d'Entrée-Sorties Synchrone Bloquante Entrées/Sorties bloquantes synchrones Lorsqu'une application effectue un appel système read(), celle-ci se bloque, le contexte bascule au niveau du noyau. Tant que la réponse au read() n'est pas retournée, l'application est en attente de la réponse (fin d'exécution ou erreur). Une fois celle-ci retournée, l'application se débloque. Ce modèle reste encore le modèle le plus répandu dans les applications mais il est efficace d'un point de vue performance unitaire uniquement. Entrées/Sorties non-bloquantes synchrones Dans ce modèle, la différence est que le noyau répond immédiatement afin de ne pas bloquer ; la réponse consiste en un code erreur indiquant que la commande est en attente. Cela nécessitera des appels systèmes supplémentaires de l'application pour une lecture de la réponse seulement une fois celle-ci disponible. Cette variante est moins efficace (notamment en raison de la latence entrées/sorties). Entrées/Sorties bloquantes asynchrones Un thread initialise une entrée-sortie via un appel système read() et délègue ensuite la tâche à un sélecteur pouvant ainsi basculer à une nouvelle tâche. Le sélecteur vérifie alors l'arrivée d'événement entrées-sorties sur le descripteur de connexions. L'intérêt étant que le sélecteur gère la notification d'un ensemble de descripteurs de connexions. thumb|right|upright=1|Séquence de lecture d'Entrée-Sorties ASynchrone Non-Bloquante