Skip to content

Commit 9c58ced

Browse files
docs: add worker threads and Node.js LTS guidance to performance best practices (#2473)
Co-authored-by: Sebastian Beltran <bjohansebas@gmail.com>
1 parent 1da4cb9 commit 9c58ced

1 file changed

Lines changed: 47 additions & 28 deletions

File tree

src/content/pages/en/advanced/best-practice-performance.mdx

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,11 @@ description: Discover performance and reliability best practices for Express app
55

66
This article discusses performance and reliability best practices for Express applications deployed to production.
77

8-
This topic clearly falls into the "devops" world, spanning both traditional development and operations. Accordingly, the information is divided into two parts:
9-
10-
- Things to do in your code (the dev part):
11-
- [Use gzip compression](#use-gzip-compression)
12-
- [Don't use synchronous functions](#dont-use-synchronous-functions)
13-
- [Do logging correctly](#do-logging-correctly)
14-
- [Handle exceptions properly](#handle-exceptions-properly)
15-
- Things to do in your environment / setup (the ops part):
16-
- [Set NODE_ENV to "production"](#set-node_env-to-production)
17-
- [Ensure your app automatically restarts](#ensure-your-app-automatically-restarts)
18-
- [Run your app in a cluster](#run-your-app-in-a-cluster)
19-
- [Cache request results](#cache-request-results)
20-
- [Use a load balancer](#use-a-load-balancer)
21-
- [Use a reverse proxy](#use-a-reverse-proxy)
8+
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).
229

2310
## Things to do in your code
2411

25-
Here are some things you can do in your code to improve your application's performance:
26-
27-
- [Use gzip compression](#use-gzip-compression)
28-
- [Don't use synchronous functions](#dont-use-synchronous-functions)
29-
- [Do logging correctly](#do-logging-correctly)
30-
- [Handle exceptions properly](#handle-exceptions-properly)
12+
Here are some things you can do in your code to improve your application's performance.
3113

3214
### Use gzip compression
3315

@@ -147,16 +129,49 @@ Additionally, using `uncaughtException` is officially recognized as [crude](http
147129
148130
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.
149131
150-
## Things to do in your environment / setup
132+
### Use worker threads for CPU-intensive tasks
133+
134+
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.
135+
136+
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.
137+
138+
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:
139+
140+
```js
141+
const { Worker } = require('node:worker_threads');
142+
143+
app.post('/resize', (req, res, next) => {
144+
const worker = new Worker('./resize-worker.js', { workerData: req.body.image });
145+
146+
worker.once('message', (result) => res.send(result));
147+
worker.once('error', next);
148+
});
149+
```
151150
152-
Here are some things you can do in your system environment to improve your app's performance:
151+
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):
153152
154-
- [Set NODE_ENV to "production"](#set-node_env-to-production)
155-
- [Ensure your app automatically restarts](#ensure-your-app-automatically-restarts)
156-
- [Run your app in a cluster](#run-your-app-in-a-cluster)
157-
- [Cache request results](#cache-request-results)
158-
- [Use a load balancer](#use-a-load-balancer)
159-
- [Use a reverse proxy](#use-a-reverse-proxy)
153+
```js
154+
const path = require('node:path');
155+
const Piscina = require('piscina');
156+
157+
const pool = new Piscina({ filename: path.resolve(__dirname, 'resize-worker.js') });
158+
159+
app.post('/resize', async (req, res, next) => {
160+
try {
161+
res.send(await pool.run(req.body.image));
162+
} catch (err) {
163+
next(err);
164+
}
165+
});
166+
```
167+
168+
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.
169+
170+
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.
171+
172+
## Things to do in your environment / setup
173+
174+
Here are some things you can do in your system environment to improve your app's performance.
160175
161176
### Set NODE_ENV to "production"
162177
@@ -183,6 +198,10 @@ Environment=NODE_ENV=production
183198
184199
For more information, see [Using Environment Variables In systemd Units](https://www.flatcar.org/docs/latest/setup/systemd/environment-variables/).
185200
201+
### Use the latest LTS version of Node.js
202+
203+
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.
204+
186205
### Ensure your app automatically restarts
187206
188207
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:

0 commit comments

Comments
 (0)