Optimizing Jint part 1: Setting the stage

This is a story that started a year ago, but I finally had the time and motivation to write something down. I hope this story about a performance journey might give some insights to performance optimizations and how I analyze and optimize code.





Both Sébastien Ros and RavenDB folks were super helpful and gave great advice during this process and were very responsive. I'm  grateful having had the opportunity to participate in these open-source projects and to interact with their members.

It all started with RavenDB’s first 4.0 beta releases. There were great promises about improved performance and I was of course intrigued, we've had our battles with 3.x series and it was no unknown feat to create workarounds and hacks to push for more performance (not serializing fields if they had default values, collections being empty, you name it).

So I was benchmarking our code and see how much we would benefit switching from RavenDB 3.5 to 4.0. All seemed faster than before as expected, but in places where we had used transformers to reduce transferred payload from server to client things seemed... slower.

Transformers in RavenDB

Transformers in RavenDB 3.5 and prior versions were used to reduce transferred data and its processing cost. You would serialize less data on server, transfer less bytes and de-serialize less data on client side. Depending on how much logic you had in your transformer would naturally affect the processing speed on server side. In RavenDB 3.5 transformers were basically C# code running on server, so pretty fast, LINQ’s pitfalls excluded.

So what had changed?

Projections in RavenDB 4

RavenDB ditched the whole transformer story in favor of having projections. When you wrap your head around them, it’s quite sweet. Projections are basically just LINQ that is transformed to JavaScript that can be run on server side. No more ceremony of writing entire class to describe input and output and everything in-between.

As said, old transformer solution had a lot of ceremony and they had to be deployed like indexes. We also saw/did a lot of re-use caused by not wanting to create Yet-Another-Transformer which lead to too generic transformers pulling too much data and no longer being specific to use cases. Projections make things a lot leaner:

See, a lot less code and the projection sample also includes the actual querying part!

Behind the scenes RavenDB is using Jint, which is is a Javascript interpreter for .NET which provides full ECMA 5.1 compliant runtime and some more from newer ECMA versions.

But the performance

After investigating what was causing things, I created an issue to RavenDB issue tracker. RavenDB had smarts in usage of Jint but it seemed that it was Jint itself that was causing a lot of memory usage and CPU burn when a JavaScript with tight loops and projects was executed against RavenDB’s document. For ‘normal’ cases the performance was quite good but it all added up when you throw in a lot of documents, larger objects graphs and nesting.

Luckily, Jint is OSS and available on GitHub, so it was just a fork away to start investigating if there was something that could be done to improve performance.

Next up, open the toolshed and bring out the big guns!

Comments

Popular posts from this blog

Optimizing Jint part 6: DictionarySlim is coming to town

Running docker-compose against WSL2