Improve React.js Server-Side Rendering by 150% with GraalVM
This post was written by Jiří Maršík.
Talkyard.io — a React.js client application with server-side features, written in Scala and TypeScript
- Nashorn was deprecated in JDK 11.
Migrating the application
To build an instance of a GraalVM Context, we use Context.Builder and specify the languages we want to have access to (in our case
After we create the Context, we need to set it up by loading our application code so that it is ready to process page render requests. Since a GraalVM Context can in general execute code in a variety of languages, the code below becomes slightly more verbose as we need to specify which language is used.
Now that we have a GraalVM Context all set up, we can execute functions within it like we did with Nashorn.
result is of the Nashorn type
ScriptObjectMirror and in the GraalVM case, it is of the GraalVM type
Apart from the changes shown above, the full patch contains the following edits:
- Replace all references to
js.Invocablewith references to
- Replace usage of
graal-sdk/org.graalvm.sdkas a Maven dependency
- Replace a custom subclass of
AbstractStrictEnginewith the use of
Finally, the Dockerfile which builds the image running the web application was modified to include GraalVM instead of the original JDK (OpenJDK).
Testing the port
Benchmarking the port
Furthermore, we had to tweak Talkyard a little to make these benchmarks possible. First, we had to disable the included rate limiter so it didn’t block the requests from our workload generator. Talkyard is also caching the results of the server-side rendering, both in memory and in a relational database. In order for changes to the server-side rendering pipeline to be visible, we had to disable those caches. We also included some hooks that let us capture latency data for each request.
As we can see from the graph, there is a long warmup curve both on the vanilla JVM with Nashorn and on GraalVM. However, we reach peak performance sooner on GraalVM (9 and 12 minutes on GraalVM Enterprise Edition/Community Edition, respectively, compared to 20 minutes on Nashorn) and more importantly, we end up with a peak performance that is more than 25% higher than the one we observe on Nashorn, with GraalVM Enterprise Edition outperforming Nashorn by more than 35%.
NB: In the graph above, throughput was calculated as the number of cores (8) divided by the average time spent rendering a page.
In the graph, we can see that on Nashorn, we get about ~800 renders per second (10ms per one render) whereas with GraalVM, we get ~2000 renders per second (4ms per one render), a 150% increase in rendering throughput.
Warmup performance is a constant topic of our optimization efforts, so we expect to get better warmup behavior in the future. This example is also our first experience with trying to run larger React.js applications on GraalVM, so we still have yet to try and optimize our performance for this kind of workload.