Showing posts with label cache. Show all posts
Showing posts with label cache. Show all posts

Saturday, March 31, 2012

NHibernate and Caching - Part - 2

So far we have seen how the NHibernate first level cache can silently improve performance when we deal with single Session.  But there should be something that works across NHibernate sessions right?

Of course there is, its called Second level cache.

Before we move on to show how Second Level Cache can be enabled, lets focus on, why is Second Level Cache so important.

Why should you enable Second Level Cache:

Well, in almost every serious project, you will have some sort of Reference Data.

What information is considered Reference Data?  

The information that acts as look-up information might be considered as Reference Data.  Information like, Countries, Employee Types etc could be categorized as reference data.  This information does not vary from User to User.  This information is used at various places in the application and is mostly never updated.

Imagine the case when two different users are entering their address in the application.  Let's say they are accessing the "Enter Your Address Page".  This page allows users to enter their address, it shows various address fields for this purpose.  This page also shows a Country drop down so that users can easily select their country.  Now to populate this country drop down, you will have to load all countries in the system.

Since two user threads are trying to enter the address, two NHibernate sessions will try to load the countries from the DB.  First level cache will not have the Countries List cached, hence, two exact same queries will be fired.  These queries will result in exact same result.

With me so far? 

Our aim in this post is to avoid two queries being fired when we try to load the Countries from two different NHibernate Session.  Some how, we should inform NHibernate that, Countries list is cache-able and it should go to the DB only once to fetch it and then cache it!

Without wasting any more time lets jump right into the steps to enable second level cache with NHibernate.

How do they Do it!

Country class and its mapping file looks like:
Nothing special here.

Setting up the Second Level Cache:

To enable second level cache in NHibernate, we need to configure a Second Level Cache Provider class.  There are numerous Second Level Cache provider's available.  All have their merits and demerits.  For the sake of this post we are going to use the SysCache provider as the second level cache provider.  You can download cache providers from here

From the downloaded package include the NHibernate.Caches.SysCache.dll into the project.

Lets look at the Fluent NHibernate configuration which enables the SysCache provider.
The only special thing that you might have noticed in the previous configuration is the call to the "Cache" method. This call enables the second level cache. We also configure the ProviderClass as the NHibernate.Caches.SysCache.SysCacheProvider.

That's about it, we have finished configuring the second level cache.  Lets see it in action, shall we?

Test code to see Second Level Cache in Action:
The test code simply calls the GetAllCountries method twice to get the list of all the countries in the system. GetAllCountries will first open a new NHibernate session and then fires off the query to fetch all the countries in the system.

We have enabled second level caching in the previous step, now if we run the test, lets see the SQL's generated.
Well not what we expected right?

Remember that our goal is that, only one query should be fired to load all countries.  The other query should get all countries from the cache.  What is the missing link?  Why did NHibernate not cache the first query?

Well, because we didn't tell NHibernate to cache the query, that's why! 

We need to inform NHibernate that certain queries are cache-able and that the results should be saved in the second level cache.  Let's look at the update test code which informs NHibernate that the countries query is cache-able.
Only thing that we changed was added the calls to "Cacheable" and specified the "CacheMode".

  • Cacheable information NHibernate that this particular query is cache-able
  •  There are various cache mode in which the Second Level Cache can be used.  
  • Normal mode means
    • NHibernate will first look for information in second level cache, if found it returns it.
    • If information is not found in the cache then, it will hit the DB to get the information.
    • But before returning the results from the DB, it will also put the results in the Cache.
  • Other CacheModes are Get, Ignore, Put and Refresh.  I will not go into details of each.  Can read about them in NHibernate documentation.
After this updates lets look at the queries that get fired.
Hmm, looks like we have gone from bad to worse! This time NHibernate fired three queries instead of one!

But lets step back for a moment and look at the queries that it has fired.  
  • First query gets all the countries in the system, that's expected.
  • Second query is different from the first query, it's trying to load the country using its ID.  It loads the country with ID=1
  • Third query is similar to the second query.  Only difference is, it's trying to load the country with ID=2
Why did this happen?

If caching is going to increase the number of queries getting fired then we certainly do not need it right?

Wrong!  The problem is that, we have just informed NHibernate that the query itself is cache-able.  By default NHibernate will only cache the identifiers returned by the cache-able query.  In our case the cache-able query (one that loads all countries) returned two countries, one with ID=1 and other with ID=2.  Since we have informed NHibernate to cache the query, it caches the identifiers of the returned result set. 



It effectively means that, NHibernate just caches the fact that, when a query to load all countries is fired, Country with ID=1 and 2 are to be fetched and returned.  And since NHibernate has not cached the Country entity, it needs to fire two more queries to load the country instances with ID=1 and 2.  That's why the other two queries are fired.

Well in that case this is horrible right? 

Yes, if second level cache is not used correctly then, you can end up firing more queries than when it was not configured.

So let cut the chase here and see how we can configure it correctly.

Making the Entity itself cache-able:

This is the last and final step, we just need to inform NHibernate that not just the identifiers but the entire Country entity is cache-able.  How do we do this?  Simple, in the mapping file.  Here's how the updated mapping file looks like
The only addition to the mapping file is the call to Cache.ReadOnly(). This informs NHibernate that this entity is a cache-able entity and its going to hold read only information.

Lets run the test after this update to see what queries are fired.
Yep, we have got it right this time around! Only one query is fired even though we are trying to load all countries from two different NHibernate Session! Mission Accomplished!

 Now imagine if there are hundreds of users concurrently accessing your application, How many queries would you end up saving?

Sunday, February 26, 2012

NHibernate and Caching - Part - 1

NHibernate has built in support for caching.  It might sound like a very simple feature to implement but, in reality, its one of the most complex piece.   Using various Caching strategies provided by NHibernate we can achieve extremely good performance improvements.

NHibernate currently provides two separate caching mechanism.  First Level Cache and Second Level Cache.  In this post we will talk about First Level Cache only.  In the next post we will see the Second Level Cache in more details.

First Level Cache

This cache is implemented using the NHibernate Session.  Each instance of NHibernate Session acts as a cache.  NHibernate keeps all objects loaded using a specific instance of Session, in the cache. This cache is lost as soon as the session is disposed.

This cache is enabled by default and nothing special has to be done to work with this cache.  Lets look at the performance improvements that we get because of the first level cache.

For the sake of this post lets consider a very simple data model.  Let's say that we want to model Employees and Departments tables using NHibernate.  One department can have many employees and one employee belongs to one and only one department.

We will be using NHibernate Fluent API to map the NHibernate model classes.  The POCO classes for Employee and Department would look like
Mapping files for these classes are also very stock standard. The data in the departments table looks like:
Department Data shows only one record
We have inserted one department called Operations.

The data in the employees table looks like:

Employee SpiderMan belongs to Department Operations
There is just one employee, SpiderMan who belongs to the Operations department.

Enough setup, Lets write a test to see first level cache in action.

To see the first level cache in action lets write a NUnit test.

Case - 1 - Multiple queries will not be fired if entity is queried using the identifier column

In this unit test lets see what happens when we retrieve an Employee with ID = 1 twice using the same session instance. Before we begin lets setup the NHibernateSessionHelper that will build the SessionFactory for us
The static class NHibernateSessionHelper builds the NHibernate Session Factory. Notice that we have configured NHibernate in such a way that it will display all the SQL's that it executes (using the ShowSql() method). Lets look at the actual test code.
As you can see, we are opening up a new NHibernate Session and fetching the Employee with ID = 1 twice. Notice that we are fetching the Employee with ID = 1 twice but using the same session.

What do you think, how many queries will be fired? 

Lets look at the NUnit output to see how many queries are fired.
NHibernate fires only one query.

Why?

This is the first example of how NHibernate makes use of the first level cache.
After the above line of code is executed, NHibernate has already cached the Employee with ID = 1 in its first level cache. Hence, when the second statement executes, NHibernate first looks up the entity Employee with ID = 1 in the first level cache, it finds it, now it knows that, there is no need to fire another query to retrieve the same employee object all over again.

This is a very important optimization. In a complex flow of events, if you happen to load the same entity, using its primary key more than once in the same session then, NHibernate would not fire multiple queries!

The last statement in the test is also an important point, it asserts that the employee reference returned by query 1, is the same reference that is returned by query 2. Another important point, in a given NHibernate session there can be one and only one reference of a given entity with a given identifier!

Case - 2 - Multiple queries will not be fired if, entity with a given identifier is already loaded in the session

Lets consider this test:
In this test we are first fetching the Department with ID = 1, then asserting that the employee count of this department = 1. We are then fetching the employee with ID = 1 explicitly.

What do you think how many queries will be fired?
Yes, only two queries are fired.
  • First query was fired to get the department with ID = 1
  • Second query was fired to get all the employees belonging to the department with ID = 1 (since the employees of department are Lazy loaded). 
Question: Where is the third query, i.e. the query to load the employee with ID = 1?  Why was it not fired?

Again, we can see the First level cache in action.  The department with ID = 1 has one employee whose ID = 1.  This employee got loaded when the second query was fired to load all the employees belonging to department with ID = 1.

When we fire the query to fetch the employee with ID = 1, NHibernate first looks into its first level cache to see if the entity is already loaded, and guess what, it finds that it was already loaded and there is no need to fire another query to load the same employee all over again!


NHibernate first level cache is a life savior, but it works only when we deal with the same NHibernate session instance.  It does not work across multiple NHibernate sessions.  Is there something that works across NHibernate Session's? 

Yes, the Second Level Cache.  We will talk about the Second level cache in the next post.  Stay tuned guys!
Have some Fun!