Optimisation des performances Nginx : optimiser les processus et les connexions des workers

Ajustez les processus workers, les connexions workers, les limites de fichiers, le comportement keepalive et la capacité en amont de Nginx sans avoir à deviner.

Optimisation des performances Nginx : optimiser les processus et les connexions des workers

L'optimisation des performances de Nginx commence souvent par deux paramètres : les processus workers et les connexions workers. Ces paramètres déterminent le nombre de requêtes que votre serveur peut traiter simultanément, de sorte que de petites erreurs peuvent se manifester par des pages lentes, des téléchargements bloqués ou des erreurs de connexion lors des pics de trafic.

La bonne nouvelle est que vous n'avez pas besoin de deviner au hasard. Vous pouvez optimiser Nginx en faisant correspondre son modèle de workers à votre CPU, vos limites de fichiers, votre modèle de trafic et le comportement de votre application en amont.

Comment les workers Nginx gèrent le trafic

Nginx utilise un processus maître et un ou plusieurs processus workers. Le processus maître lit la configuration, démarre les workers et gère les rechargements. Les workers effectuent le traitement réel des requêtes.

Chaque worker peut gérer de nombreuses connexions car Nginx utilise un modèle piloté par les événements. C'est différent des anciens serveurs web qui nécessitaient souvent un processus ou un thread par requête. Un worker Nginx peut conserver des milliers de connexions keepalive inactives si le système d'exploitation le permet.

Les deux directives principales sont généralement placées dans /etc/nginx/nginx.conf :

worker_processes auto;

events {
    worker_connections 1024;
}

worker_processes auto; indique à Nginx de créer un worker pour chaque cœur de CPU disponible. Pour la plupart des serveurs Linux modernes, c'est le bon point de départ. Cela évite de coder en dur une valeur qui deviendrait incorrecte lorsque vous redimensionnez une machine virtuelle.

worker_connections définit le nombre maximum de connexions simultanées que chaque worker peut ouvrir. La limite supérieure approximative est :

worker_processes * worker_connections

Si vous avez 4 workers et 4096 connexions workers, le maximum théorique est de 16 384 connexions. Dans la réalité, le nombre utilisable est inférieur car le trafic de proxy inverse peut utiliser à la fois les connexions client et les connexions en amont.

Par exemple, si Nginx proxy le trafic vers une application Node.js, une requête utilisateur peut consommer une connexion client plus une connexion en amont. Cela signifie que 16 384 connexions ouvertes peuvent prendre en charge environ 8 000 requêtes proxy actives, selon le keepalive et le timing des requêtes.

Choisir les valeurs des processus et connexions workers

Commencez par worker_processes auto sauf si vous avez une raison spécifique de ne pas le faire. Définir manuellement cette valeur à un nombre supérieur au nombre de CPU aide rarement. Cela peut augmenter les changements de contexte et aggraver les performances sous charge.

Ensuite, ajustez worker_connections en fonction de la concurrence attendue. Un outil interne peu sollicité peut être satisfaisant avec 1024. Un site web public derrière un équilibreur de charge peut nécessiter 4096, 8192 ou plus.

Une base de référence pratique pour de nombreux serveurs de production ressemble à ceci :

worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    multi_accept on;
}

worker_rlimit_nofile augmente la limite de descripteurs de fichiers disponible pour les workers Nginx. C'est important car chaque socket réseau utilise un descripteur de fichier. Si la limite du système d'exploitation reste basse, augmenter worker_connections seul ne servira à rien.

Vous devez également vérifier la limite du gestionnaire de services. Sur les systèmes systemd, Nginx peut nécessiter une surcharge telle que :

[Service]
LimitNOFILE=65535

Après avoir modifié les limites systemd, rechargez systemd et redémarrez Nginx. Pour une référence de commandes plus large, consultez Commandes de contrôle du service Nginx.

Soyez prudent avec multi_accept on. Cela permet à un worker d'accepter autant de nouvelles connexions que possible après avoir reçu une notification de disponibilité. Cela peut aider lors des pics, mais ce n'est pas magique. Si votre application en amont est lente, accepter les connexions plus rapidement peut simplement remplir les files d'attente plus rapidement.

Limites du système d'exploitation qui affectent Nginx

Les paramètres Nginx reposent sur les limites de Linux. Si ces limites sont trop faibles, Nginx atteindra un plafond même si sa propre configuration semble généreuse.

Vérifiez ces domaines lors de l'optimisation :

  • Limite de fichiers ouverts pour le processus Nginx
  • Paramètres de backlog réseau du noyau
  • Disponibilité des ports éphémères pour le trafic proxy lourd
  • Comportement keepalive en amont
  • Valeurs de délai d'inactivité de l'équilibreur de charge

La limite de fichiers ouverts est le blocage le plus courant. Si Nginx enregistre des messages comme worker_connections are not enough ou too many open files, vous devez examiner à la fois les limites Nginx et systemd.

Les paramètres de backlog sont importants lorsque de nombreux clients se connectent en même temps. Si la file d'attente d'acceptation du noyau se remplit, les utilisateurs peuvent voir des délais de connexion même si l'utilisation du CPU semble normale. Des valeurs telles que net.core.somaxconn et net.ipv4.tcp_max_syn_backlog sont souvent examinées lors de l'optimisation du trafic élevé.

Ne copiez pas de grandes valeurs du noyau à partir d'exemples aléatoires sans les tester. Une petite équipe gérant un seul serveur API n'a pas besoin des mêmes paramètres qu'un nœud de périphérie CDN. Optimisez par étapes, mesurez et prenez des notes.

Il y a un autre détail qui piège les gens : la limite de connexion Nginx n'est pas la seule limite de connexion sur le chemin. Un équilibreur de charge cloud a des délais d'inactivité. Un environnement d'exécution de conteneur peut avoir des limites de traduction d'adresses réseau. L'application backend peut avoir son propre pool de workers ou de connexions à la base de données. Si Nginx peut accepter 20 000 connexions mais que l'application ne peut traiter que 200 requêtes simultanées, les utilisateurs attendront toujours.

C'est pourquoi une modification de l'optimisation des connexions doit inclure une vérification rapide de bout en bout. Exécutez un petit test de charge à partir d'un hôte extérieur au serveur, surveillez les connexions actives Nginx, et surveillez également le backend. Si la latence du backend augmente fortement alors que Nginx reste calme, le proxy fait son travail et la prochaine limite se trouve derrière lui.

Optimisation pour les charges de travail de proxy inverse

De nombreux serveurs Nginx agissent comme des proxys inverses devant des serveurs d'applications. Dans ce rôle, le comportement en amont compte autant que la capacité de Nginx.

Utilisez le keepalive en amont lorsque Nginx communique de manière répétée avec le même pool de backend :

upstream app_backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    location / {
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_pass http://app_backend;
    }
}

Cela réduit le coût d'ouverture de nouvelles connexions backend. C'est particulièrement utile lorsque votre application reçoit de nombreuses petites requêtes, comme des appels API depuis un tableau de bord.

Vérifiez également vos valeurs de délai d'attente. Des délais d'attente de proxy très longs peuvent maintenir les connexions workers occupées après la disparition des clients ou l'arrêt de réponse des applications. Des délais d'attente très courts peuvent interrompre des requêtes lentes légitimes. Faites correspondre les valeurs de délai d'attente à la charge de travail au lieu d'utiliser une valeur par défaut partout.

Un scénario pratique : votre site est rapide la plupart du temps mais ralentit lors de l'envoi d'une newsletter. Le CPU n'est qu'à 35%, mais les logs Nginx montrent des avertissements de connexion. Cela indique autre chose que le CPU brut et pointe vers les limites de connexion, les descripteurs de fichiers ou la mise en file d'attente en amont. Augmenter les connexions workers peut aider, mais seulement si l'application et le système d'exploitation peuvent supporter la charge supplémentaire.

Un autre scénario courant est une application de tableau de bord qui effectue de nombreux petits appels API depuis chaque onglet du navigateur. Dix personnes peuvent créer des centaines de requêtes courtes. Dans ce cas, le keepalive en amont est souvent plus important que le simple fait d'augmenter worker_connections, car la configuration TCP répétée vers le backend devient une surcharge inutile.

Pour un service de téléchargement de fichiers, l'histoire est différente. Un petit nombre d'utilisateurs peut maintenir les connexions ouvertes pendant longtemps tout en téléchargeant des fichiers volumineux. Vous pouvez avoir besoin de suffisamment de connexions workers pour les transferts de longue durée, mais vous devez également vérifier sendfile, le débit du disque, le débit du réseau et le comportement du délai d'attente du client.

Pour les applications WebSocket ou à long polling, les connexions inactives sont normales. Un nombre élevé de Waiting n'est pas automatiquement mauvais. La question est de savoir si ces connexions inactives laissent suffisamment de capacité pour les nouvelles requêtes et si l'utilisation de la mémoire reste prévisible.

Lire stub_status lors de l'optimisation

Le module stub_status vous donne un aperçu rapide du comportement des connexions :

Active connections: 291
server accepts handled requests
 1162447 1162447 4496426
Reading: 6 Writing: 17 Waiting: 268

Reading signifie que Nginx lit les en-têtes de requête. Un nombre élevé soutenu peut indiquer des clients lents, des en-têtes volumineux ou un modèle d'attaque. Writing signifie que Nginx envoie des réponses. Cela peut augmenter lorsque les clients sont lents à recevoir les données ou lorsque les réponses sont volumineuses. Waiting signifie des connexions keepalive inactives. Ce nombre peut être élevé sur les sites sains.

Les compteurs accepts et handled doivent généralement évoluer ensemble. Si les connexions acceptées augmentent mais que les connexions traitées sont en retard ou que des erreurs apparaissent, vérifiez les limites des workers et les limites des descripteurs de fichiers. Vérifiez également si le noyau abandonne les connexions avant que Nginx puisse les traiter.

Ces compteurs sont basiques, mais ils sont utiles car ils séparent la pression des connexions de la pression du CPU. Si les connexions actives sont faibles et que le CPU est élevé, le problème n'est probablement pas worker_connections. Si les connexions actives sont élevées et que le CPU est faible, les limites de connexion, le comportement keepalive, la mise en file d'attente en amont ou les clients lents deviennent plus probables.

Une configuration de base sécurisée

Pour un petit serveur de production, je préfère commencer de manière conservatrice et mesurer :

worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    multi_accept on;
}

http {
    keepalive_timeout 30s;
    keepalive_requests 1000;
}

Ce n'est pas une configuration universellement optimale. C'est un point de départ raisonnable pour de nombreuses charges de travail de proxy inverse normales. Une très petite VM peut nécessiter moins. Un proxy de périphérie très sollicité peut nécessiter beaucoup plus. L'important est que worker_connections et worker_rlimit_nofile soient alignés.

Après avoir appliqué une base de référence, comparez les métriques avant et après pendant un trafic similaire. Ne jugez pas un changement d'optimisation par une minute chanceuse après un rechargement. Regardez la latence p95 ou p99, le taux d'erreur, le CPU, la mémoire et la mise en file d'attente du backend sur une période suffisante pour voir le comportement réel.

Erreurs qui rendent l'optimisation des connexions aléatoire

La première erreur est de compter les requêtes au lieu des connexions. Un navigateur peut réutiliser une connexion pour plusieurs requêtes, et HTTP/2 peut multiplexer de nombreuses requêtes sur une seule connexion. Un client lent peut également maintenir une connexion ouverte tout en effectuant très peu de travail utile. Cela signifie que "nous n'avons que 500 requêtes par seconde" ne vous dit pas combien de connexions Nginx nécessite.

La deuxième erreur est d'oublier les connexions en amont. Si Nginx sert des fichiers statiques, la plupart des connexions sont orientées client. Si Nginx proxy vers une application, les requêtes actives nécessitent souvent aussi des sockets backend. Si vous utilisez le keepalive vers l'amont, certaines connexions backend restent ouvertes pour être réutilisées. C'est bien, mais cela consomme toujours des descripteurs de fichiers des deux côtés.

La troisième erreur est d'augmenter les limites Nginx sans vérifier l'application. Supposons que Nginx puisse maintenant accepter 12 000 connexions simultanées, mais que l'application ait 16 processus workers et un pool de base de données de 50 connexions. Nginx acceptera plus de travail que l'application ne peut en terminer. Les utilisateurs peuvent voir moins d'erreurs de connexion immédiates, mais la latence peut s'aggraver car les requêtes attendent plus longtemps dans les files d'attente.

La quatrième erreur est d'utiliser des délais d'attente keepalive longs partout. Le keepalive est utile car il évite les configurations TCP et TLS répétées. Mais un délai d'attente très long peut laisser de nombreux sockets inactifs ouverts après un pic de trafic. Sur un proxy de périphérie riche en mémoire, cela peut être acceptable. Sur une petite VM, cela peut évincer le travail actif. Si vous voyez un nombre énorme de Waiting et une faible réutilisation des requêtes, essayez un keepalive_timeout plus court et mesurez à nouveau.

Exemples de dépannage

Si le journal d'erreurs indique worker_connections are not enough, vérifiez la valeur configurée, le nombre de workers et la limite de fichiers du processus :

grep -R "worker_connections\\|worker_processes\\|worker_rlimit_nofile" /etc/nginx/nginx.conf /etc/nginx/conf.d
cat /proc/$(pgrep -o nginx)/limits | grep "open files"

La commande pgrep -o nginx trouve généralement le processus Nginx le plus ancien, qui est souvent le maître. Sur certains systèmes, vous pouvez préférer systemctl status nginx pour voir le PID principal.

Si le journal d'erreurs indique too many open files, n'augmentez pas seulement worker_connections. Le processus atteint sa limite de descripteurs. Ajoutez ou ajustez LimitNOFILE pour le service systemd, rechargez systemd et redémarrez Nginx pour que la nouvelle limite soit réellement appliquée :

sudo systemctl edit nginx
sudo systemctl daemon-reload
sudo systemctl restart nginx

Si les utilisateurs voient des délais d'attente mais que Nginx a du CPU de réserve et aucun avertissement de connexion, regardez derrière Nginx. Vérifiez le temps de réponse en amont dans les journaux d'accès. Vérifiez le pool de workers de l'application. Vérifiez les connexions à la base de données. Un proxy inverse peut accepter le trafic en douceur tandis que le véritable goulot d'étranglement est un backend saturé.

Si un pic provoque des réinitialisations de connexion avant que les requêtes n'apparaissent dans les journaux d'accès, le problème peut être antérieur au traitement des requêtes Nginx. Examinez les paramètres de backlog du noyau, les journaux de l'équilibreur de charge, les tables d'état du pare-feu et la protection contre les inondations SYN. Nginx ne peut pas enregistrer une requête qu'il n'a jamais reçue.

Comment tester les changements en toute sécurité

N'optimisez jamais la production en éditant et en espérant. Testez d'abord la syntaxe :

sudo nginx -t

Rechargez ensuite Nginx pour que les connexions actives soient gérées en douceur :

sudo systemctl reload nginx

Surveillez le journal d'erreurs après chaque modification :

sudo tail -f /var/log/nginx/error.log

Vous devez également surveiller la latence des requêtes, les taux 4xx et 5xx, les connexions actives, le CPU, la mémoire et le temps de réponse en amont. Un changement d'optimisation qui augmente la capacité de connexion mais augmente la latence de l'application peut ne pas être un véritable gain.

Pour des étapes de validation plus approfondies, consultez test des configurations Nginx.

Quand faire appel à un spécialiste

Faites appel à un ingénieur DevOps expérimenté ou à un spécialiste des performances web lorsque les erreurs Nginx persistent après une optimisation de base, lorsque les pics de trafic affectent les revenus, ou lorsque vous modifiez les paramètres réseau du noyau sur un système de production. La même chose s'applique si vous optimisez Nginx devant des flux de paiement, des systèmes de connexion ou des API critiques.

L'aide professionnelle est également utile lorsque le goulot d'étranglement n'est pas clair. Nginx peut sembler être le problème alors que le vrai problème est une base de données lente, un pool d'applications en amont épuisé, une couche de terminaison TLS surchargée ou un décalage de délai d'attente de l'équilibreur de charge.

Le point clé est simple : ajustez les processus workers pour correspondre au CPU, ajustez les connexions workers pour correspondre à la concurrence, et assurez-vous que les limites de fichiers Linux soutiennent les deux. Modifiez une couche à la fois, testez la configuration avant de recharger, et mesurez le trafic réel au lieu de vous fier aux maximums théoriques.