Troubleshooting nHibernate Performance
Introduction
nHibernate is still one of the most popular ORM layers in use today. It provides support for collections, advanced querying, caching, statement batching, automatic change tracking for entities and more, making it one of more mature ORM layers in use. However, several projects that I have been involved with seem to point fingers at nHibernate’s performance. This post summarizes some of my findings from investigating bottlenecks, misconfigurations and known nHibernate gotchas. If you have additional nHibernate performance tweaks, please feel free to provide feedback.
Symptoms
You have probably already run some type of load test (if you would like to get started with load testing, try this post) – which indicates slow response times with increasing user load. Some symptoms that n-Hibernate may be a problem include:
- Query execution times in excess of 1 second (use the ‘profiler’ below to identify your bottleneck) .
- Frequent deadlock errors thrown by underlying database. In SqlServer , they may look like (note: Here is a post discussing how to quickly identify such sql server deadlocks).
Transaction (Process ID xx) was deadlocked on {xxx} resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
Where to look first ? (nHibernate’s generated sql queries)
You want to get an estimate of the queries that nHibernate is sending to the database – and how long those queries are taking to execute. I don’t know of a better tool than Hibernating Rhinos nHibernate profiler.
Here are some of the things the tool will tell you:
- what the underlying sql query was – and how long it took to execute. It shows the execution times of both the database as well as total execution time (that includes nHibernate’s overhead)
- Which sessions are opened by nHibernate as your request is sent. And which queries are part of each session.
Once you have an idea of the more expensive queries, there are a few things you can do to fix their performance:
Use FetchMode.JOIN
Without the JOIN, your query will get broken down into multiple smaller queries – each of which will require nHibernate to track each of the entities. This is overall more expensive than a single JOIN – which is what you probably intended in the first place. To keep your JOIN intact, use NHibernate.FetchMode.Join in the Criteria.SetFetchMode as shown below.
criteria.CreateAlias("Products", "SingleProduct", JoinType.LeftOuterJoin)—> NOTE the JOINCriteria.SetFetchMode("SingleProduct", NHibernate.FetchMode.Join) –> NOTE THE JOIN (Without this join, this will translate to multiple fetches i.e. multiple round trips)
Projection(Projections.Projectionist() _
Add(Projections.GroupProperty("Id"), "Id")
.Add(Projections.GroupProperty("Number"), "Number")
Get rid of the XML (Use Fluent nHibernate – nuGet package)
nHibernate maintains mappings in XML files. This slows down startup of nHibernate – and is also error-prone (since the compiler has no idea if your XML file has newly renamed properties). Fluent solves both of these problems by eliminating the XML files altogether. The mappings are moved to actual code and are compiled along with the rest of your application – providing a fail-safe check at compile time (to pick up renamed, changed properties). Here is what a sample fluent mapping in c# source looks like (you do not need to do any of this manually, Fluent generates these mappings for you):
public AMap()
{
/* mapping for id and properties here */
HasOne(x => x.child)
.Cascade.All();
}
public BMap()
{
/* mapping for id and properties here */
References(x => x.parent)
.Unique();
}
nHibernate Transactions
Transactions – By default, if you do not set an isolation level on your transactions, the isolation level will be ‘undefined’. This tells your database to use its default, IMPLICIT transactions – which it has to REVERT to – for every single query that nHibernate sends it (this article discusses this in detail). To set a specific isolation level, just set the property in your nHibernate’s configuration section in your web (or app) .config.
<property name="connection.isolation">ReadCommitted</property>
Transactions Summary – Always use transactions, always define a transaction isolation level (if you are unsure, READCOMMITTED is a good, middle road option).
nHibernate Sessions
What is session flushing ?
A session will not write to the database every time something is changed. In order to achieve better performance, database access is usually delayed until the last possible moment. This avoids unnecessary database calls, i. e. when multiple fields of a persistent objects are changed within a session. This synchronization with the database is called flushing. For instance, suppose you’ve loaded a hundred different entities in one session. If the FlushMode is set to automatic, it means that NHibernate will perform a dirty check for each entity associated with the session before each query is executed.
Read this post about dealing with nHibernate sessions – and why you should not be performing multiple operations inside a session anyway.
Manual Session Flushing
Flushing the cache is a known source of performance issues in nHibernate.
To see if flushing is the source of your problems, try the following:
var session = SessionManager.GetSession();
// Right before you begin your transaction, flush the session and turn off automatic flushing
session.Flush();
session.FlushMode = NHibernate.FlushMode.Never;
using (var transaction = session.BeginTransaction())
{
// your ICriteria , linq query
//....
//....
//....
session.Save(nomination);
// Before committing (and after saving your session), turn on automatic session flushing
session.FlushMode = NHibernate.FlushMode.Auto;
transaction.Commit();
}
So, before you begin your transaction, do a manual flush (session.Flush()) and turn off auto flushing (FlushMode.Never). Then, after you save your session and before you commit your transaction, turn auto flushing back on.
Use Lazy Loading
-
In Fluent-nHibernate, you would use the .eager property in code as shown below
var user = session.QueryOver<User>()
.Fetch(u => u.Comments)
.Eager
.List()
.Where(u => u.Id == userId)
.FirstOrDefault();
-
In non-fluent nHibernate, you can do this using the following (Default-lazy = true):
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="@core.assembly@"
default-access="nosetter.camelcase-underscore" default-lazy="true">
Singleton Session Manager
Make sure the Session Manager you are using is a singleton (read ‘singleton pattern in C#’, if you are new to the Singleton Pattern). Creating multiple instances of the session manager is not required – and can use up a lot of memory – since nHibernate uses a session for each unit of work.
public class MySessionManager
{
private static ISessionFactory _sessionFactory;
public ISession OpenSession()
{
lock (factorylock)
{
if (_sessionFactory == null)
{
var cfg = Fluently.Configure().
Database(SQLiteConfiguration.Standard.ShowSql().UsingFile("Foo.db")).
Mappings(m => m.FluentMappings.AddFromAssemblyOf<MappingsPersistenceModel>());
_sessionFactory = cfg.BuildSessionFactory();
BuildSchema(cfg);
}
}
return _sessionFactory.OpenSession();
}
private static void BuildSchema(FluentConfiguration configuration)
{
var sessionSource = new SessionSource(configuration);
var session = sessionSource.CreateSession();
sessionSource.BuildSchema(session);
}
}
Summary
nHibernate is a powerful and mature ORM layer which is possibly the most popular ORM within the .NET community. However, if used incorrectly, it can be the source of a variety of performance bottlenecks. This post describes some of nHibernate’s common performance issues – along with solutions.
Leave a Reply