3

I'm develop my first application using mvc3 nhibernate orm layer with mssql db.

This is my first application created with using nhibernate and everything is fine except intiial responsn time. After some investigations I'm implemented session per web request, which is definitely an upgrade, my entities are loaded much faster after first call, but my problem remains the same.

Initial response time is really slow, when I type domainname.com and hit enter lwaiting time is approx. 10-15 sec. and this is not actual loading time of content, after that time 10-15 sec. my site is starts to load, few more sec.

Is that time that session factory must init all "stuff" that needs but I tnink it must be something else. This is unacceptable.

My app is running on winhost on Site Memory Allocation 200 MB, so I think this is not the problem.

Any hints are welcome. If you need more details please ask.

Thanks

Update: After examing application session usage with nhibernate profiler I found some interesting stuff. Since I'm really a begginer in using profiler I think I found expensive session. IN general statistics 67 entities are loaded in 36.571 duration in seconds. This seconds value is really strange cause I have 10-max 15 sec to load.

Second update: global.asax

public class MvcApplication : System.Web.HttpApplication{     

public static ISessionFactory SessionFactory =
        MyDomain.Infrastructure.SessionProvider.CreateSessionFactory();

//My session factory is open in Application_Start() like this 
SessionFactory.OpenSession();

}

I'm using fluent approach in mapping my objects. So my session provider in domain project looks like this

//This should be used from web app, global.asax.cs calls
        public static ISessionFactory CreateSessionFactory()
        {
            string conStringName = "ConnectionString";
            var cfg = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008
                .ConnectionString(c => c.FromConnectionStringWithKey(conStringName)))
                .Mappings(m => m.FluentMappings.Add<Entity1>())
                .Mappings(m => m.FluentMappings.Add<Entity2>())
                .Mappings(m => m.FluentMappings.Add<Entity3>())
                .ExposeConfiguration(p => p.SetProperty("current_session_context_class", "web"))
                .BuildConfiguration();

            return cfg.BuildSessionFactory();            
        }

Update 3 Still no solution for this problem

Update 4 and final My problem is definitilly in sessionFactory. I think that my configuration object should be serialized. If anyone can be kind enough to show how to do it using my code showed here with fluently conf. I will gladlly accept his/her answer. Thanks.

tereško
  • 58,060
  • 25
  • 98
  • 150
panjo
  • 3,467
  • 11
  • 48
  • 82
  • Have you considered an IIS profiling tool? – asawyer May 25 '12 at 18:10
  • no, I have not, please explain further. – panjo May 25 '12 at 18:11
  • Add some trace information to see where is the bottleneck. That load time is not reasonable. – Claudio Redi May 25 '12 at 18:11
  • are you doing alot of db calls on page load ? what is happening on page load ? – Micah Armantrout May 25 '12 at 18:15
  • Are you building the session factory on application_start? or are you building session factory on every call? – Rippo May 25 '12 at 18:18
  • @Rippo No I'm calling open session on application_start. – panjo May 25 '12 at 18:23
  • @MicahArmantrout Actually I do have some db calls on page load, but as I said I'm not sure that is the problem, since content load time is few sec. 3,4sec. after initial loading time (which is 10-15sec). – panjo May 25 '12 at 18:24
  • I have never used nhibernate, but 200 MB seems low. – Internet Engineer May 25 '12 at 21:34
  • @InternetEngineer really? I was thinking when buying 10$/month account for one site with 200MB that will be more than enough. How do you measure app memory consuption. – panjo May 25 '12 at 21:38
  • That is hard question to answer. It depends on many factors. For example you never want to keep a GUID in session memory, instead you want to keep the identity ID of the user table. Because GUIDs will eat memory. Did you try nhibernate caching http://stackoverflow.com/questions/4942585/nhibernate-uses-a-lot-of-memory – Internet Engineer May 25 '12 at 21:46
  • yep, I know that guids consume 4x memory than integers, but still guids takes id from sequence list versus integers where integers must travel to database every time when record is created for identity, this is fameous doubt :) Nhibernate caching makes sense after first loading, so I think caching will not help here. Anyway thanks. – panjo May 25 '12 at 21:58
  • how far are you from finishing the application? is it slow from the get-go? if yes, then your foundation is flawed. if no, then you can trace back to from when did it get slow. – Ray Cheng May 26 '12 at 05:23

3 Answers3

12

A lot of details are missing, but since you say this is your first NHibernate app, i'm going to recommend the likely things to check:

  • Create NH SessionFactory once at application start (as @Rippo is getting at). SessionFactory is expensive to create. Do it in Application_Start()
  • Open a new NH Session for each web Request. Once the Request is over, throw it away. NH ISession are cheap/quick to create. Generally, it's bad practice to reuse or cache ISession for a long time. For a simple implementation, you can do it in your Controller if you like, since that only lives per request.
  • When querying (NH LINQ? QueryOver? what do you use), be sure to limit the records returned. Don't .ToList() the whole table and just show 20. Use Skip/Take.
  • Watch out for the SELECT N+1 problem. This can kill your performance on any OR/M.

Those would be my recommendations, code sight-unseen.

Update: so the primary problem seems to be the 10-15 sec startup, which is likely the SessionFactory initialization time during Application_Start.

I have not tried it yet, but the general recommendation to have quick startup times is to serialize the NH Configuration object to disk (which contains mappings), and load that on each startup (a primitive cache). If the mappings change, you would need to detect that, or else do a manual load (delete the serialized configuration file).

In your code, you are using Fluent NHibernate to build a FluentNHibernate.Cfg.FluentConfiguration instance, and call cfg.BuildSessionFactory() on it to return the new ISessionFactory. The Configuration you need to serialize is NHibernate.Cfg.Configuration. So you would probably modify your code to something like this:

    public static ISessionFactory CreateSessionFactory()
    {
        string conStringName = "ConnectionString";

        // http://weblogs.asp.net/ricardoperes/archive/2010/03/31/speeding-up-nhibernate-startup-time.aspx
        System.Runtime.Serialization.IFormatter serializer = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

        NHibernate.Cfg.Configuration cfg = null;

        if (File.Exists("Configuration.serialized"))
        {
            using (Stream stream = File.OpenRead("Configuration.serialized"))
            {
                cfg = serializer.Deserialize(stream) as Configuration;
            }
        }
        else
        {
            // file not exists, configure normally, and serialize NH configuration to disk
            cfg = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008
                .ConnectionString(c => c.FromConnectionStringWithKey(conStringName)))
                .Mappings(m => m.FluentMappings.Add<Entity1>())
                .Mappings(m => m.FluentMappings.Add<Entity2>())
                .Mappings(m => m.FluentMappings.Add<Entity3>())
                .ExposeConfiguration(p => p.SetProperty("current_session_context_class", "web"))
                .BuildConfiguration();

            using (Stream stream = File.OpenWrite("Configuration.serialized"))
            {
                serializer.Serialize(stream, cfg);
            }
        }

        return cfg.BuildSessionFactory();
    }

This would cache the configuration to disk, so your app startups will be fast. Of course, when you change the NH mappings, you would have to detect that and reload the Fluent Configuration, or else manually delete the cache file.

A couple other tuning comments:

  • You have .Mappings(m => m.FluentMappings.Add()).Mappings(m => m.FluentMappings.Add()) , etc. Just guessing here, but adding one-by-one may be creating multiple HBM files under the hood. You could try adding the mappings from an external assembly, and use .Mappings(M => M.FluentMappings.AddFromAssemblyOf())
  • really you should not do SessionFactory.OpenSession() in Application_Start(). Just create the SessionFactory there, and access SessionFactory.GetCurrentSession() in your code. Your global.asax should have:

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        // we open one NH session for every web request, 
        var nhsession = SessionFactory.OpenSession();
        // and bind it to the SessionFactory current session
        CurrentSessionContext.Bind(nhsession);
    }
    
    protected void Application_EndRequest(object sender, EventArgs e)
    {
        // close/unbind at EndRequest
        if (SessionFactory != null)
        {
            var nhsession = CurrentSessionContext.Unbind(SessionFactory);
            nhsession.Dispose();
        }
    }
    

That would be the way to do session-per-request.

Raul Nohea Goodness
  • 2,549
  • 23
  • 24
  • session factory is created inside application_start, I'm using session per web request with disposing on the end of the request, I'm using NH LINQ and I'm using ToList without take but I have only 25 entities loaded. So, I will examing SELECT N+1 problem. Thanks – panjo May 25 '12 at 22:17
  • OK, great - here is an example of using the NH LINQ .Fetch() extension method: var customers = session.Query().Fetch(c => c.Orders).ToList(); http://mikehadlow.blogspot.com/2010/08/nhibernate-linq-eager-fetching.html – Raul Nohea Goodness May 25 '12 at 22:23
  • +1, I was thinking the session factory creation as prime suspect #1 – dwerner May 25 '12 at 22:30
  • @dwerner , me too on SessionFactory. It's possible he could get the 2nd and later page loads rockin' in less than a second, but still 10+ seconds to init SessionFactory. At that point, it's time to look at other tuning options (warm-up , caching Configuration, mapping-by-code, etc). – Raul Nohea Goodness May 25 '12 at 22:39
  • @panjo - how do you do NH mapping? HBM xml files? Fluent NHibernate? NH 3.2 Mapping by code? – Raul Nohea Goodness May 25 '12 at 22:42
  • If you use separate .hbm files, you can concatenate them all together into a single one to drastically reduce load time. – dwerner May 25 '12 at 22:48
  • @dwerner I'm updated question to represent init part of global.asax related to session factory and sessionFactory class itself from my domain project. – panjo May 26 '12 at 07:18
3

Couple of extra points:-

  1. Are you writing anything to the bin folder? This causes a app recycle which then causes the session factory to be rebuilt.
  2. When you run the SQL that NHProf is showing you in Management Studio does it take time. Are you fully indexed up?
  3. TRIPLE check that you are not building the session factory more than once by introducing logging on Application_start

These problems are either related to SQL server or code, NHibernate is not the culprit!

Also I think you need to add tracing to your code to see where the real bottle neck is or use ANTS profiler (you get a 14 day free trial)

Rippo
  • 22,117
  • 14
  • 78
  • 117
  • 1. No I do not write anything to bin folder. 2. Even with commented db access code my init time is the same. 3. My question is updated to represent session factory init from global.asax and class itself inside domain projects. Thanks for your time – panjo May 26 '12 at 07:26
2

@raulg made some great points. Also consider:

  1. Serialize your NH config to disk and load it up in app start. Search google for some examples.
  2. Check your NHibernate logging! This catches people out. If you are using log4net or similar, make sure you are not logging "ALL" your NHibernate info as this kills perf and should only be used for debugging a problem.
  3. Beware of using not.lazyload in mappings - you can end up loading your entire DB in a query without realizing! Eager load in queries as needed.

Stick with it - its a great tool.

Serializing NH:

  if (_loadConfiguration && _configurationFile.Exists && _IsConfigurationFileValid())
            {
                configuration = _LoadConfigurationFromFile();
            }
            else
            {
                configuration = Fluently.Configure()
                    .Database(MsSqlConfiguration.MsSql2008
                        .ConnectionString(x => x.FromConnectionStringWithKey("Development"))
                        #if DEBUG
                        .ShowSql()
                        #endif
                    )
                    .ProxyFactoryFactory("NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate")
                    .Mappings(mappings => mappings.FluentMappings.AddFromAssemblyOf<UserMap>()
                                              .Conventions.Setup(MappingConventions.GetConventions()))
                    .Mappings(mappings => mappings.HbmMappings.AddFromAssemblyOf<UserMap>())
                    .BuildConfiguration();

                _SaveConfigurationToFile(configuration);
            }

  private bool _IsConfigurationFileValid()
        {
            var assInfo = new FileInfo(Assembly.GetCallingAssembly().Location);
            return _configurationFile.LastWriteTime >= assInfo.LastWriteTime;
        }

        private void _SaveConfigurationToFile(Configuration configuration)
        {
            //#if DEBUG
            //return;
            //#endif
            _logger.Debug("Starting to save NHibernate configuration to " + _configurationFile.FullName);
            using(var file = _configurationFile.OpenWrite())
            {
                new BinaryFormatter().Serialize(file, configuration);
                file.Close();
            }
            _logger.Debug("Finished saving NHibernate configuration to " + _configurationFile.FullName);
        }

        private Configuration _LoadConfigurationFromFile()
        {
            //#if DEBUG
            //    return null;
            //#endif
            _logger.Debug("Starting to load NHibernate configuration from " + _configurationFile.FullName);
            using(var file = _configurationFile.OpenRead())
            {
                var binaryFormatter = new BinaryFormatter();
                var config = binaryFormatter.Deserialize(file) as Configuration;
                file.Close();
                _logger.Debug("Finished loading NHibernate configuration from " + _configurationFile.FullName);
                return config;
            }
        }
Samuel Goldenbaum
  • 18,391
  • 17
  • 66
  • 104
  • I found this example code to serialize/load Configuration to disk: http://weblogs.asp.net/ricardoperes/archive/2010/03/31/speeding-up-nhibernate-startup-time.aspx . Feel free to add it to the actual answer. I'm thinking now that will definitively removed the 10-15 sec wait time. – Raul Nohea Goodness May 27 '12 at 01:31
  • @raulg can you post code solution with having my code in mind, I will accept your answer. I need practical example to figure it out. Anyway thanks. – panjo May 27 '12 at 19:32
  • @panjo , chev just added NH serialization example, it is better than my simple one, since it will check if it has to reload. Let us know if serialized NH config solves your problem! – Raul Nohea Goodness May 29 '12 at 15:01