Meilleures pratiques en production : performances et fiabilité
Cet article traite des meilleures pratiques en termes de performances et de fiabilité pour les applications Express déployées en production.
La prĂ©sente rubrique sâinscrit clairement dans le concept âdevopsâ, qui couvre Ă la fois le dĂ©veloppement traditionnel et lâexploitation. Ainsi, les informations se divisent en deux parties :
- Things to do in your code (the dev part):
- Utiliser le middleware pour exploiter les fichiers statiques
- Asynchronous Error Handling in Express with Promises, Generators and ES7
- Do logging correctly
- Traiter correctement les exceptions
- A faire dans votre environnement/configuration (partie exploitation, âopsâ).
- DĂ©finir NODE_ENV sur âproductionâ
- Vérifier que votre application redémarre automatiquement
- Exécuter votre application dans un cluster
- Cache request results
- Utilisation de StrongLoop PM avec un équilibreur de charge Nginx
- Encore mieux, utilisez un proxy inverse pour exploiter les fichiers statiques ; pour plus dâinformations, voir Utiliser un proxy inverse.
A faire dans votre code
Les actions suivantes peuvent ĂȘtre rĂ©alisĂ©es dans votre code afin dâamĂ©liorer les performances de votre application :
- Utiliser le middleware pour exploiter les fichiers statiques
- Asynchronous Error Handling in Express with Promises, Generators and ES7
- Do logging correctly
- Traiter correctement les exceptions
Utiliser la compression gzip
La compression Gzip peut considĂ©rablement rĂ©duire la taille du corps de rĂ©ponse et ainsi augmenter la vitesse dâune application Web. Utilisez le middleware compression pour la compression gzip dans votre application Express. Par exemple :
const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
Pour un site Web en production dont le trafic est Ă©levĂ©, la meilleure mĂ©thode pour mettre en place la compression consiste Ă lâimplĂ©menter au niveau dâun proxy inverse (voir Utiliser un proxy inverse). Dans ce cas, vous nâavez pas besoin dâutiliser le middleware compression. Pour plus de dĂ©tails sur lâactivation de la compression gzip dans Nginx, voir Module ngx_http_gzip_module dans la documentation Nginx.
Ne pas utiliser les fonctions synchrones
Les fonctions et les mĂ©thodes synchrones ralentissent le processus dâexĂ©cution jusquâĂ leur retour. Un simple appel Ă une fonction synchrone peut revenir en quelques microsecondes ou millisecondes ; pour les sites Web dont le trafic est Ă©levĂ©, ces appels sâadditionnent et rĂ©duisent les performances de lâapplication. Evitez de les utiliser en production.
Bien que Node et plusieurs modules mettent Ă disposition les versions synchrone et asynchrone de leurs fonctions, utilisez toujours la version asynchrone en production. Lâutilisation dâune fonction synchrone nâest justifiĂ©e que lors du dĂ©marrage initial.
You can use the --trace-sync-io
command-line flag to print a warning and a stack trace whenever your application uses a synchronous API. Bien entendu vous nâutiliserez pas rĂ©ellement cette option en production, mais plutĂŽt pour vĂ©rifier que votre code est prĂȘt pour la phase production. Pour plus dâinformations, voir Weekly update for io.js 2.1.0.
Procéder à une journalisation correcte
En rĂšgle gĂ©nĂ©rale, vous utilisez la journalisation Ă partir de votre application Ă deux fins : le dĂ©bogage et la journalisation de lâactivitĂ© de votre application (principalement tout le reste). Lâutilisation de console.log()
ou de console.err()
pour imprimer des messages de journal sur le terminal est une pratique courante en développement. But these functions are synchronous when the destination is a terminal or a file, so they are not suitable for production, unless you pipe the output to another program.
Pour le débogage
Si vous utilisez la journalisation Ă des fins de dĂ©bogage, utilisez un module de dĂ©bogage spĂ©cial tel que debug plutĂŽt que dâutiliser console.log()
. Ce module vous permet dâutiliser la variable dâenvironnement DEBUG pour contrĂŽler les messages de dĂ©bogage envoyĂ©s Ă console.err()
, le cas échéant. Pour que votre application reste exclusivement asynchrone, vous devrez toujours diriger console.err()
vers un autre programme. Mais bon, vous nâallez pas vraiment procĂ©der Ă un dĂ©bogage en production, nâest-ce pas ?
Pour journaliser lâactivitĂ© de votre application
If youâre logging app activity (for example, tracking traffic or API calls), instead of using console.log()
, use a logging library like Pino, which is the fastest and most efficient option available.
Traiter correctement les exceptions
Les applications Node plantent lorsquâelles tombent sur une exception non interceptĂ©e. Si vous ne traitez pas les exceptions et ne prenez pas les dĂ©cisions appropriĂ©es, votre application Express plantera et sera dĂ©connectĂ©e. Si vous suivez les conseils de la rubrique ci-dessous intitulĂ©e VĂ©rifier que votre application redĂ©marre automatiquement, votre application pourra ĂȘtre restaurĂ©e suite Ă un plantage. Le dĂ©lai de dĂ©marrage des applications Express est heureusement court en rĂšgle gĂ©nĂ©rale. Vous souhaitez toutefois Ă©viter tout plantage en prioritĂ© et pour ce faire, vous devez traiter les exceptions correctement.
Pour vérifier que vous traitez toutes les exceptions, procédez comme suit :
Avant de sâimmerger dans les rubriques qui suivent, il est conseillĂ© de possĂ©der des connaissances de base concernant le traitement des erreurs Node/Express, Ă savoir lâutilisation des rappels âerror-firstâ et la propagation des erreurs dans le middleware. Node utilise la convention de ârappel error-firstâ pour renvoyer les erreurs issues des fonctions asynchrones, dans laquelle le premier paramĂštre de la fonction callback est lâobjet error, suivi par les donnĂ©es de rĂ©sultat dans les paramĂštres suivants. Pour nâindiquer aucune erreur, indiquez null comme premier paramĂštre. La fonction de rappel doit suivre la convention de rappel âerror-firstâ de sorte Ă traiter lâerreur de maniĂšre significative. Dans Express, la meilleure pratique consiste Ă utiliser la fonction next() pour propager les erreurs via la chaĂźne du middleware.
Pour plus dâinformations sur les bases du traitement des erreurs, voir :
Utiliser try-catch
Try-catch est un Ă©lĂ©ment de langage JavaScript que vous pouvez utiliser pour intercepter les exceptions dans le code synchrone. Utilisez try-catch pour traiter les erreurs dâanalyse JSON, comme indiquĂ© ci-dessous, par exemple.
Voici un exemple dâutilisation de try-catch pour traiter une exception potentielle de plantage de processus. Cette fonction middleware accepte un paramĂštre de zone de requĂȘte nommĂ© âparamsâ qui est un objet JSON.
app.get('/search', (req, res) => {
// Simulating async operation
setImmediate(() => {
const jsonStr = req.query.params
try {
const jsonObj = JSON.parse(jsonStr)
res.send('Success')
} catch (e) {
res.status(400).send('Invalid JSON string')
}
})
})
Toutefois, try-catch ne fonctionne que dans le code synchrone. Etant donnĂ© que la plateforme Node est principalement asynchrone (en particulier dans un environnement de production), try-catch nâinterceptera pas beaucoup dâexceptions.
Utiliser des promesses
When an error is thrown in an async
function or a rejected promise is awaited inside an async
function, those errors will be passed to the error handler as if calling next(err)
app.get('/', async (req, res, next) => {
const data = await userData() // If this promise fails, it will automatically call `next(err)` to handle the error.
res.send(data)
})
app.use((err, req, res, next) => {
res.status(err.status ?? 500).send({ error: err.message })
})
Also, you can use asynchronous functions for your middleware, and the router will handle errors if the promise fails, for example:
app.use(async (req, res, next) => {
req.locals.user = await getUser(req)
next() // This will be called if the promise does not throw an error.
})
Best practice is to handle errors as close to the site as possible. So while this is now handled in the router, itâs best to catch the error in the middleware and handle it without relying on separate error-handling middleware.
A ne pas faire
Vous ne devriez pas Ă©couter lâĂ©vĂ©nement uncaughtException
, Ă©mis lorsquâune exception remonte vers la boucle dâĂ©vĂ©nements. Lâajout dâun programme dâĂ©coute dâĂ©vĂ©nement pour uncaughtException
va modifier le comportement par dĂ©faut du processus qui rencontre une exception ; le processus va continuer Ă sâexĂ©cuter malgrĂ© lâexception. Cela pourrait ĂȘtre un bon moyen dâempĂȘcher votre application de planter, mais continuer Ă exĂ©cuter lâapplication aprĂšs une exception non interceptĂ©e est une pratique dangereuse qui nâest pas recommandĂ©e, Ă©tant donnĂ© que lâĂ©tat du processus devient peu fiable et imprĂ©visible.
De plus, lâutilisation dâuncaughtException
est officiellement reconnue comme étant rudimentaire et il a été proposé de le supprimer. Ecouter uncaughtException
nâest quâune mauvaise idĂ©e. VoilĂ pourquoi nous recommandons dâutiliser plusieurs processus et superviseurs Ă la place : faire planter son application et la redĂ©marrer est souvent plus sĂ»r que de la restaurer aprĂšs une erreur.
Lâutilisation de domain nâest Ă©galement pas recommandĂ©e. Ce module obsolĂšte ne rĂ©sout globalement pas le problĂšme.
Things to do in your environment / setup
{#in-environment}
Les actions suivantes peuvent ĂȘtre rĂ©alisĂ©es dans votre environnement systĂšme afin dâamĂ©liorer les performances de votre application :
- DĂ©finir NODE_ENV sur âproductionâ
- Vérifier que votre application redémarre automatiquement
- Exécuter votre application dans un cluster
- Cache request results
- Utilisation de StrongLoop PM avec un équilibreur de charge Nginx
- Encore mieux, utilisez un proxy inverse pour exploiter les fichiers statiques ; pour plus dâinformations, voir Utiliser un proxy inverse.
DĂ©finir NODE_ENV sur âproductionâ
La variable dâenvironnement NODE_ENV spĂ©cifie lâenvironnement dans lequel une application sâexĂ©cute (en rĂšgle gĂ©nĂ©rale, dĂ©veloppement ou production). One of the simplest things you can do to improve performance is to set NODE_ENV to production
.
En dĂ©finissant NODE_ENV sur âproductionâ, Express :
- Met en cache les modĂšles dâaffichage.
- Met en cache les fichiers CSS gĂ©nĂ©rĂ©s Ă partir dâextensions CSS.
- GĂ©nĂšre moins de messages dâerreur prolixes.
Tests indicate that just doing this can improve app performance by a factor of three!
Si vous avez besoin dâĂ©crire du code spĂ©cifique Ă un environnement, vous pouvez vĂ©rifier la valeur de NODE_ENV avec process.env.NODE_ENV
. Sachez que la vĂ©rification de la valeur de nâimporte quelle variable dâenvironnement pĂ©nalise les performances et devrait donc ĂȘtre effectuĂ©e avec modĂ©ration.
En dĂ©veloppement, vous dĂ©finissez gĂ©nĂ©ralement les variables dâenvironnement dans votre shell interactif, Ă lâaide de export
ou de votre fichier .bash_profile
par exemple. But in general, you shouldnât do that on a production server; instead, use your OSâs init system (systemd). La section qui suit fournit des dĂ©tails sur lâutilisation de votre systĂšme init en gĂ©nĂ©ral, mais la dĂ©finition de NODE_ENV est tellement importante pour les performances (et facile Ă rĂ©aliser), quâelle est mise en Ă©vidence ici.
Avec systemd, utilisez la directive Environment
dans votre fichier dâunitĂ©. Par exemple :
# /etc/systemd/system/myservice.service
Environment=NODE_ENV=production
For more information, see Using Environment Variables In systemd Units.
Vérifier que votre application redémarre automatiquement
En production, vous ne souhaitez jamais que votre application soit dĂ©connectĂ©e. Vous devez donc veiller Ă ce quâelle redĂ©marre si elle plante et si le serveur plante. MĂȘme si vous espĂ©rez que cela nâarrive pas, vous devez en rĂ©alitĂ© considĂ©rer ces deux Ă©ventualitĂ©s en :
- Utilisant un gestionnaire de processus pour redĂ©marrer lâapplication (et Node) lorsquâelle plante.
- Utilisant le systĂšme init fourni par votre systĂšme dâexploitation pour redĂ©marrer le gestionnaire de processus lorsque le systĂšme dâexploitation plante. Vous pouvez Ă©galement utiliser le systĂšme init sans gestionnaire de processus.
Les applications Node plantent si elles tombent sur une exception non interceptĂ©e. Avant toute chose, vĂ©rifiez que votre application est correctement testĂ©e et quâelle traite toutes les exceptions (voir Traiter correctement les exceptions pour plus de dĂ©tails). En cas dâĂ©chec, mettez en place un mĂ©canisme qui garantit que si et lorsque votre application plante, elle redĂ©marre automatiquement.
Utiliser un gestionnaire de processus
En développement, vous avez simplement démarré votre application à partir de la ligne de commande avec node server.js
ou une instruction similaire. En production, cela vous mĂšnera droit au dĂ©sastre. Si lâapplication plante, elle sera dĂ©connectĂ©e tant que vous ne la redĂ©marrerez pas. Pour garantir que votre application redĂ©marre si elle plante, utilisez un gestionnaire de processus. Un gestionnaire de processus est un âconteneurâ dâapplications qui facilite le dĂ©ploiement, offre une haute disponibilitĂ© et vous permet de gĂ©rer lâapplication lors de son exĂ©cution.
En plus de redĂ©marrer votre application lorsquâelle plante, un gestionnaire de processus peut vous permettre :
- De vous informer sur les performances dâexĂ©cution et la consommation des ressources.
- De modifier les paramĂštres de maniĂšre dynamique afin dâamĂ©liorer les performances.
- Control clustering (pm2).
Historically, it was popular to use a Node.js process manager like PM2. See their documentation if you wish to do this. However, we recommend using your init system for process management.
Utiliser un systĂšme init
Le niveau de fiabilitĂ© suivant consiste Ă garantir que votre application redĂ©marre lorsque le serveur redĂ©marre. Les systĂšmes peuvent toujours tomber en panne pour divers motifs. Pour garantir que votre application redĂ©marre si le serveur plante, utilisez le systĂšme init intĂ©grĂ© Ă votre systĂšme dâexploitation. The main init system in use today is systemd.
Vous pouvez utiliser les systĂšmes init de deux maniĂšres dans votre application Express :
- ExĂ©cutez votre application dans un gestionnaire de processus, puis installez le gestionnaire de processus en tant que service avec le systĂšme init. Le gestionnaire de processus va redĂ©marrer votre application lorsquâelle plantera et le systĂšme init va redĂ©marrer le gestionnaire de processus lorsque le systĂšme dâexploitation redĂ©marrera. Il sâagit de la mĂ©thode recommandĂ©e.
- ExĂ©cutez votre application (et Node) directement avec le systĂšme init. Cette mĂ©thode est plus simple, mais vous ne profitez pas des avantages dâun gestionnaire de processus.
Systemd
Systemd est un systÚme Linux et un gestionnaire de services. La plupart des distributions Linux principales ont adopté systemd comme leur systÚme init par défaut.
Un fichier de configuration de service systemd est appelĂ© fichier dâunitĂ© et porte lâextension .service. Voici un exemple de fichier dâunitĂ© permettant de gĂ©rer une application Node directement (remplacez le texte en gras par les valeurs appropriĂ©es Ă votre systĂšme et votre application) : Replace the values enclosed in <angle brackets>
for your system and app:
[Unit]
Description=<Awesome Express App>
[Service]
Type=simple
ExecStart=/usr/local/bin/node </projects/myapp/index.js>
WorkingDirectory=</projects/myapp>
User=nobody
Group=nogroup
# Environment variables:
Environment=NODE_ENV=production
# Allow many incoming connections
LimitNOFILE=infinity
# Allow core dumps for debugging
LimitCORE=infinity
StandardInput=null
StandardOutput=syslog
StandardError=syslog
Restart=always
[Install]
WantedBy=multi-user.target
Pour plus dâinformations sur systemd, voir la page dâaide de systemd.
Exécuter votre application dans un cluster
Dans un systĂšme multicoeur, vous pouvez augmenter les performances dâune application Node en lançant un cluster de processus. Un cluster exĂ©cute plusieurs instances de lâapplication, idĂ©alement une instance sur chaque coeur dâUC, rĂ©partissant ainsi la charge et les tĂąches entre les instances.
IMPORTANT : Ă©tant donnĂ© que les instances dâapplication sâexĂ©cutent en tant que processus distincts, elles ne partagent pas le mĂȘme espace mĂ©moire. Autrement dit, les objets sont en local sur chaque instance de lâapplication. Par consĂ©quent, vous ne pouvez pas conserver lâĂ©tat dans le code de lâapplication. Vous pouvez toutefois utiliser un magasin de donnĂ©es en mĂ©moire tel que Redis pour stocker les donnĂ©es de session et lâĂ©tat. Cette fonctionnalitĂ© sâapplique essentiellement Ă toutes les formes de mise Ă lâĂ©chelle horizontale, que la mise en cluster soit effectuĂ©e avec plusieurs processus ou avec plusieurs serveurs physiques.
Dans les applications mises en cluster, les processus de traitement peuvent planter individuellement sans impacter le reste des processus. Outre les avantages en termes de performance, lâisolement des pannes constitue une autre raison dâexĂ©cuter un cluster de processus dâapplication. Chaque fois quâun processus de traitement plante, veillez toujours Ă consigner lâĂ©vĂ©nement et Ă gĂ©nĂ©ration un nouveau processus Ă lâaide de cluster.fork().
Utilisation du module cluster de Node
Clustering is made possible with Nodeâs cluster module. Ce module permet Ă un processus maĂźtre de gĂ©nĂ©rer des processus de traitement et de rĂ©partir les connexions entrantes parmi ces processus.
Using PM2
If you deploy your application with PM2, then you can take advantage of clustering without modifying your application code. You should ensure your application is stateless first, meaning no local data is stored in the process (such as sessions, websocket connections and the like).
When running an application with PM2, you can enable cluster mode to run it in a cluster with a number of instances of your choosing, such as the matching the number of available CPUs on the machine. You can manually change the number of processes in the cluster using the pm2
command line tool without stopping the app.
To enable cluster mode, start your application like so:
# Start 4 worker processes
$ pm2 start npm --name my-app -i 4 -- start
# Auto-detect number of available CPUs and start that many worker processes
$ pm2 start npm --name my-app -i max -- start
This can also be configured within a PM2 process file (ecosystem.config.js
or similar) by setting exec_mode
to cluster
and instances
to the number of workers to start.
Once running, the application can be scaled like so:
# Add 3 more workers
$ pm2 scale my-app +3
# Scale to a specific number of workers
$ pm2 scale my-app 2
For more information on clustering with PM2, see Cluster Mode in the PM2 documentation.
Mettre en cache les rĂ©sultats dâune demande
Pour amĂ©liorer les performances en production, vous pouvez Ă©galement mettre en cache le rĂ©sultat des demandes, de telle sorte que votre application ne rĂ©pĂšte pas lâopĂ©ration de traitement de la mĂȘme demande plusieurs fois.
Use a caching server like Varnish or Nginx (see also Nginx Caching) to greatly improve the speed and performance of your app.
Utiliser un équilibreur de charge
Quel que soit le niveau dâoptimisation dâune application, une instance unique ne peut traiter quâun volume limitĂ© de charge et de trafic. Pour faire Ă©voluer une application, vous pouvez exĂ©cuter plusieurs instances de cette application et rĂ©partir le trafic en utilisant un Ă©quilibreur de charge. La configuration dâun Ă©quilibreur de charge peut amĂ©liorer les performances et la vitesse de votre application et lui permettre dâĂ©voluer plus largement quâavec une seule instance.
Un Ă©quilibreur de charge est gĂ©nĂ©ralement un proxy inverse qui orchestre le trafic entrant et sortant de plusieurs instances dâapplication et serveurs. You can easily set up a load balancer for your app by using Nginx or HAProxy.
Avec lâĂ©quilibrage de charge, vous devrez peut-ĂȘtre vĂ©rifier que les demandes associĂ©es Ă un ID de session spĂ©cifique sont connectĂ©es au processus dont elles sont issues. Ce procĂ©dĂ© est appelĂ© affinitĂ© de session (ou sessions persistantes) et peut ĂȘtre effectuĂ© en utilisant un magasin de donnĂ©es tel que Redis pour les donnĂ©es de session (en fonction de votre application), comme dĂ©crit ci-dessus. Pour en savoir plus, voir Using multiple nodes.
Utiliser un proxy inverse
Un proxy inverse accompagne une application Web et exĂ©cute des opĂ©rations de prise en charge sur les demandes, en plus de diriger les demandes vers lâapplication. Il peut gĂ©rer les pages dâerreur, la compression, la mise en cache, le dĂ©pĂŽt de fichiers et lâĂ©quilibrage de charge entre autres.
La transmission de tĂąches qui ne requiĂšrent aucune connaissance de lâĂ©tat dâapplication Ă un proxy inverse permet Ă Express de rĂ©aliser des tĂąches dâapplication spĂ©cialisĂ©es. For this reason, it is recommended to run Express behind a reverse proxy like Nginx or HAProxy in production.
Edit this page