Improving Node JS performance by 400% with Turb.js

Turb.js is a function caching server that turbo charges your application with safe and fast memory management. Today we will use Turb to increase the performance of static assets in our PocketBase database. Although this example is for PocketBase, Turb can be used anywhere in your application to speed up long processes.

From the description we know that turb is a function caching server, but what does that mean? Turb acts as an intermediate layer between your database's SDK and your application by storing each function call in memory. After the response is stored in memory, you won't have to request the asset from your database again! After I added turb to this site, I saw a 4x decrease in load times.

Installing Turb

1npm i turb

Building the server

1git clone https://github.com/lakefox/turb.git
2cd turb
3npm run build

After the executable is built I moved turb to the top of my application and deleted the repository.

In the folder you move the executable, create a config.json that looks like below. This will create a single shard named "PocketBase" that is allocated 50MB of storage.

1{
2    "shards": {
3        "PocketBase": {
4            "size": 50000000
5        }
6    },
7    "hostname": "0.0.0.0",
8    "port": 6748
9}

PocketBase API layer

This is just a simple wrapper for the PocketBase JS SDK that will grab a post from the collection posts, filtering the request by the slug attribute. You might have your own way of querying your database but this is the way I like to do it as it gives me a direct function to get what I need.

1export async function getPost(slug) {
2 return new Promise((resolve) => {
3  resolve(
4   pb.collection('posts').getFullList(200, {
5    filter: `slug = "${hostname}" && active = true`
6   })
7  );
8 });
9}

Now we will use this module as the function we will cache using turb. Below shows the basics of importing the module, registering a function, and calling the function. The register step is the key part of the script, it creates an exact duplicate of the function provided and when called it handles the caching system.

If you want to learn more about how the caching system works, visit the NPM page here.

 1import { turb } from "turb";
 2import { getPost } from "./pocketbase.wrapper.js";
 3
 4// Initiate the turb module
 5let turb0 = new Turb({
 6 shard: "PocketBase"
 7});
 8
 9// Register the function
10let turb0GetPost = turb0.register(getPost);
11
12let slug = "improving-pocket-base-performance-by-400-with-turb-js";
13
14console.time();
15// Non cached version
16let post1 = await getPost(slug);
17console.timeEnd();
18
19console.time();
20// Cached version
21let post2 = await turb0GetPost(slug);
22console.timeEnd();

When this is run twice, the results are very significant obviously this example will not work for you unless you have access to this site database. However, before I implemented turb into this site, I benchmarked it using the wrk command and this is what I got.

Results

Before

1Running 30s test @ https://decode.sh
212 threads and 400 connections
3Thread Stats   Avg      Stdev     Max   +/- Stdev
4Latency     0.00us    0.00us   0.00us     nan%
5Req/Sec     2.81      4.59    20.00     82.22%
6104 requests in 30.09s, 7.31MB read
7Socket errors: connect 157, read 0, write 0, timeout 104
8Requests/sec:      3.46
9Transfer/sec:    248.94KB

After

1Running 30s test @ https://decode.sh
212 threads and 400 connections
3Thread Stats   Avg      Stdev     Max   +/- Stdev
4Latency     1.22s   592.91ms   1.84s    66.67%
5Req/Sec     4.59      4.80    30.00     79.56%
6421 requests in 30.08s, 29.61MB read
7Socket errors: connect 157, read 0, write 0, timeout 415
8Requests/sec:     14.00
9Transfer/sec:      0.98MB

As you can see turb improved performance by over 400%! If you have any questions on how turb works feel free to drop them in the comments or create an issue on the GitHub repository.