Serveur HTTP Apache Version 2.4

Apache 2.x est un serveur web à usage général, conçu dans un but d'équilibre entre souplesse, portabilité et performances. Bien que non conçu dans le seul but d'établir une référence en la matière, Apache 2.x est capable de hautes performances dans de nombreuses situations du monde réel.
Comparée à Apache 1.3, la version 2.x comporte de nombreuses optimisations supplémentaires permettant d'améliorer le débit du serveur et sa personnalisation. La plupart de ces améliorations sont activées par défaut. Cependant, certains choix de configuration à la compilation et à l'exécution peuvent affecter les performances de manière significative. Ce document décrit les options qu'un administrateur de serveur peut configurer pour améliorer les performances d'une installation d'Apache 2.x. Certaines de ces options de configuration permettent au démon httpd de mieux tirer parti des possibilités du matériel et du système d'exploitation, tandis que d'autres permettent à l'administrateur de privilégier la vitesse par rapport aux fonctionnalités.

 Problèmes matériels et relatifs au système d'exploitation
 Problèmes matériels et relatifs au système d'exploitation Optimisation de la configuration à l'exécution
 Optimisation de la configuration à l'exécution Optimisation de la configuration à la compilation
 Optimisation de la configuration à la compilation Appendice : Analyse détaillée d'une trace
 Appendice : Analyse détaillée d'une traceLe principal problème matériel qui affecte les performances du serveur
    web est la mémoire vive (RAM). Un serveur web ne devrait jamais avoir à
    utiliser le swap, car le swapping augmente le temps de réponse de chaque
    requête au delà du point que les utilisateurs considèrent comme
    "trop lent". Ceci incite les utilisateurs à cliquer sur "Stop", puis
    "Charger à nouveau", ce qui a pour effet d'augmenter encore la charge
    du serveur. Vous pouvez, et même devez définir la valeur de la directive
    MaxRequestWorkers de façon à ce que
    votre serveur ne lance pas un nombre de processus enfants tel qu'il
    commence à faire du swapping. La méthode pour y parvenir est
    simple : déterminez la taille de votre processus Apache standard en
    consultant votre liste de processus à l'aide d'un outil tel que
    top, et divisez votre quantité totale de mémoire disponible
    par cette taille, tout en gardant un espace suffisant
    pour les autres processus.
Hormis ce réglage relatif à la mémoire, le reste est trivial : le processeur, la carte réseau et les disques doivent être suffisamment rapides, où "suffisamment rapide" doit être déterminé par l'expérience.
Le choix du système d'exploitation dépend principalement du contexte local. Voici cependant quelques conseils qui se sont généralement avérés utiles :
Exécutez la dernière version stable et le niveau de patches le plus haut du système d'exploitation que vous avez choisi. De nombreux éditeurs de systèmes d'exploitation ont amélioré de manière significative les performances de leurs piles TCP et de leurs bibliothèques de thread ces dernières années.
Si votre système d'exploitation possède un appel système
	sendfile(2), assurez-vous d'avoir installé la version
	et/ou les patches nécessaires à son activation. (Pour Linux, par
	exemple, cela se traduit par Linux 2.4 ou plus. Pour les versions
	anciennes de Solaris 8, vous pouvez être amené à appliquer un patch.)
	Sur les systèmes où il est disponible, sendfile permet
	à Apache 2 de servir les contenus statiques plus rapidement, tout en
	induisant une charge CPU inférieure.
| Modules Apparentés | Directives Apparentées | 
|---|---|
Avant Apache 1.3, la directive
      HostnameLookups était positionnée
      par défaut à On. Ce réglage augmente le temps de réponse de
      chaque requête car il entraîne une recherche DNS et le traitement de la
      requête ne pourra pas être achevé tant que cette recherche ne sera
      pas terminée. Avec Apache 1.3, ce réglage est défini par défaut à
      Off. Si vous souhaitez que les adresses dans vos fichiers
      journaux soient résolues en noms d'hôtes, utilisez le programme
      logresolve fourni avec Apache, ou un des nombreux
      paquets générateurs de rapports sur les journaux disponibles.
Il est recommandé d'effectuer ce genre de traitement a posteriori de vos fichiers journaux sur une autre machine que celle qui héberge le serveur web en production, afin que cette activité n'affecte pas les performances du serveur.
Si vous utilisez une directive
      Allowfrom domainDeny from domain
Notez qu'il est possible de modifier la portée des directives, en les
      plaçant par exemple à l'intérieur d'une section
      <Location "/server-status">. Les recherches DNS ne
      seront alors effectuées que pour les requêtes qui satisfont aux critères.
      Voici un exemple qui désactive les recherches DNS sauf pour les fichiers
      .html et .cgi :
HostnameLookups off <Files ~ "\.(html|cgi)$"> HostnameLookups on </Files>
Mais même dans ce cas, si vous n'avez besoin de noms DNS que dans
      certains CGIs, vous pouvez effectuer l'appel à gethostbyname
      dans les CGIs spécifiques qui en ont besoin.
Chaque fois que la ligne Options FollowSymLinks sera
      absente, ou que la ligne Options SymLinksIfOwnerMatch sera
      présente dans votre espace d'adressage, Apache devra effectuer des
      appels système supplémentaires pour vérifier la présence de liens
      symboliques. Un appel supplémentaire par élément du chemin du fichier.
      Par exemple, si vous avez :
DocumentRoot "/www/htdocs" <Directory "/"> Options SymLinksIfOwnerMatch </Directory>
et si une requête demande l'URI /index.html, Apache
      effectuera un appel à lstat(2) pour
      /www, /www/htdocs, et
      /www/htdocs/index.html. Les résultats de ces appels à
      lstat ne sont jamais mis en cache, ils devront donc être
      générés à nouveau pour chaque nouvelle requête. Si vous voulez absolument
      vérifier la sécurité des liens symboliques, vous pouvez utiliser une
      configuration du style :
DocumentRoot "/www/htdocs" <Directory "/"> Options FollowSymLinks </Directory> <Directory "/www/htdocs"> Options -FollowSymLinks +SymLinksIfOwnerMatch </Directory>
Ceci évite au moins les vérifications supplémentaires pour le chemin
      défini par DocumentRoot. Notez que
      vous devrez ajouter des sections similaires si vous avez des chemins
      définis par les directives
      Alias ou
      RewriteRule en dehors de
      la racine de vos documents. Pour améliorer les performances, et supprimer
      toute protection des liens symboliques, ajoutez l'option
      FollowSymLinks partout, et n'utilisez jamais l'option
      SymLinksIfOwnerMatch.
Dans toute partie de votre espace d'adressage où vous autoriserez
      la surcharge de la configuration (en général à l'aide de fichiers
      .htaccess), Apache va tenter d'ouvrir .htaccess
      pour chaque élément du chemin du fichier demandé. Par exemple, si vous
      avez : 
DocumentRoot "/www/htdocs" <Directory "/"> AllowOverride all </Directory>
et qu'une requête demande l'URI /index.html, Apache
      tentera d'ouvrir /.htaccess, /www/.htaccess,
      et /www/htdocs/.htaccess. Les solutions sont similaires à
      celles évoquées précédemment pour Options FollowSymLinks.
      Pour améliorer les performances, utilisez AllowOverride None
      pour tous les niveaux de votre espace d'adressage.
Dans la mesure du possible, évitez toute négociation de contenu si vous tenez au moindre gain en performances. En pratique toutefois, les bénéfices de la négociation l'emportent souvent sur la diminution des performances. Il y a cependant un cas dans lequel vous pouvez accélérer le serveur. Au lieu d'utiliser une directive générique comme :
DirectoryIndex index
utilisez une liste explicite d'options :
DirectoryIndex index.cgi index.pl index.shtml index.html
où vous placez le choix courant en première position.
Notez aussi que créer explicitement un fichier de
      correspondances de type fournit de meilleures performances
      que l'utilisation des MultiViews, car les informations
      nécessaires peuvent être simplement obtenues en lisant ce fichier, sans
      avoir à parcourir le répertoire à la recherche de types de fichiers.
Par conséquent, si la négociation de contenu est nécessaire pour votre
    site, préférez les fichiers de correspondances de type aux
    directives Options MultiViews pour mener à bien cette
    négociation. Se référer au document sur la
    Négociation de contenu pour une
    description complète des méthodes de négociation, et les instructions
    permettant de créer des fichiers de correspondances de type.
Dans les situations où Apache 2.x doit consulter le contenu d'un
      fichier en train d'être servi - par exemple à l'occasion du traitement
      d'une inclusion côté serveur - il transfère en général le fichier en
      mémoire si le système d'exploitation supporte une forme quelconque
      de mmap(2).
Sur certains systèmes, ce transfert en mémoire améliore les performances. Dans certains cas, ce transfert peut toutefois les dégrader et même diminuer la stabilité du démon httpd :
Dans certains systèmes d'exploitation, mmap devient
	  moins efficace que read(2) quand le nombre de
	  processeurs augmente. Sur les serveurs multiprocesseurs sous Solaris,
	  par exemple, Apache 2.x sert parfois les fichiers consultés par le
	  serveur plus rapidement quand mmap est désactivé.
Si vous transférez en mémoire un fichier localisé dans un système de fichiers monté par NFS, et si un processus sur une autre machine cliente NFS supprime ou tronque le fichier, votre processus peut rencontrer une erreur de bus la prochaine fois qu'il essaiera d'accéder au contenu du fichier en mémoire.
Pour les installations où une de ces situations peut se produire,
      vous devez utiliser EnableMMAP off afin de désactiver le
      transfert en mémoire des fichiers servis. (Note : il est possible de
      passer outre cette directive au niveau de chaque répertoire.)
Dans les cas où Apache peut se permettre d'ignorer le contenu du
      fichier à servir - par exemple, lorsqu'il sert un contenu de fichier
      statique - il utilise en général le support sendfile du noyau si le
      système d'exploitation supporte l'opération sendfile(2).
Sur la plupart des plateformes, l'utilisation de sendfile améliore les performances en éliminant les mécanismes de lecture et envoi séparés. Dans certains cas cependant, l'utilisation de sendfile peut nuire à la stabilité du démon httpd :
Certaines plateformes peuvent présenter un support de sendfile défaillant que la construction du système n'a pas détecté, en particulier si les binaires ont été construits sur une autre machine et transférés sur la machine où le support de sendfile est défaillant.
Dans le cas d'un système de fichiers monté sous NFS, le noyau peut s'avérer incapable de servir les fichiers réseau de manière fiable depuis son propre cache.
Pour les installations où une de ces situations peut se produire,
      vous devez utiliser EnableSendfile off afin de désactiver
      la mise à disposition de contenus de fichiers par sendfile. (Note : il
      est possible de passer outre cette directive au niveau de chaque
      répertoire.)
Avant Apache 1.3, les directives
      MinSpareServers,
      MaxSpareServers, et
      StartServers avaient des
      effets drastiques sur les performances de référence. En particulier,
      Apache avait besoin d'un délai de "montée en puissance" afin d'atteindre
      un nombre de processus enfants suffisant pour supporter la charge qui lui
      était appliquée. Après le lancement initial des processus enfants par
      StartServers, seulement un
      processus enfant par seconde était créé afin d'atteindre la valeur de la
      directive MinSpareServers. Ainsi,
      un serveur accédé par 100 clients simultanés et utilisant la valeur par
      défaut de 5 pour la directive
      StartServers, nécessitait
      environ 95 secondes pour lancer suffisamment de processus enfants
      permettant de faire face à la charge. Ceci fonctionne en pratique pour
      les serveurs en production, car ils sont rarement redémarrés. Ce n'est
      cependant pas le cas pour les tests de référence (benchmarks) où le
      serveur ne fonctionne que 10 minutes.
La règle "un processus par seconde" avait été implémentée afin
      d'éviter l'enlisement de la machine dans le démarrage de nouveaux
      processus enfants. Pendant que la machine est occupée à lancer des
      processus enfants, elle ne peut pas traiter les requêtes. Mais cette
      règle impactait tellement la perception des performances d'Apache qu'elle
      a dû être remplacée. A partir d'Apache 1.3, le code a assoupli la règle
      "un processus par seconde". Il va en lancer un, attendre une seconde,
      puis en lancer deux, attendre une seconde, puis en lancer quatre et
      ainsi de suite jusqu'à lancer 32 processus. Il s'arrêtera lorsque le
      nombre de processus aura atteint la valeur définie par la directive
      MinSpareServers.
Ceci s'avère suffisamment réactif pour pouvoir en général se passer
      de manipuler les valeurs des directives
      MinSpareServers,
      MaxSpareServers et
      StartServers. Lorsque plus de
      4 processus enfants sont lancés par seconde, un message est émis vers
      le journal des erreurs. Si vous voyez apparaître souvent ce genre de
      message, vous devez vous pencher sur ces réglages. Pour vous guider,
      utilisez les informations délivrées par le module
      mod_status.
À mettre en relation avec la création de processus, leur destruction
    est définie par la valeur de la directive
    MaxConnectionsPerChild. Sa valeur
    par défaut est 0, ce qui signifie qu'il n'y a pas de limite
    au nombre de connexions qu'un processus enfant peut traiter. Si votre
    configuration actuelle a cette directive réglée à une valeur très basse,
    de l'ordre de 30, il est conseillé de l'augmenter de manière
    significative. Si vous utilisez SunOs ou une ancienne version de Solaris,
    utilisez une valeur de l'ordre de 10000  à cause des fuites
    de mémoire.
Lorsqu'ils sont en mode "keep-alive", les processus enfants sont
    maintenus et ne font rien sinon attendre la prochaine requête sur la
    connexion déjà ouverte. La valeur par défaut de 5 de la
    directive KeepAliveTimeout tend à
    minimiser cet effet. Il faut trouver le bon compromis entre la bande
    passante réseau et les ressources du serveur. En aucun cas vous ne devez
    choisir une valeur supérieure à 60 seconds, car
    
    la plupart des bénéfices sont alors perdus.
Apache 2.x supporte les modèles simultanés enfichables, appelés
      Modules Multi-Processus (MPMs). Vous devez
      choisir un MPM au moment de la construction d'Apache. Certaines
      plateformes ont des modules MPM spécifiques :
      mpm_netware, mpmt_os2 et
      mpm_winnt. Sur les systèmes de type Unix, vous avez le
      choix entre un grand nombre de modules MPM. Le choix du MPM peut affecter
      la vitesse et l'évolutivité du démon httpd :
worker utilise plusieurs processus
	enfants possédant chacun de nombreux threads. Chaque thread gère une
	seule connexion à la fois. Worker est en général un bon choix pour les
	serveurs présentant un traffic important car il possède une empreinte
	mémoire plus petite que le MPM prefork.event utilise
	les threads, mais il a été conçu pour traiter davantage de
	requêtes simultanément en confiant une partie du travail à des
	threads de support, ce qui permet aux threads principaux de
	traiter de nouvelles requêtes.prefork utilise plusieurs processus enfants
	possédant chacun un seul thread. Chaque processus gère une seule
	connexion à la fois. Sur de nombreux systèmes, prefork est comparable
	en matière de vitesse à worker, mais il utilise plus de mémoire. De par
	sa conception sans thread, prefork présente des avantages par rapport à
	worker dans certaines situations : il peut être utilisé avec les
	modules tiers qui ne supportent pas le threading, et son débogage est plus
	aisé sur les platesformes présentant un support du débogage des threads
	rudimentaire.Pour plus d'informations sur ces deux MPMs et les autres, veuillez vous référer à la documentation sur les MPM.
Comme le contrôle de l'utilisation de la mémoire est très important
	en matière de performance, il est conseillé d'éliminer les modules que
	vous n'utilisez pas vraiment. Si vous avez construit ces modules en
	tant que DSOs, leur élimination consiste
	simplement à commenter la directive
	LoadModule associée à ce
	module. Ceci vous permet de vérifier si votre site fonctionne toujours
	après la suppression de tel ou tel module.
Par contre, si les modules que vous voulez supprimer sont liés statiquement à votre binaire Apache, vous devrez recompiler ce dernier afin de pouvoir les éliminer.
La question qui découle de ce qui précède est évidemment de
	savoir de quels modules vous avez besoin et desquels vous pouvez vous
	passer. La réponse sera bien entendu différente d'un site web à
	l'autre. Cependant, la liste minimale de modules nécessaire à
	la survie de votre site contiendra certainement
	mod_mime, mod_dir et
	mod_log_config. mod_log_config est bien
	entendu optionnel puisque vous pouvez faire fonctionner un site web
	en se passant de fichiers journaux ; ceci est cependant
	déconseillé.
Certains modules, à l'instar de mod_cache et des
      versions de développement récentes du MPM worker, utilisent l'API
      atomique d'APR. Cette API propose des opérations atomiques que l'on
      peut utiliser pour alléger la synchronisation des threads.
Par défaut, APR implémente ces opérations en utilisant les
      mécanismes les plus efficaces disponibles sur chaque plateforme cible
      (Système d'exploitation et processeur). De nombreux processeurs modernes,
      par exemple, possèdent une instruction qui effectue une opération
      atomique de type comparaison et échange ou compare-and-swap (CAS) au
      niveau matériel. Sur certaines platesformes cependant, APR utilise par
      défaut une implémentation de l'API atomique plus lente, basée sur les
      mutex, afin d'assurer la compatibilité avec les anciens modèles de
      processeurs qui ne possèdent pas ce genre d'instruction. Si vous
      construisez Apache pour une de ces platesformes, et ne prévoyez de
      l'exécuter que sur des processeurs récents, vous pouvez sélectionner une
      implémentation atomique plus rapide à la compilation en utilisant
      l'option --enable-nonportable-atomics du
      script configure :
        ./buildconf
        ./configure --with-mpm=worker --enable-nonportable-atomics=yes
      
L'option --enable-nonportable-atomics concerne les
      platesformes suivantes :
--enable-nonportable-atomics au script configure, APR
	    génère un code qui utilise le code opération SPARC v8plus pour des
	    opérations de compare-and-swap matériel plus rapides. Si vous
	    utilisez cette option de configure avec Apache, les opérations
	    atomiques seront plus efficaces (permettant d'alléger la charge du
	    processeur et un plus haut niveau de simultanéité), mais
	    l'exécutable produit ne fonctionnera que sur les processeurs
	    UltraSPARC.
	--enable-nonportable-atomics au script configure,
	    APR générera un code qui utilise un code d'opération du 486
	    pour des opérations de compare-and-swap matériel plus rapides. Le
	    code résultant est plus efficace en matière d'opérations atomiques,
	    mais l'exécutable produit ne fonctionnera que sur des processeurs
	    486 et supérieurs (et non sur des 386).
        Si vous incluez le module mod_status à la
      construction d'Apache et ajoutez ExtendedStatus On à sa
      configuration, Apache va effectuer pour chaque requête deux appels à
      gettimeofday(2) (ou times(2) selon votre
      système d'exploitation), et (pour les versions antérieures à 1.3) de
      nombreux appels supplémentaires à time(2). Tous ces
      appels sont effectués afin que le rapport de statut puisse contenir
      des indications temporelles. Pour améliorer les performances, utilisez
      ExtendedStatus off (qui est le réglage par défaut).
Cette section n'a pas été totalement mise à jour car elle ne tient pas compte des changements intervenus dans la version 2.x du Serveur HTTP Apache. Certaines informations sont encore pertinentes, il vous est cependant conseillé de les utiliser avec prudence.
Ce qui suit est une brève discussion à propos de l'API des sockets
      Unix. Supposons que votre serveur web utilise plusieurs directives
      Listen afin d'écouter
      plusieurs ports ou de multiples adresses. Afin de tester chaque socket
      pour voir s'il a une connexion en attente, Apache utilise
      select(2). select(2) indique si un socket a
      zéro ou au moins une connexion en attente. Le modèle
      d'Apache comporte plusieurs processus enfants, et tous ceux qui sont
      inactifs testent la présence de nouvelles connexions au même moment.
      Une implémentation rudimentaire de ceci pourrait ressembler à
      l'exemple suivant
      (ces exemples ne sont pas extraits du code d'Apache, ils ne sont
      proposés qu'à des fins pédagogiques) :
        for (;;) {
          for (;;) {
            fd_set accept_fds;
            FD_ZERO (&accept_fds);
            for (i = first_socket; i <= last_socket; ++i) {
              FD_SET (i, &accept_fds);
            }
            rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
            if (rc < 1) continue;
            new_connection = -1;
            for (i = first_socket; i <= last_socket; ++i) {
              if (FD_ISSET (i, &accept_fds)) {
                new_connection = accept (i, NULL, NULL);
                if (new_connection != -1) break;
              }
            }
            if (new_connection != -1) break;
          }
          process_the(new_connection);
        }
      Mais cette implémentation rudimentaire présente une sérieuse lacune.
      Rappelez-vous que les processus enfants exécutent cette boucle au même
      moment ; ils vont ainsi bloquer sur select s'ils se trouvent
      entre deux requêtes. Tous ces processus bloqués vont se réactiver et
      sortir de select quand une requête va apparaître sur un des
      sockets (le nombre de processus enfants qui se réactivent varie en
      fonction du système d'exploitation et des réglages de synchronisation).
      Ils vont alors tous entrer dans la boucle et tenter un
      "accept" de la connexion. Mais seulement un d'entre eux y
      parviendra (en supposant qu'il ne reste q'une seule connexion en
      attente), les autres vont se bloquer au niveau de accept.
      Ceci verrouille vraiment ces processus de telle sorte qu'ils ne peuvent
      plus servir de requêtes que par cet unique socket, et il en sera ainsi
      jusqu'à ce que suffisamment de nouvelles requêtes apparaissent sur ce
      socket pour les réactiver tous. Cette lacune a été documentée pour la
      première fois dans
      PR#467. Il existe
      au moins deux solutions.
La première consiste à rendre les sockets non blocants. Dans ce cas,
      accept ne bloquera pas les processus enfants, et ils
      pourront continuer à s'exécuter immédiatement. Mais ceci consomme des
      ressources processeur. Supposons que vous ayez dix processus enfants
      inactifs dans select, et qu'une connexion arrive.
      Neuf des dix processus vont se réactiver, tenter un accept
      de la connexion, échouer, et boucler dans select, tout en
      n'ayant finalement rien accompli. Pendant ce temps, aucun de ces processus
      ne traite les requêtes qui arrivent sur d'autres sockets jusqu'à ce
      qu'ils retournent dans select. Finalement, cette solution
      ne semble pas très efficace, à moins que vous ne disposiez d'autant de
      processeurs inactifs (dans un serveur multiprocesseur) que de processus
      enfants inactifs, ce qui n'est pas une situation très courante.
Une autre solution, celle qu'utilise Apache, consiste à sérialiser les entrées dans la boucle interne. La boucle ressemble à ceci (les différences sont mises en surbrillance) :
        for (;;) {
          accept_mutex_on ();
          for (;;) {
            fd_set accept_fds;
            
            FD_ZERO (&accept_fds);
            for (i = first_socket; i <= last_socket; ++i) {
              FD_SET (i, &accept_fds);
            }
            rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
            if (rc < 1) continue;
            new_connection = -1;
            for (i = first_socket; i <= last_socket; ++i) {
              if (FD_ISSET (i, &accept_fds)) {
                new_connection = accept (i, NULL, NULL);
                if (new_connection != -1) break;
              }
            }
            if (new_connection != -1) break;
          }
          accept_mutex_off ();
          process the new_connection;
        }
      Les fonctions
      accept_mutex_on et accept_mutex_off
      implémentent un sémaphore permettant une exclusion mutuelle. Un seul
      processus enfant à la fois peut posséder le mutex. Plusieurs choix se
      présentent pour implémenter ces mutex. Ce choix est défini dans
      src/conf.h (versions antérieures à 1.3) ou
      src/include/ap_config.h (versions 1.3 ou supérieures).
      Certaines architectures ne font pas ce choix du mode de verrouillage ;
      l'utilisation de directives
      Listen multiples sur ces
      architectures est donc peu sûr.
La directive Mutex permet
      de modifier l'implémentation du mutex mpm-accept à
      l'exécution. Des considérations spécifiques aux différentes
      implémentations de mutex sont documentées avec cette directive.
Une autre solution qui a été imaginée mais jamais implémentée, consiste à sérialiser partiellement la boucle -- c'est à dire y faire entrer un certain nombre de processus. Ceci ne présenterait un intérêt que sur les machines multiprocesseurs où plusieurs processus enfants peuvent s'exécuter simultanément, et encore, la sérialisation ne tire pas vraiment parti de toute la bande passante. C'est une possibilité d'investigation future, mais demeure de priorité basse car les serveurs web à architecture hautement parallèle ne sont pas la norme.
Pour bien faire, vous devriez faire fonctionner votre serveur sans
      directives Listen multiples
      si vous visez les performances les plus élevées.
      Mais lisez ce qui suit.
Ce qui précède convient pour les serveurs à sockets multiples, mais
      qu'en est-il des serveurs à socket unique ? En théorie, ils ne
      devraient pas rencontrer les mêmes problèmes car tous les processus
      enfants peuvent se bloquer dans accept(2) jusqu'à ce qu'une
      connexion arrive, et ils ne sont pas utilisés à ne rien faire. En
      pratique, ceci dissimule un même comportement de bouclage
      discuté plus haut dans la solution non-blocante. De la manière dont
      sont implémentées les piles TCP, le noyau réactive véritablement tous les
      processus bloqués dans accept quand une seule connexion
      arrive. Un de ces processus prend la connexion en compte et retourne
      dans l'espace utilisateur, les autres bouclant dans l'espace du
      noyau et se désactivant quand ils s'aperçoivent qu'il n'y a pas de
      connexion pour eux. Ce bouclage est invisible depuis le code de l'espace
      utilisateur, mais il est quand-même présent. Ceci peut conduire à la
      même augmentation de charge à perte que la solution non blocante au cas
      des sockets multiples peut induire.
Pour cette raison, il apparaît que de nombreuses architectures se
      comportent plus "proprement" si on sérialise même dans le cas d'une socket
      unique. Il s'agit en fait du comportement par défaut dans la plupart des
      cas. Des expériences poussées sous Linux (noyau 2.0.30 sur un
      biprocesseur Pentium pro 166 avec 128 Mo de RAM) ont montré que la
      sérialisation d'une socket unique provoque une diminution inférieure à 3%
      du nombre de requêtes par secondes par rapport au traitement non
      sérialisé. Mais le traitement non sérialisé des sockets uniques induit
      un temps de réponse supplémentaire de 100 ms pour chaque requête. Ce
      temps de réponse est probablement provoqué par une limitation sur les
      lignes à haute charge, et ne constitue un problème que sur les réseaux
      locaux. Si vous voulez vous passer de la sérialisation des sockets
      uniques, vous pouvez définir
      SINGLE_LISTEN_UNSERIALIZED_ACCEPT et les
      serveurs à socket unique ne pratiqueront plus du tout la
      sérialisation.
Comme discuté dans draft-ietf-http-connection-00.txt section 8, pour implémenter de manière fiable le protocole, un serveur HTTP doit fermer les deux directions d'une communication indépendamment (rappelez-vous qu'une connexion TCP est bidirectionnelle, chaque direction étant indépendante de l'autre).
Quand cette fonctionnalité fut ajoutée à Apache, elle causa une
      avalanche de problèmes sur plusieurs versions d'Unix à cause d'une
      implémentation à courte vue. La spécification TCP ne précise pas que
      l'état FIN_WAIT_2 possède un temps de réponse mais elle ne
      l'exclut pas. Sur les systèmes qui n'introduisent pas ce temps de
      réponse, Apache 1.2 induit de nombreux blocages définitifs de socket
      dans l'état FIN_WAIT_2. On peut eviter ceci dans de nombreux
      cas tout simplement en mettant à jour TCP/IP avec le dernier patch mis à
      disposition par le fournisseur. Dans les cas où le fournisseur n'a
      jamais fourni de patch (par exemple, SunOS4 -- bien que les utilisateurs
      possédant une license source puissent le patcher eux-mêmes), nous avons
      décidé de désactiver cette fonctionnalité.
Il y a deux méthodes pour arriver à ce résultat. La première est
      l'option de socket SO_LINGER. Mais le sort a voulu que cette
      solution ne soit jamais implémentée correctement dans la plupart des
      piles TCP/IP. Et même dans les rares cas où cette solution a été
      implémentée correctement (par exemple Linux 2.0.31), elle se
      montre beaucoup plus gourmande (en temps processeur) que la solution
      suivante.
Pour la plus grande partie, Apache implémente cette solution à l'aide
      d'une fonction appelée lingering_close (définie dans
      http_main.c). La fonction ressemble approximativement à
      ceci :
        void lingering_close (int s)
        {
          char junk_buffer[2048];
          
          /* shutdown the sending side */
          shutdown (s, 1);
          signal (SIGALRM, lingering_death);
          alarm (30);
          for (;;) {
            select (s for reading, 2 second timeout);
            if (error) break;
            if (s is ready for reading) {
              if (read (s, junk_buffer, sizeof (junk_buffer)) <= 0) {
                break;
              }
              /* just toss away whatever is here */
            }
          }
          
          close (s);
        }
      Ceci ajoute naturellement un peu de charge à la fin d'une connexion,
      mais s'avère nécessaire pour une implémentation fiable. Comme HTTP/1.1
      est de plus en plus présent et que toutes les connexions sont
      persistentes, la charge sera amortie par la multiplicité des requêtes.
      Si vous voulez jouer avec le feu en désactivant cette fonctionnalité,
      vous pouvez définir NO_LINGCLOSE, mais c'est fortement
      déconseillé. En particulier, comme les connexions persistantes en
      pipeline de HTTP/1.1 commencent à être utilisées,
      lingering_close devient une absolue nécessité (et les
      
      connexions en pipeline sont plus rapides ; vous avez donc tout
      intérêt à les supporter).
Les processus parent et enfants d'Apache communiquent entre eux à
      l'aide d'un objet appelé "Tableau de bord" (Scoreboard). Idéalement, cet
      échange devrait s'effectuer en mémoire partagée. Pour les systèmes
      d'exploitation auxquels nous avons eu accès, ou pour lesquels nous avons
      obtenu des informations suffisamment détaillées pour effectuer un
      portage, cet échange est en général implémenté en utilisant la mémoire
      partagée. Pour les autres, on utilise par défaut un fichier d'échange sur
      disque. Le fichier d'échange sur disque est non seulement lent, mais
      aussi peu fiable (et propose moins de fonctionnalités). Recherchez dans
      le fichier src/main/conf.h correspondant à votre
      architecture soit USE_MMAP_SCOREBOARD, soit
      USE_SHMGET_SCOREBOARD. La définition de l'un des deux
      (ainsi que leurs compagnons respectifs HAVE_MMAP et
      HAVE_SHMGET), active le code fourni pour la mémoire
      partagée. Si votre système propose une autre solution pour la gestion de
      la mémoire partagée, éditez le fichier src/main/http_main.c
      et ajoutez la portion de code nécessaire pour pouvoir l'utiliser dans
      Apache (Merci de nous envoyer aussi le patch correspondant).
Si vous n'avez pas l'intention d'utiliser les modules chargés
      dynamiquement (ce qui est probablement le cas si vous êtes en train de
      lire ce document afin de personnaliser votre serveur en recherchant le
      moindre des gains en performances), vous pouvez ajouter la définition
      -DDYNAMIC_MODULE_LIMIT=0 à la construction de votre serveur.
      Ceci aura pour effet de libérer la mémoire RAM allouée pour le
      chargement dynamique des modules.
Voici la trace d'un appel système d'Apache 2.0.38 avec le MPM worker sous Solaris 8. Cette trace a été collectée à l'aide de la commande :
      truss -l -p httpd_child_pid.
    
L'option -l demande à truss de tracer l'ID du LWP
    (lightweight process--la version de Solaris des threads niveau noyau) qui
    invoque chaque appel système.
Les autres systèmes peuvent proposer des utilitaires de traçage
    des appels système différents comme strace,
    ktrace, ou par. Ils produisent cependant tous une
    trace similaire.
Dans cette trace, un client a demandé un fichier statique de 10 ko au démon httpd. Le traçage des requêtes pour des contenus non statiques ou comportant une négociation de contenu a une présentation différente (et même assez laide dans certains cas).
/67: accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...) /67: accept(3, 0x00200BEC, 0x00200C0C, 1) = 9
Dans cette trace, le thread à l'écoute s'exécute à l'intérieur de LWP #67.
accept(2). Sur
    cette plateforme spécifique, le MPM worker utilise un accept non sérialisé
    par défaut sauf s'il est en écoute sur des ports multiples./65: lwp_park(0x00000000, 0) = 0 /67: lwp_unpark(65, 1) = 0
Après avoir accepté la connexion, le thread à l'écoute réactive un thread du worker pour effectuer le traitement de la requête. Dans cette trace, le thread du worker qui traite la requête est associé à LWP #65.
/65: getsockname(9, 0x00200BA4, 0x00200BC4, 1) = 0
Afin de pouvoir implémenter les hôtes virtuels, Apache doit connaître
    l'adresse du socket local utilisé pour accepter la connexion. On pourrait
    supprimer cet appel dans de nombreuses situations (par exemple dans le cas
    où il n'y a pas d'hôte virtuel ou dans le cas où les directives
    Listen contiennent des adresses
    sans caractères de substitution). Mais aucun effort n'a été accompli à ce
    jour pour effectuer ces optimisations.
/65: brk(0x002170E8) = 0 /65: brk(0x002190E8) = 0
L'appel brk(2) alloue de la mémoire dans le tas. Ceci est
    rarement visible dans une trace d'appel système, car le démon httpd
    utilise des allocateurs mémoire de son cru (apr_pool et
    apr_bucket_alloc) pour la plupart des traitements de requêtes.
    Dans cette trace, le démon httpd vient juste de démarrer, et il doit
    appeler malloc(3) pour réserver les blocs de mémoire
    nécessaires à la création de ses propres allocateurs de mémoire.
/65: fcntl(9, F_GETFL, 0x00000000) = 2 /65: fstat64(9, 0xFAF7B818) = 0 /65: getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B910, 2190656) = 0 /65: fstat64(9, 0xFAF7B818) = 0 /65: getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B914, 2190656) = 0 /65: setsockopt(9, 65535, 8192, 0xFAF7B918, 4, 2190656) = 0 /65: fcntl(9, F_SETFL, 0x00000082) = 0
Ensuite, le thread de worker passe la connexion du client (descripteur
    de fichier 9) en mode non blocant. Les appels setsockopt(2)
    et getsockopt(2) constituent un effet de bord de la manière
    dont la libc de Solaris utilise fcntl(2) pour les sockets.
/65: read(9, " G E T / 1 0 k . h t m".., 8000) = 97
Le thread de worker lit la requête du client.
/65:    stat("/var/httpd/apache/httpd-8999/htdocs/10k.html", 0xFAF7B978) = 0
/65:    open("/var/httpd/apache/httpd-8999/htdocs/10k.html", O_RDONLY) = 10Ce démon httpd a été configuré avec les options
    Options FollowSymLinks et AllowOverride None. Il
    n'a donc ni besoin d'appeler lstat(2) pour chaque répertoire
    du chemin du fichier demandé, ni besoin de vérifier la présence de fichiers
    .htaccess. Il appelle simplement stat(2) pour
    vérifier d'une part que le fichier existe, et d'autre part que c'est un
    fichier régulier, et non un répertoire.
/65: sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C) = 10269
Dans cet exemple, le démon httpd peut envoyer l'en-tête de la réponse
    HTTP et le fichier demandé à l'aide d'un seul appel système
    sendfilev(2). La sémantique de sendfile varie en fonction des
    systèmes d'exploitation. Sur certains autres systèmes, il faut faire un
    appel à write(2) ou writev(2) pour envoyer les
    en-têtes avant d'appeler sendfile(2).
/65: write(4, " 1 2 7 . 0 . 0 . 1 - ".., 78) = 78
Cet appel à write(2) enregistre la requête dans le journal
    des accès. Notez qu'une des choses manquant à cette trace est un appel à
    time(2). A la différence d'Apache 1.3, Apache 2.x utilise
    gettimeofday(3) pour consulter l'heure. Sur certains systèmes
    d'exploitation, comme Linux ou Solaris, gettimeofday est
    implémenté de manière optimisée de telle sorte qu'il consomme moins de
    ressources qu'un appel système habituel.
/65: shutdown(9, 1, 1) = 0 /65: poll(0xFAF7B980, 1, 2000) = 1 /65: read(9, 0xFAF7BC20, 512) = 0 /65: close(9) = 0
Le thread de worker effectue une fermeture "en prenant son temps" (lingering close) de la connexion.
/65: close(10) = 0 /65: lwp_park(0x00000000, 0) (sleeping...)
Enfin, le thread de worker ferme le fichier qu'il vient de délivrer et se bloque jusqu'à ce que le thread en écoute lui assigne une autre connexion.
/67: accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)
Pendant ce temps, le thread à l'écoute peut accepter une autre connexion
    à partir du moment où il a assigné la connexion présente à un thread de
    worker (selon une certaine logique de contrôle de flux dans le MPM worker
    qui impose des limites au thread à l'écoute si tous les threads de worker
    sont occupés). Bien que cela n'apparaisse pas dans cette trace,
    l'accept(2) suivant peut (et le fait en général, en situation
    de charge élevée) s'exécuter en parallèle avec le traitement de la
    connexion qui vient d'être acceptée par le thread de worker.