New Odnoklassniki frontend: launching React in Java. Part I

Many have heard the name GraalVM, but so far not everyone has had the chance to try this technology in production. For Odnoklassniki, this technology has already become the “holy grail” changing front-end.

In this article I want to talk about how we managed to make friends with Java and JavaScript, and start migrating to a huge system with a lot of legacy code, and how GraalVM helps in this way.

At the time of writing the article, it turned out that the entire volume of the material does not fit into the HABR’s traditional size and if you publish the entire publication, it will take several hours to read it. Therefore, we decided to divide the article into 2 parts.

From the first part you will learn about the history of the front-end in Odnoklassniki and get acquainted with its historical features, go through the path of finding a solution to the problems that have accumulated in us over 11 years of the project, and at the very end you will plunge into the technical features of the server implementation of the decision we made.

Background

The first version of Odnoklassniki appeared 13 years ago, in 2006. The site was made on .NET, no JavaScript existed then, everything was server-side rendering.

A year later, Odnoklassniki had over one million users. In 2007, these were incredible numbers, and the site, unable to withstand the load, began to fall. The developers solved the problem with the help of the One.lv project, created by the Latvian company Forticom, whose core competencies were in Java development. Therefore, Odnoklassniki, it was decided to rewrite from .NET to Java.

Over time, the project developed, and the need arose for new client solutions. For example, when navigating a site, it’s not completely updated, but only certain parts. Or so that when submitting, only the form is updated, and not the entire page. At the same time, the site worked only in Java.

To do this, they came up with a system in which the site was marked up with named blocks. When navigating, a request was made to the server, which made a decision about what should be changed using this link, and the client was given the necessary pieces. The client engine simply replaced the necessary parts, and so the client dynamics was implemented. Very convenient, because all business logic is on the server. The small engine allowed the company to still write Java code to manage the client.

Of course, without minimal JavaScript was not enough. To make pop-up, manipulations are needed: for example, by hovering over a div display: block was hung up or it was hiding with display: none.

But at the same time, the content of the pop-up was requested from the server, all the business logic was there and was in Java.

2018

After 12 years, Odnoklassniki turned into a giant service with more than 70 million users. We have more than 7,000 machines in 4 data centers, and only 600 thousand requests per second come to the OK.RU front-end.

Odnoklassniki’s front-server continues to work in Java, and the code base of fronts alone exceeds two million lines.

The technologies implemented on the client side also did not stand still: many solutions appeared using different libraries: GWT, jQuery, DotJs, RequireJS and many others.

At that time, standards like React, Angular, and Vue were not common, each developer tried to find the optimal solution using all available tools.

It became clear that living with this is very difficult, because a huge number of problems have accumulated:

  • Many old libraries
  • There is no single framework
  • No isomorphism (since the backend is in Java, the client is in JS)
  • There is no single structured application on the client
  • Poor responsiveness
  • Insufficient tools
  • High entry threshold

The world was already in 2018 and it was necessary to change.
Using all the power of technical thought, we thought through and formulated four basic requirements for solving problems:

  1. Classmates should have isomorphic code for the UI. Because it is impossible to constantly write the server in Java, and then, if you need to add some kind of dynamics, play the same thing on the client.
  2. A smooth transition is needed. Because it’s impossible to quickly make the second version of Odnoklassniki and switch
  3. Necessarily need server rendering (more on that below)
  4. The new solution, working on the same amount of iron, should not impair performance and fault tolerance under our loads.

Why server-side rendering?

Odnoklassniki has many users who live far from Moscow and they do not always have good internet.

Server rendering will help these users get content faster. While the pictures are loaded, they will be able to start reading something:

We conducted a series of experiments, trying to understand what would happen if some data (for example, tape) was already delivered to the client, with an expectation. As a result, it turned out that this negatively affected user activity.

How the server works now

The browser makes a request to the OK site, and gets to the OK-WEB application, which is entirely written in Java. The application follows the data in the API. Between WEB and API, one-nio fast binary transport developed in Odnoklassniki is implemented. Requests are completed in less than one millisecond. You can see what it is separately. One-nio lets you do a lot of queries cheaply without worrying about delays.

The API pulls out the data, gives it to the web. The web generates HTML pages with a Java engine and gives it to the browser.

All this takes now less than 200 ms.

Search for a solution

At first, the concept of migration based on widgets was developed.

Applications will be delivered to the site in small pieces. Inside they will be written on a new stack. And for the rest of the site it will be just a DOM element with some kind of custom behavior.

It will be like a tag

Which stack to choose?

Now the concept needed to be implemented, they began to sort out the options.

Kotlin

The first prototype was made at Kotlin. The idea was as follows: for new components, write logic in Kotlin, and describe the component markup in XML. Everything can be run on the server in the JVM using the existing template engine, and for the client it can be transposed in JavaScript.

In addition to introducing a new language with a high entry threshold, Kotlin turned out to have insufficiently developed tools for working with JavaScript, and much more would have to be developed independently.

Therefore, unfortunately, this concept had to be abandoned.

Node.js

Another option is to put Node.js or another runtime, for example, Dart. But what happens?

There are two ways to use Node.js.
The first way is to delegate the rendering of the component to the server on Node.js running on the same server as the Java application. Thus, we save the application in Java and just in the process of rendering HTML we make a call to the service locally running on Node.js.

However, there are several problems with this approach:

  1. A remote call to Node.js involves serializing / deserializing the input. This data can be very voluminous, for example, in the case when a new component in JS is a wrapper around a strictly component implemented in Java.
  2. A remote call, even on a local machine, is far from free, and also introduces an additional delay. If there are dozens or such components on the page, even very simple ones, we will significantly increase the overhead and the delay in processing the user’s request.
  3. In addition, the operation of such a system is significantly complicated, since instead of a single process we would need to have a process in Java and several processes in Node.js. Accordingly, all operations become much more complicated, for example: deployment, collection of operational indicators, log analysis, error monitoring, etc.

The second way to use Node.js is to put it in front of the web server in Java and use it for post-processing HTML. In other words, it is a proxy that parses HTML, finds components in JS, draws them, and returns the finished HTML to the user. An interesting option, it seems to be universal and quite working. The disadvantages of this approach are that it requires a thorough change in the entire infrastructure, significantly increases overhead costs and carries serious risks – any request must go through Node.js, that is, we will begin to completely depend on it. It seems too expensive a solution to solve our problem.

It turns out that Node.js cannot be used for the following reasons:

  • Serialization / deserialization is extra workload and delays
  • Node.js is another component in Odnoklassniki’s huge distributed system

We already have many specialists who know how to “cook” Java, and now we will have to hire a staff who will operate Node.js and create another infrastructure in addition to the existing one.

JavaScript in the JVM

But what if you try to run JavaScript inside the JVM? It turns out that the Java and JavaScript code will be executed in one process and interact with a minimum of overhead.

This will smoothly replace Java pieces with JavaScript inside the current WEB’s.
JS components will receive data from Java and generate HTML. They will be able to work isomorphically on both the client and server.

But how to run JS in the JVM?

Can use v8 like Cloudflare. But this is binary code third-party to Java. Therefore, in the JVM it will not be possible to catch errors inside the V8. Any V8 crash will destroy the whole process. As a result, the use of V8 will increase operational risks, and this should not be allowed.

There are several JS runtimes for the JVM: two rhino, Nashorn and Rhino (one from Oracle, the other from Mozilla) and fresh GraalVM.

Advantages of JS Runtimes for JVM:

  • Everything works in the JVM, and we have a lot of expertise in this.
  • Free Java and JavaScript interaction
  • Secure runtime
  • Java compiler in case of GraalVM

Further it was enough to compare these runtimes in speed. It turned out that GraalVM is ahead of everyone by a wide margin:

What is GraalVM?

GraalVM This is a high performance runtime that supports programs in different languages. It has a framework for writing language compilers for the JVM. Thanks to this, the execution of programs in Java, Kotlin, JS, Python and other languages ​​within the same JVM is supported.

Learn more about GraalVM features from report of Oleg Shelaevwho works at Oracle Labs, where they are developing GraalVM. Recommended for watching back-end and front-end.

GraalVM allows us to run JS to render the UI on the server. As a library we use React.

The advantages of such a bundle:

  • No new languages ​​added: still Java and JavaScript
  • Big community: everyone knows React
  • Low entry threshold
  • Easily look for colleagues in a team
  • Operation is not complicated

Running React in GraalVM

Inside GraalVM, you can create a Context – an isolated container in which the program will run in the guest language. In our case, the guest language is JS:

Context context = Context.create("js");

// получаем global данного контекста
Value js = context.getBindings("js");

To interact with the context, its global object is used:

// можно записать в global
js.putMember("serverProxy", serverProxy);

// можно читать из global
Value app = js.getMember("app");

You can load the module code into the context:

// получаем метод загрузки кода
Value load = js.getMember("load");

// загружаем модуль в контекст
load.execute(pathToModule);

Or, “zap-eval-it” is any code there:

context.eval("js", someCode);

JS server rendering: concept

Create a JavaScript context in the JVM and load the React application module code into it. We throw from Java to JS the necessary functions and methods. Then from this context we extract the link to the JS function render () of this module, so that later we can call it from Java.

When the user requests the page, the server template engine starts, he calls the render () function of the necessary components with the necessary data, receives HTML code from them and gives it along with the HTML of the entire page to the user.

JS Server Rendering: Implementation

In the server template engine of Odnoklassniki, layout is written in the form of HTML markup. In order to distinguish JS applications from the usual markup, we use custom tags.
When the template engine encounters a custom tag, a task is created to render the corresponding module. It is sent to the thread pool, each of which has its own JS context, is executed on a free thread, renders the component in it, and gives it to the client.

Why do I need a context pool

The component is rendered synchronously in one thread. At this time, the JS rendering context is busy. Therefore, by creating several independent contexts, you can parallelize component rendering using Java multithreading capabilities.

Java data acquisition functions are passed by reference to each context. The result is cool multi-threaded JavaScript inside a single process.

How the implementation of the client part of the new frontend is built on this concept, we will describe in the next article.

To be continued

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *