With performance, test, benchmark and be ready to back out
So we did that, we took all the places in our code where we were doing something with Jint and moved them to Jurrasic. Of course, that isn’t nearly as simple as it sounds. We have a lot of places like that, and a lot of already existing code. But we also took the time to do this properly, of making sure that there is a single namespace that is aware of JS execution in RavenDB and hide that functionality from the rest of the code.
Now, one of the things that we do with the scripts we execute is expose to them both functions that they can call and documents to look at and mutate. Consider the following patch script:
This is on a customer document that may be pretty big, as in, tens of KB or higher. We don’t want to have to serialize the whole document into the JS environment and then serialize it back, that road lead to a lot of allocations and extreme performance costs. No, already with Jint we have implemented a wrapper object that we expose to the JS environment that would do lazy evaluation of just the properties that were needed by the script and track all changes so we can reserialize things more easily.
Moving to Jurassic had broken all of that, so we have to re-write it all. The good thing is that we already knew what we wanted, and how we wanted to do it, it was just a matter for us to figure out how Jurassic allows it. There was an epic coding montage (see image on the right) and we got it all back into working state.
After a lot of time, we had everything stable enough so we could now test RavenDB with the new JS engine. The results were… abysmal. I mean, truly and horribly so.
And even if you have missing properties along the way, the result will be null, and not an error, like normal. That requires quite deep integration with the engine in question and lead to some tough questions about the meaning of the universe and the role of keyboards in our life versus the utility of punching bugs (not a typo).
Ouch, painful to discover that at the end of the process. At least you got a good refactoring out of it.
With regards to the new Jint, ES6 features like arrow functions would be very much welcomed, both for the ease of writing them and the "this" capturing.
Sidenote: I had no idea you could do user.Address.City.Street.Number.StopAlready in patches. I just tried it in 3.5 Studio and sure enough it works, heh. I'm unsure whether to rely on that, though; I've always just used standard JS in my patches.
Judah, Yes, that was painful. I might give arrow functions a try when I have a free weekend, right now I realized that I'm looking into variable name resolution inside JS functions, and that is scary enough.
This is also show our indexes work in C#, you can do the same in the index, and it will not throw. This is really important for schema free db. That isn't something you can generally rely on, and many users will likely go with the standard approach, but this give us better behavior for the common scenario. In particular when you look at the actual query as in the previous post. Try imagining needing to pull a value from two levels down and doing that with null checking.
He he. Love the paragraph end statements.
Eric, I haven't checked how that is implemented, but if you don't do an interpreter, it is very hard to make the system secure. One of the things we tried to do with Jurassic is to plug some of that, and we were told that we missed at least three different ways to do that without even realizing it. The other problem is that building a language from scratch is _hard_, if there is an issue with the JS code, a user can take that, put it in a browser, and debug that. That means that I don't have to do anything to get it working. Hell, I can probably offer them a debugger right there in the studio if they need it.
@Oren: "if there is an issue with the JS code, a user can take that, put it in a browser, and debug that. That means that I don't have to do anything to get it working. Hell, I can probably offer them a debugger right there in the studio if they need it". Yes, except that even with just the null propagation it is not (strictly speaking) normal JS anymore. How about that?
@Oren: just to be clear, i love the idea of null propagation in a JS environment (even more so in one like this RavenDB usage), it's a really great feature to have.
@ Andrew Davey: I'm not convinced that would help. Roslyn is fairly heavy-weight and Roslyn scripting specifically has some significant performance issues (and wasn't the focus of the Roslyn team recently, so it probably won't improve much in the near future).
Andrew, There is no good way to give a C# based scripting language that isn't also a major security hazard And given the fact that we need to run this as part of our queries, we cannot allow that.
njy, Actually, no. In that case, what we'll pass to your code would be a Proxy, not a standard object. See here: https://medium.com/@andv/js-alternative-of-rubys-method-missing-78dfe600fe31 This would just work, pretty much.
@Oren oh, I see, that probably makes sense.
@Oren, I know you probably looked into this, but what was the reason you couldn't cache or reuse the instance of the engine ?
Ian, I most certainly did cache and reuse the instance.
@Oren, guess I am miss-understanding the post. I read it as Instantiating the engine was much more expensive. I think this part of the post does not make much sense to me
Ian, Creating the engine (and parsing / generating IL, which we count in engine creation) is expensive. However, what you are doing with the JS matters. In Jurassic, every time you make a call, you do a pretty expensive operation, and we had to do a lot of these. Sending data to Jurassic from the outside was expensive and getting data from Jurassic was expensive. And calling from inside Jurassic was expensive. Now, remember that I'm talking about this in the sense that I'm counting CPU instructions, and that I'm usually looking at this for orchestrating, not actual execution, so the integration cost was major for us.