preface

  • With the rise of React, React isomorphism has become one of the trends, combining the performance advantages of Node and the componentization of React. Enjoy the technical benefits while facing the technical challenges, in complex scenarios, challenge the ultimate performance optimization of more than 10 times.

What is isomorphism?

  • A set of code that can run on both the server and the client is called a homogeneous application. In short, it is a combination of server-side direct output and client-side rendering that fully combines the advantages of both and effectively avoids the disadvantages of both.

Why isomorphism?

  • Performance: Through Node straight out, the traditional three serial HTTP requests are simplified into one HTTP request, reducing the first screen rendering time

  • SEO: Server rendering has a natural advantage in search engine crawling. Although Ali’s e-commerce system does not have a strong demand for SEO, with the promotion of internationalization, more and more international businesses join Ali’s family, and many businesses rely on traffic import from Google and other search engines, such as Lazada.

  • Compatibility: Some display pages can effectively avoid client compatibility problems, such as a blank screen.

The performance data

Performance is a comprehensive issue. We cannot simply assert that isomorphic applications have better performance than non-isomorphic applications. We can only say that isomorphic applications can indeed bring certain performance improvement in appropriate scenarios and reasonable application.

Generally speaking, the worse the network condition, the more obvious the advantage of isomorphism. Below is a comparison of the first screen rendering time under different network conditions

Online case

  1. In the past two years, a large number of isomorphic practices have emerged both in the industry and inside Ali. Influential companies in the industry include Facebook, Quora, Medium, Twitter, Airbnb, Walmart, Q-hand and QQ Interest Tribe

  2. Ali also has a large number of internal applications, just to list some of the projects that Beidou development team has done technical support

  • Ali Cloud – Big data real estate

  • Dingding – Corporate home page

  • Nailing – Nailing log and approval template market

  • Cainiao – logistics market

  • Cloud Retail – store owner

  • Lazada – PDP

  • International Division – AGLA

  • AILab – Industry solutions

  • AILab – Intelligent hardware platform

  • Ailab-aligenie Open platform

  • Ailab-ar official website

  • ICBU – ICBU Shop

  • Business platform – store evaluation

  • International UED – Data operations

  • International UED – Know it

  • International UED – Detection of flowers

  • International UED-NUKE official website and process management

  • International UED – Proceedings, real-time translation

  • International UED-LBS data map

  • International UED – Digital Exploration

  • International UED – Micro strategy

  • International UED-Shuttle

  • International UED – fie Portal

  • .

Industry ecology

  • React server: React Server rendering framework (https://react-server.io/)

  • Next. Js: homogeneous lightweight framework (https://github.com/zeit/next.js)

  • Beidou: ali isomorphism framework, based on eggjs, positioning is the enterprise isomorphism framework (https://github.com/alibaba/beidou)

In addition to open source framework, React16 reconstructs SSR, and React-Router provides more friendly SSR support. To some extent, isomorphism is also a trend, or at least one of the directions.

Thinking and Realizing

The starting point of isomorphism is not “to do isomorphism, so we do”, but to return to the business, to solve SEO, first-screen performance, user experience and other problems in business scenarios, driving us to find available solutions. In such a scenario, in addition to isomorphism itself, we also need to consider:

  • High-performance Node Server

  • Reliable isomorphic rendering service

  • Controllable operation and maintenance costs

  • Reusable solutions

  • .

In a nutshell, we need an enterprise-level isomorphic rendering solution.

How did we do that?

Add pluggable isomorphism ability based on EGGJS

  • Beidou plugin – react (https://github.com/alibaba/beidou/tree/master/packages/beidou-plugin-react) as the original MVC architecture, the view layer replacement, Using the React Component as a view-layer template, you can render the React Component directly and export it to the client

  • Beidou plugin – webpack (https://github.com/alibaba/beidou/tree/master/packages/beidou-plugin-webpack) integrated webpack into the framework, During the development phase, code compilation and packaging services are provided

  • Beidou plugin – isomorphic React from the server (https://github.com/alibaba/beidou/tree/master/packages/beidou-plugin-isomorphic) Runtime: Babel-register Polyfill injection: environment variables, BOM and other non-JS file parsing: CSS, images, fonts…

  • The server supports CSS Modules

  • Automatic routing (https://github.com/alibaba/beidou/blob/master/packages/beidou-docs/basic/router.md#auto-router) : Pure static pages require no server-side code, just as simple as writing pure front-end pages

  • .

Here go into specific how to implement, interested readers can read our open source isomorphism framework beidou – https://github.com/alibaba/beidou

Hot issue

Any kind of technology has its application scenarios and limitations, isomorphism is no exception, the following try to do one or two, in order to do.

  • A memory leak

  • Performance bottleneck

  • .

Memory leakage is not unique to homogeneous applications. Theoretically, all server applications can leak memory, but homogeneous applications are a “high-risk group”. For details on how to solve the problem, please refer to my “Node Application Memory Leak Analysis Methodology and Practice”.

Extreme performance optimization

As mentioned earlier, isomorphic applications do not necessarily perform better than non-isomorphic applications. There are too many factors that affect performance. Let’s look at another set of data

The figure above shows the data collected by four processes based on Node V8.9.1 and [email protected]. On the X-axis, the number of final page nodes is generated. On the Y-axis, the red line represents RT(including rendering time and network time), and the green column represents QPS. It can be seen:

  • As the number of page nodes increases, rendering times can become very long and QPS drop very quickly. At around 3000 page nodes, QPS approach single digits, and the actual page may contain complex logic and unfriendly writing, which can be even worse.

By the way, the author sampled the home page of Taobao, a detail page of Taobao and a detail page of Lazada, and the number of nodes on the page was 2620, 2467 and 3701 respectively. In most cases, the number of nodes on the page is less than 1000. For example, the home page of Cainiao logistics market looks like it has a lot of content, but actually the number of nodes is 775.

So what do we do for pages with 3000 nodes or more? The author summarizes the following strategies and highlights one or two:

  • Use the Compiled Version of React: According to Sasha Aickin’s blog, the compiled version of React15 delivers 2.36 times, 3 times, and 3.85 times better performance than the uncompiled version on Node4, Node6, and Node8, respectively

  • Module split: Module split is conducive to concurrent rendering, currently ICBU shop decoration is using this way

  • Mod-level caching: Some modules in the page are actually suitable for caching. For example, the number of nodes in the Lazada detail page is as high as 3701, but the header accounts for 55.5% and the tail accounts for 3.5%, and the header and tail are constant all year round.

  • Component-level cache: The smallest unit of cache granularity. Performance gains depend on cache scope and hit ratio, and can result in significant performance gains when used properly. Reference walmartlabs

  • Using HSF instead of HTTP to provide external services: the network consumption of HSF is much lower than that of HTTP. In the practice of shop isomorphism, using HSF can shorten the time of invoking Node from the Java end by half.

  • Partial module client rendering (useless for SEO): Directly reduce the complexity of the SSR part

  • Intelligent degradation: When the traffic surge approaches or exceeds the threshold, the RT of the service increases rapidly. It can monitor the CPU and memory usage in real time. When the CPU and memory usage exceeds a certain percentage, it is automatically demoted to client rendering to reduce the server pressure. When the CPU and memory are restored to normal, it is automatically switched back to server rendering.

  • Using Node8: Also in shop practice, using Node8 reduced rendering time from 28ms to 18ms compared to Node6, an improvement of 36%.

  • Using the latest version of React16: Official Facebook data, on Node8, React16 is still 3.8 times better than compiled React15 and an order of magnitude better than uncompiled React15.

Component-level caching

If there is a magic bullet for performance optimization, it is caching. From Nigix caching to module-level caching to component-level caching, the most exciting one is component-level caching. Let’s take a look at how to implement it

1. Intercept React rendering logic. There are three main implementations in the industry

  • Fork React and add cache logic to it, representing the react-dom-stream library. Although this library is very popular, I still object to this implementation.

  • Through the require hook to intercept instantiateReactComponent load and into the caching logic, reference the react, SSR, and optimization

  • Extend the mountComponent method of ReactCompositeComponent by referring to the page-react-SSR-cachin method

2. Inject cache logic as follows


     
  1. const ReactCompositeComponent = require("react/lib/ReactCompositeComponent");

  2. ReactCompositeComponent.Mixin._mountComponent = ReactCompositeComponent.Mixin.mountComponent;

  3. ReactCompositeComponent.Mixin.mountComponent = function(rootID, transaction, context) {

  4.  const hashKey = generateHashKey(this._currentElement.props);

  5.  if (cacheStorage.hasEntry(hashKey)) {

  6. // If the cache is hit, the cache result is returned

  7.    return cacheStorage.getEntry(hashKey);

  8.  } else {

  9. // If not, call the mountComponent of React to render and cache the result

  10.    const html = this._mountComponent(rootID, transaction, context);

  11.    cacheStorage.addEntry(hashKey, html);

  12.    return html;

  13.  }

  14. };

Copy the code

3. Set the maximum cache and cache update policy


     
  1. lruCacheSettings: {

  2.      max: 500,  // The maximum size of the cache

  3.      maxAge: 1000 * 5 // The maximum age in milliseconds

  4.  }

Copy the code

The above cache logic is attribute based and can cover most application scenarios, with the exception that the attribute values must be enumerable and have few options. Take a look at the scene below.

Taobao has a large number of goods on a page, and Taobao’s goods are more than a million, even if a cache, the possibility of being hit next time is still minimal. So how do you solve this problem? As the savvy reader may have noticed, although the final rendering of each item is highly variable, the structure is always the same and therefore cacheable.

To implement caching of the structure, three additional steps need to be added to the above logic.

1. Generate intermediate structure:

  • Take the example of component ${Price} , Price replaces set(price,”${price}”) with a placeholder ${price} and calls the React native mountComponent method to generate an intermediate structure

    ${price}

2. Cache intermediate structure

3. Build the final component

This is how component level caching is implemented, but it is important to note that caching is a double-edged sword that can lead to memory leaks and data inconsistencies if not used properly.

React16 SSR

  • FB released the official version of Act16 on 9.26, and the much-anticipated SSR performance improvement did not disappoint everyone, citing the comparison picture of Sasha Aickin from the Core development of React

The author upgraded the previous application to Act16, and compared with 3909 node, RT dropped from 295ms to 51ms, and QPS increased from 9 to 44. The improvement was very obvious.

In actual combat

Here is an example of how to improve performance step by step. Code – https://github.com/alibaba/beidou/ warehouse

More than 10 times performance improvement

  • First, construct a very complex page with a node number of 3342. In contrast, the first screen of Taobao home page has a node number of 831. After asynchronous full loading (lazy loading completed), the node number of the whole page is 3049. Note: Taobao page is a dynamic page, each sample may be different.

  • The initial average rendering time is 295.75ms(Node6.92, React15.6.2). Note: there are four average values of 296.50ms, 317.25ms, 297.25ms and 295.75ms in the figure, because four processes are started and the last one is sampled, the same as below.

  • Enable the Babel performance acceleration plug-in with an average render time of 219.00ms

  • With Node8.9.1(or later), the average render time is 207ms

  • The average rendering time in Production mode is 81.75ms

  • Part of the content was rendered by the client, with an average rendering time of 44.63ms

  • Partial content component level cache with an average render time of 22.65ms

  • Act16 (or later) with an average render time of 5.17ms

  • Combined with Act16 and partial client rendering, the average render time was 2.68ms

At this point, the server rendering time has been reduced from 295.75ms to 2.68ms, an increase of more than 100 times.

More Performance Policies

In addition to the strategies mentioned above, there are other strategies, such as

  • Using Async, it is claimed that the performance can be improved by 30%. The author has tried, but no obvious improvement has been found. The beidou framework supports ES6 scripting such as import and React JSX syntax on the server side. In fact, it is very simple, directly narrow the scope of Babel compilation, in the Beidou framework can be customized.

  • Lower the nesting level of the React component. Test data, the same number of page nodes, server rendering time and component nesting level are linearly positive correlation.

  • Hot cache…

All changes are inseparable

To borrow a classic line from “Kung Fu”, nothing can be broken but fast. Similarly, as time goes by, the above strategies will be broken sooner or later. For example, after react16 SSR reconstruction, the previous component-level cache logic is no longer valid. On the other hand, there may be a lack of architectural design/technology selection. For example, React16 was only released on September 26 this year, and many third-party components have not yet been updated. If some components in your application rely heavily on React15 or earlier versions, you may not be able to take advantage of the performance benefits of React16.

So is there a universal way, can do only fast not broken?

The answer is: yes. Only by mastering the methodology, can we find the performance optimization strategy suitable for our application in the constant change.

The methodology will be covered in the next article, “Make NodeJS Faster!”

Remark: This article was published on Beidou Isomorphic Github (https://github.com/alibaba/beidou/blob/master/packages/beidou-docs/articles/high-performance-isomorphic-app.md), Please indicate the source of reprint.