Skip to content

v20.71.0

Latest

Choose a tag to compare

@uNetworkingAB uNetworkingAB released this 16 Sep 09:06
· 36 commits to binaries since this release
9ec36de

Microcaching JavaScript invocations for 13x performance

This release adds the concept of Microcaching.

Any idempotent HTTP handler dealing with dynamic-but-public content can benefit from significant (3x) performance improvements and reduced business logic complexity by enabling some degree of microcaching (see benchmark below).

The feature is experimental and subject to change.

Let's build a financial app

uWS.App().get('/prices/gold', async (res, req) => {
  /* Before going async we need to listen to socket abortions */
  res.onAborted(() => {
    res.aborted = true;
  });
  /* This would be some async DB action or fetch quest that you want to essentially rate-limit */
  const goldPrice = await getGoldPriceJSON();
  if (!res.aborted) {
    res.cork(() => {
      res.end(goldPrice);
    });
  }
}, {
  /* This object specifies the caching options */
  lowerExpiry: 1,
  upperExpiry: 5
})

Caching is enabled per-route, by passing the following object as final route handler argument:

{
  /* This object specifies the caching options */
  lowerExpiry: 1,
  upperExpiry: 5
}
  • lowerExpiry is essentially the rate-limiter, it decides how rapidly the cache will try to update. A value of 0 is allowed and will update the cache as fast as the request handler supports (it will still act as protection against thundering herd since only 1 update can be pending at any given time, in other words, the updating is coalesced)
  • upperExpiry is the deadline cutoff. It decides the absolute upper limit for the cache. If your service has ample traffic and your request handler is reliable, you should never hit upperExpiry.

See the complete HttpCache example

Benchmarking the service

Fastify, more like Crawlify, am I right?

For context, a baseline Fastify "Hello World" server tops out at around 15k req/sec at 100% CPU-core utilization on the test computer.

fastify_slow

Benchmarking our Gold price endpoint—which serves dynamic payloads like this:

{
  "status": "success",
  "asset": "Gold Futures",
  "symbol": "GC=F",
  "price": 4408.9,
  "currency": "USD",
  "timestamp": 1789160399,
  "isoDate": "2026-09-11T20:59:59.000Z"
}

...results in 200k req/sec using the same CPU-time budget.

ayo-what

(Actually I decided to hit the Bitcoin endpoint here, but they are identical performance wise)

dynamic_cache_200

What the heck is going on?

Without any caching, uWS.js is 5x faster than Fastify. By adding caching, we are 13x faster. With a cache, we dilute the JavaScript overhead by allowing native ("core") uWS to respond from its native cache for the most part of a second, and only call into JavaScript to update the cache about once every second. This drastically reduces the density of JavaScript in the hot path, which causes the app to approach native performance even though the business logic remains the same JavaScript.

In fact, the business logic becomes simpler by using a cache, as we can rely on the inherent rate-limiting property of the cache when designing our JavaScript data fetcher. Knowing that we have a cache, we can afford high level async/await without worrying about DOS.

uWS js price feed service is 13x as fast as Fastify's _Hello World_