Friday, September 28, 2007

Improving Performance of ActiveRecord and NHibernate for Large Queries

First off, let me say that in general I think that NHibernate is fast enough for most use cases. And as always, save optimizations for last and use a profiling tool to tell what actually is taking up the most time instead of guessing what you think is taking up the most time.

With that said, there are occasions when you need to put a little extra thought into improving performance. A web application I'm working executes a database query that can return thousands of rows. I really wasn't pleased with the performance of the application for this use case. It took a longer than I wanted it to take, especially given that this query is run frequently by the application's users. I ran the NHibernate-emitted SQL query inside of SQL Server Management Studio, and the query returned nearly instantly, so that got me thinking.

I busted out the profiler. I'm using JetBrain's dotTrace profiling tool for .NET, which is a great tool. I loaded up my application to the view where I kick off this large query, turned on the profiling, and then let it run. Here are the results:

NHibernate is spending a lot of time determining whether or not it needs to flush changes to the results of my query to the database. In this specific use case however, no modifications will be made to these entities, so this check is just a waste of resources. It took this much time not to actually update the database with anything, but rather just to see if it should being doing any updates.

For those that don't know, NHibernate follows the unit of work pattern. All changes made to objects are not immediately flushed to the database (via INSERTs and UPDATEs.) Instead, NHibernate patiently waits for you to tell it that your session is over (or manually tell it to flush) before it starts updating the database.

There are a couple of ways to stop NHibernate from spending this much time on the flush. You can either evict all the objects that you don't want checked for a flush from the session, or you can just tell the session not to flush when closed. The latter option will not work if you actually make modifications to some other objects while using the same session, but I'm not making any changes to these objects, so that's fine with me. If you're using Castle ActiveRecord like I am, you can add an argument when you create the SessionScope surrounding your query to turn off flushing:

            using (new SessionScope(FlushAction.Never))


                // Large query inside here


Normally, the SessionScope is initialized with FlushAction.Auto, which causes NHibernate to perform the flush check at the end of a session.

As I said before, another option is to evict the objects that came from the query out of the session. In NHibernate, you can use the ISession.Evict() method to perform this action. Since I'm using ActiveRecord, I shy away from dealing with NHibernate directly whenever possible, so that is yet another reason I chose not to go this route.

Now that we've told NHibernate not to flush anything, and therefore not to check for something to flush, the performance has increased. Take a look:

After I made my changes, NHibernate did not perform any flushes during this request.

NHiberate and ActiveRecord are great tools and they make tedious data-driven tasks simple and easy, but it helps to know a little bit about what's going on under the hood. The takeaway from this is that you should look for opportunies to avoid triggering NHibernate's flush mechanism if you know an operation is read-only, especially when you're dealing with lots of entities in the session.

1 comment:

Anonymous said...

Unfortunatelly there is one problem with this solution: objects returned by query placed inside "using (new SessionScope(FlushAction.Never))" cannot be lazy-loaded or cannot have any lazy-loaded collections, otherwise you'll get "Cannot initialize proxy" error when you later access them (unless of course you put all processing inside using(){} block)