This article is published on
Beidou isomorphic GithubPlease indicate the source

Note: This post has been desensitized to build reliable and High-performance React Isomorphic Solutions at the 12th D2 Front-end Technology Forum.

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

  • 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
  • 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: the react server rendering framework
  • Next. Js: Lightweight homogeneous framework
  • Beidou: Ali’s own isomorphic framework, based on EggJS, is positioned as an enterprise-level isomorphic framework

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 replaces the view layer in the original MVC architecture. The React Component is used as the template of the view layer to directly render the React Component and output it to the client
  • Beidou-plugin-webpack integrates Webpack into the framework and provides code compilation and packaging services at the development stage
  • Beidou-plugin-isomorphic server React runtime: babel-register polyfill: environment variables, BOM and other non-JS file parses: CSS, images, fonts…
  • The server supports CSS Modules
  • Automatic routing: Pure static pages require no server-side code, just as simple as writing pure front-end pages
  • .

We won’t go into details here, but you can read our open source isomorphic framework beidou — github.com/alibaba/bei…

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

  • There are three main implementations of React rendering logic

    • 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
  • Inject cache logic as follows
const ReactCompositeComponent = require("react/lib/ReactCompositeComponent"); ReactCompositeComponent.Mixin._mountComponent = ReactCompositeComponent.Mixin.mountComponent; ReactCompositeComponent.Mixin.mountComponent = function(rootID, transaction, context) { const hashKey = generateHashKey(this._currentElement.props); If (cachestorage.hasentry (hashKey)) {return cachestorage.getentry (hashKey); } else {// Call react mountComponent render and cache the result const HTML = this._mountComponent(rootID, transaction, context); cacheStorage.addEntry(hashKey, html); return html; }};Copy the code
  • Set the maximum cache and cache update policy
lruCacheSettings: {
      max: 500,  // The maximum size of the cache
      maxAge: 1000 * 5 // The maximum age in milliseconds
  }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.

  • Generate intermediate structure:

    • In the component<Price>${price}</Price>For example, take the variable price as a placeholder${price}Instead ofset(price, "${price}")Call the React native mountComponent method to generate an intermediate structure<div>${price}</div
  • Cache intermediate structure
  • 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 Repository – github.com/alibaba/bei…

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 render time is295.75 ms(Node6.92, React15.6.2), note: in the figure296.50 ms.317.25 ms.297.25 ms.295.75 msFour averages, because we started four processes, sampled the last one, same thing.

  • To enable theBabel performance acceleration plug-in, the average rendering time is219.00 ms

  • With Node8.9.1(or later), the average rendering time is207ms

  • usingproductionMode average render time is81.75 ms

  • Partial content client rendering, the average rendering time is44.63 ms

  • Partial content component level cache, average render time is22.65 ms

  • React16(or later) with an average render time of5.17 ms

  • Combined with act16 and partial client rendering, the average render time is2.68 ms

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

  • usingAsync, several claims that the performance of 30%, the author tried, did not see a significant improvement. It was probably compiled by Babel and didn’t workAsyncThe advantage of this is becauseBeidou frameworkSupport on the server sideimportES6 and React supportJSX grammar. It’s actually very simple, just zoom outbabelCompilation range, inBeidou frameworkIs self-definable.
  • 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.

For more on the methodology, please refer to my other article, “Make NodeJS Faster if It’s Fast.”