Before we start, let's have a quick look at the NHibernate caching system. NHibernate uses the following caches:
- Entity Cache
- Query Cache
- Collections Cache
- Timestamp Cache
// Queries DB, inserts into cache session.QueryOver<Users>().Where(u => u.FirstName = "John").Cachable(); // Pulls result from cache session.QueryOver<Users>().Where(u => u.FirstName = "John").Cachable();
When NHibernate receives the result from the database, it will store the entities in the entity cache, while storing the set of returned IDs in the query cache. When you perform the query again, It pulls the list of IDs from the query cache and then hydrates each entity from the entity cache.
Now suppose we delete a user in between performing both of these queries, or perhaps create a new one.
// Queries DB, insert into cache session.QueryOver<Users>().Where(u => u.FirstName = "John").Cachable(); // Marks as stale in cache session.Delete(john); // Queries DB again session.QueryOver<Users>().Where(u => u.FirstName = "John").Cachable();
NHibernate would take notice and not pull the second query from the query cache but instead return to the database for the latest information. In this way, NHibernate seems to do a rather great job at taking care of the cache. For a bit more information, have a look at this post by Ayende.
So, if instead, we create the two sessions (in the example above) from different session factories but with identical configurations, then the second level cache will be shared and still be used. But if the delete is performed between, then the second query will still hit the cache.
// Queries DB, insert into cache session1.QueryOver<Users>().Where(u => u.FirstName = "John").Cachable(); // Marks query and entities as stale in cache session1.Delete(john); // Does not notice that session1 marked it as stale, pulls from cache session2.QueryOver<Users>().Where(u => u.FirstName = "John").Cachable();
It would seem that the sharing of the timestamp cache should take care of this. Perhaps, the timestamp cache is not shared between the factories.
The cache is not designed to be shared between session factories. Normally, chances of key collisions are low due to the use of the entity GUID in the key. But since we create multiple session factories to access the same database, or if you used an incrementing int as the key, key collisions are possible. Most of the time, you could use the region prefix as shown in the blog post or at the bug report.
Where does this leave us? Due to the fact that the DeleteVisibleSessionFactory is only used to access entities about to be deleted, we decided that caching these entities is pointless and disabled caching on it. This prevents it from retrieving any stale data. The last issue is that an entity deleted in the DeleteVisibleSession will not be removed from the second level entity cache. Now we are clearing the entity cache manually after any delete in the event listeners.
NHibernateHelper.EvictEntity(@event.Entity as ModelBase2);
Due to the granular nature of our query cache, we decided to manage them on a per case basis. They often contain the ID of a parent object and need to be cleared individually. This provides us with the best compromise of complexity and performance. Letting us know that the entity cache will be managed properly by NHibernate and that the query cache is our responsibility.
About NexPort Solutions Group
NexPort Solutions Group is a division of Darwin Global, LLC, a systems and software engineering company that provides innovative, cost-effective training solutions and support for federal, state and local government, as well as the private sector.
0 comments :
Post a Comment