react: Server-side rendering performance degradation with renderToPipeableStream

Hello!

When switching from renderToString to renderToPipeableStream, I run load tests on the application, and found a decrease in server throughput, from 50 to 15 RPS, and an increase in response timings. When profiling the CPU, I see a large overhead on the internal work of the stream, specifically the methods Writable.write and Writable.uncork. All these method calls together take more than twice as much CPU time (about 50-60ms) as rendering my test page (about 15-20ms)

Also, I don’t want to give the HTML to the client in the stream, this approach has some disadvantages. So I have to buffer the data, and it slows down the application a bit more.

CPU profiler in production mode:

CPU profiler in development mode:

My custom Writable stream with buffering:

class HtmlWritable extends Writable {
  chunks = [];
  html = '';

  getHtml() {
    return this.html;
  }

  _write(chunk, encoding, callback) {
    this.chunks.push(chunk);
    callback();
  }

  _final(callback) {
    this.html = Buffer.concat(this.chunks).toString();
    callback();
  }
}

And rendering flow:

import { renderToPipeableStream } from 'react-dom/server';
 
new Promise((resolve, reject) => {
  const htmlWritable = new HtmlWritable();

  const { pipe, abort } = renderToPipeableStream(renderResult, {
    onAllReady() {
      pipe(htmlWritable);
    },
    onError(error) {
      reject(error);
    },
  });

  htmlWritable.on('finish', () => {
    resolve(htmlWritable.getHtml());
  });
});

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 5
  • Comments: 22 (1 by maintainers)

Most upvoted comments

The initial fix is in 18.1.0.

I’m going to continue working on this and will likely have future PRs but can’t say exactly when. If you look for titles with [Fizz] in the title that’s likely where they will be. If I remember I’ll ping you here or tag you in the future work

It’s copied from here so you should be able to take this one: https://github.com/facebook/react/tree/main/fixtures/ssr2

Thanks! Create few examples from this - https://github.com/SuperOleg39/react-ssr-perf-test

Similarly, if you run React 18 with renderToString.

At first, I made comparsion between renderToString with 17 and 18 versions (call it react@18 legacy). React 17 - 50 RPS React 18 - 70 RPS, and I only changed a react and react-dom versions.

Just to narrow down how much of the perf issues are with the writable vs the rest.

Then I tested renderToPipeableStream (call it react@18 stream) I got timings around 1.5s for responses, problem was in app.use(compress()); middleware:

Снимок экрана 2022-03-31 в 23 36 17

After removal this middleware, I got around 2 RPS, 300+ ms CPU work for request. Still a big overhead for stream internals:

Снимок экрана 2022-03-31 в 23 38 30

If you run the same benchmark but with your custom HtmlWritable with buffering. What kind of numbers do you get?

At last, tried buffering (call it react@18 buffering), and got around 15 RPS. CPU profile:

Снимок экрана 2022-03-31 в 23 40 47