-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
docs: add worker threads and Node.js LTS guidance to performance best practices #2473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fb58cd2
6c847a2
5b0a373
d0ceedb
6b8b2b8
3ab126f
4b60e71
1985c1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,29 +5,11 @@ description: Discover performance and reliability best practices for Express app | |
|
|
||
| This article discusses performance and reliability best practices for Express applications deployed to production. | ||
|
|
||
| This topic clearly falls into the "devops" world, spanning both traditional development and operations. Accordingly, the information is divided into two parts: | ||
|
|
||
| - Things to do in your code (the dev part): | ||
| - [Use gzip compression](#use-gzip-compression) | ||
| - [Don't use synchronous functions](#dont-use-synchronous-functions) | ||
| - [Do logging correctly](#do-logging-correctly) | ||
| - [Handle exceptions properly](#handle-exceptions-properly) | ||
| - Things to do in your environment / setup (the ops part): | ||
| - [Set NODE_ENV to "production"](#set-node_env-to-production) | ||
| - [Ensure your app automatically restarts](#ensure-your-app-automatically-restarts) | ||
| - [Run your app in a cluster](#run-your-app-in-a-cluster) | ||
| - [Cache request results](#cache-request-results) | ||
| - [Use a load balancer](#use-a-load-balancer) | ||
| - [Use a reverse proxy](#use-a-reverse-proxy) | ||
| This topic clearly falls into the "devops" world, spanning both traditional development and operations. Accordingly, the information is divided into two parts: [things to do in your code](#things-to-do-in-your-code) (the dev part), and [things to do in your environment / setup](#things-to-do-in-your-environment--setup) (the ops part). | ||
|
|
||
| ## Things to do in your code | ||
|
|
||
| Here are some things you can do in your code to improve your application's performance: | ||
|
|
||
| - [Use gzip compression](#use-gzip-compression) | ||
| - [Don't use synchronous functions](#dont-use-synchronous-functions) | ||
| - [Do logging correctly](#do-logging-correctly) | ||
| - [Handle exceptions properly](#handle-exceptions-properly) | ||
| Here are some things you can do in your code to improve your application's performance. | ||
|
|
||
| ### Use gzip compression | ||
|
|
||
|
|
@@ -147,16 +129,49 @@ Additionally, using `uncaughtException` is officially recognized as [crude](http | |
|
|
||
| We also don't recommend using [domains](https://nodejs.org/api/domain.html). It generally doesn't solve the problem and is a deprecated module. | ||
|
|
||
| ## Things to do in your environment / setup | ||
| ### Use worker threads for CPU-intensive tasks | ||
|
|
||
| Node.js runs your JavaScript on a single thread, so a CPU-intensive operation (such as image processing, parsing or transforming large payloads, or cryptographic operations) can block the event loop and slow down every request your Express app is handling. If your app does CPU-bound work, you can run it in [worker threads](https://nodejs.org/api/worker_threads.html), which execute JavaScript in parallel within the same process, keeping the main thread free to serve requests. | ||
|
|
||
| Each worker thread runs in its own JavaScript engine instance, so the data you pass to it through `workerData` or `postMessage` is copied, not shared. Copying large payloads has a cost of its own, which you can avoid by transferring objects such as `ArrayBuffer`s or by sharing memory explicitly with `SharedArrayBuffer`. Worker threads are not a replacement for [running your app in a cluster](#run-your-app-in-a-cluster), which scales your app across CPU cores by running multiple instances of it in separate processes. | ||
|
|
||
| This example offloads an image-resizing task to a worker, where `resize-worker.js` receives the image via `workerData` and posts the result back when it's done: | ||
|
|
||
| ```js | ||
| const { Worker } = require('node:worker_threads'); | ||
|
|
||
| app.post('/resize', (req, res, next) => { | ||
| const worker = new Worker('./resize-worker.js', { workerData: req.body.image }); | ||
|
|
||
| worker.once('message', (result) => res.send(result)); | ||
| worker.once('error', next); | ||
| }); | ||
| ``` | ||
|
|
||
| Here are some things you can do in your system environment to improve your app's performance: | ||
| Note that this example is kept simple for illustration: creating a worker thread is relatively expensive, so [the Node.js documentation recommends using a pool of workers](https://nodejs.org/api/worker_threads.html#worker-threads) instead of spawning a new one for every request. Create a fixed pool of workers at startup and dispatch tasks to them as requests come in, for example with a library like [piscina](https://www.npmjs.com/package/piscina): | ||
|
|
||
| - [Set NODE_ENV to "production"](#set-node_env-to-production) | ||
| - [Ensure your app automatically restarts](#ensure-your-app-automatically-restarts) | ||
| - [Run your app in a cluster](#run-your-app-in-a-cluster) | ||
| - [Cache request results](#cache-request-results) | ||
| - [Use a load balancer](#use-a-load-balancer) | ||
| - [Use a reverse proxy](#use-a-reverse-proxy) | ||
| ```js | ||
| const path = require('node:path'); | ||
| const Piscina = require('piscina'); | ||
|
|
||
| const pool = new Piscina({ filename: path.resolve(__dirname, 'resize-worker.js') }); | ||
|
|
||
| app.post('/resize', async (req, res, next) => { | ||
| try { | ||
| res.send(await pool.run(req.body.image)); | ||
| } catch (err) { | ||
| next(err); | ||
| } | ||
| }); | ||
| ``` | ||
|
|
||
| Here `resize-worker.js` simply exports the function to run in the pool, and piscina takes care of creating the workers, queueing tasks, and reusing threads across requests. | ||
|
|
||
| For more guidance on keeping the event loop responsive, including when offloading work is worth its communication costs, see [Don't Block the Event Loop](https://nodejs.org/en/learn/asynchronous-work/dont-block-the-event-loop) in the Node.js documentation. | ||
|
|
||
| ## Things to do in your environment / setup | ||
|
|
||
| Here are some things you can do in your system environment to improve your app's performance. | ||
|
|
||
| ### Set NODE_ENV to "production" | ||
|
|
||
|
|
@@ -183,6 +198,10 @@ Environment=NODE_ENV=production | |
|
|
||
| For more information, see [Using Environment Variables In systemd Units](https://www.flatcar.org/docs/latest/setup/systemd/environment-variables/). | ||
|
|
||
| ### Use the latest LTS version of Node.js | ||
|
|
||
| Running your app on a recent Node.js release is one of the easiest ways to improve performance. Each new version of Node.js ships with cumulative improvements to the V8 JavaScript engine and the runtime itself, so the same Express app can handle significantly more requests simply by upgrading. Benchmarks such as NodeSource's [Node.js Performance Report](https://nodesource.com/pages/content-node-performance-report-wb.html) show how much throughput can improve from one Node.js version to the next. For production, use the latest [LTS release](https://nodejs.org/en/about/previous-releases) of Node.js, and keep it up to date as new versions come out. | ||
|
GroophyLifefor marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Upgrading what? The object is missing and the sentence reads weird. "runtime" and "Node.js" would be repetitions and "it" is a bit ambiguous (could refer to the app), so I don't have any suggestion how to improve it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since it isn't a single sentence, it's connected to the part before the comma, and the part before the comma clearly refers to upgrading Node.js, I think it's clear. |
||
|
|
||
| ### Ensure your app automatically restarts | ||
|
|
||
| In production, you don't want your application to be offline, ever. This means you need to make sure it restarts both if the app crashes and if the server itself crashes. Although you hope that neither of those events occurs, realistically you must account for both eventualities by: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.