A) Yes. An AppPool in IIS is really limited to about 1800MB in private bytes before you start getting into territory where the framework will start throwing out of memory exceptions. So when we have ~50 customers in an AppPool, you get 60MB * 50 = 300MB or ~ 1/6 of the total memory available to the process solely for the SessionFactory. I just fixed problem where configuration object was being kept in process which was also 60MB, thus nhibernate was sitting on about 1/3 of memory B) I completely agree with you here, only issue we run into is since some clients only use parts of our system, some update statements NEVER get executed, thus there is never a need to have them in memory. C) Completely agree
On B & C I think you are taking the GC workload argument across all statements, when GC work load argument was only specifically about interning the strings like and, or, ), (, etc. Like I said, I understand why they are cached, and I completely agree with all the arguments for caching them, the only argument against them being cached is when they are not used, or are used infrequently. On Tuesday, May 29, 2012 12:58:59 PM UTC-6, Ayende Rahien wrote: > > A) Are you seriously having problems with tens of MB being used? How much > memory do you have on the server? How much apps are running? > B) The reason those are cached is to _reduce_ memory usage. Otherwise, we > would need to generate an Update statement on every update. That would mean > that if you need to update two Users, you would have to create two update > statements. > By caching that, we avoid using N times memory, reduce the GC work load > and generally get much better performance and memory utilization. > > > On Tue, May 29, 2012 at 9:55 PM, Darren Kopp wrote: > >> Recently I've been trying to reduce memory usage of our website, and >> basically found that NHibernate was the largest source of memory usage in >> our system, and as I dug in found some things that I considered to be >> issues, though I understand the rationale behind them. I thought we might >> have a discussion about it though and talk about some solutions. I will say >> that my knowledge of nhibernate's inner workings and reasons why things are >> is very cursory and limited to what I have discovered/guessed during this >> process, which is why I would like this to be a discussion. >> >> *Background that can be skipped but explains why this is an issue for us. >> * >> The way our system was originally built is that in IIS each customer has >> their own website (so we are not multi-tenant), and we have a few builds >> that the customers are on (alpha, beta, stable) that we routinely move them >> between. Originally we had everyone in their own AppPool (process) but that >> caused a lot of memory issues because each site would have it's own copy >> off the code dll's loaded into memory, so we combined people into groups of >> app domains because IIS will share assemblies that are the same between the >> sites which now are in separate AppDomains rather than separate processes. >> This works fairly well, but we are still hitting the top end of the memory >> for an AppPool about every hour, so that AppPool will get recycled and a >> new process will be spun up. This kind of sucks because it takes about 10 >> seconds to initialize the application (yes, we serialize our nhibernate >> configuration). >> >> *The real issue* >> >> Now, while poking around in WinDbg, I decided to look at the size of the >> SessionFactory, which for our system was ~60MB. I also notice that there is >> about ~30MB of strings in the process, but i'm not sure how many are unique >> to nhibernate, but we'll just say that it's 20MB (before session factory is >> built, strings account for ~10MB). Now, when I look at the session factory, >> it looks like for every type and builds the persisters / select builders / >> whatever which build up SqlString instances and cache them. All of those >> strings are built by SqlString class and stored in the parts. The problem >> is, by keeping those and holding them, you can never free up that system >> memory. Likely this is so that those never have to be generated again, and >> I do see that SqlString is immutable so really it all makes sense, just >> there is the problem that the memory can never be free'd up by the system. >> This sucks when most things never get used (in our case, we have lots of >> different parts of our system, but most companies only use a couple). >> >> Now the question I have is do all of those strings _need_ to be cached? I >> understand all the reasons why: they never change so we should just >> generate once, generating on initialization removes need for locks, etc. At >> the same time I think, how expensive is it to generate an insert string? >> When it comes to select strings, how often is that string re-used? With >> dynamic queries (ie linq or just building up a query over or hql string), >> are they also stored someplace in which they can't be garbage collected? >> >> Another thing I noticed is that some strings could EASILY be interned so >> that they exist only once where currently there are millions of duplicates. >> There are strings like ) and , that have lots of instances, but not the >> same instance, duplicate instances across lots of SqlString instances. If >> there were an interned version that was used, it would help with the memory >> situation. On the other hand, these do not make up the majority of memory >> held by strings, maybe at most 0.5MB, but there are a lot, so interning >> could help the garbage collector out. >> >> *Ideas to help the situation* >> * >> * >> >> 1. First idea, and the least invasive/problematic would be to intern >> certain strings like "() ' and or" and use those when building sql >> strings. >> This is only really necessary due to keeping SqlString instances around, >> which pin those strings in memory >> 2. Don't cache SqlString / SqlCommand in >> persisters/generators/whatever, cache them in ISession and regenerate >> them >> in each session. >> 3. Cache them in a least-recently-used type cache for which a copy is >> injected into the ISession and is updated when sessions are disposed / >> closed (this would imply that some amounts of locking would need to be >> added, but only if sessions added new queries) >> >> >> Thoughts? Suggestions? I understand that this would constitute large >> changes, and I understand that likely nothing will come of this, but I do >> think that these are real issues and worth thinking about and looking out >> for in future coding as well. As for how much solving this would take, I >> have no idea, because like I said earlier, I only have a cursory >> understanding of nhibernate's inner workings. >> > >
