Skip to content
This repository has been archived by the owner on Feb 22, 2020. It is now read-only.

MongoServer lifecycle #7

Closed
saille opened this issue Jun 29, 2015 · 5 comments
Closed

MongoServer lifecycle #7

saille opened this issue Jun 29, 2015 · 5 comments
Assignees

Comments

@saille
Copy link

saille commented Jun 29, 2015

This is half question, half bug report.

I believe there might be a significant bug in MongoRepository's management of the lifecycle of MongoServer. I believe MongoServer should be singleton/static, and the same instance should be used every time. Otherwise there's some overhead in connecting to the database with each instantiation of the repository.

Here in Util.cs:
private static MongoDatabase GetDatabaseFromUrl(MongoUrl url)
{
var client = new MongoClient(url);
var server = client.GetServer(); // <---- should not be returning a new MongoServer instance!
return server.GetDatabase(url.DatabaseName); // WriteConcern defaulted to Acknowledged
}

See also http://stackoverflow.com/questions/10241641/what-is-the-right-way-to-manage-mongodb-connections-in-asp-net-mvc

I picked this up when looking at performance of queries - was seeing 200ms+ overhead with each new MongoRepository.

Apologies if I have misunderstood the code.

@RobThree
Copy link
Owner

See MongoClient Connection Pooling. What goes there essentially also goes for MongoRepository since MongoRepository is merely a layer on top of the C# Driver.

The driver will, for the most part, use the same connection settings. [...] Hence, the best practice will be to only instantiate 1 MongoClient at app startup and use that. Put it in an IoC container, create a static variable somewhere, etc... Essentially, make it a singleton. Is there any reason you are constructing a new client for every request?

@saille
Copy link
Author

saille commented Jun 29, 2015

I'm just instantiating MongoRepository, with a connection string argument. MongoRepository is calling Util's GetDatabaseFromUrl() on my behalf. Callstack is:
MongoRepository(connString)
Util.GetCollectionFromConnectionString(connectionString)
Util.GetCollectionFromConnectionString(connectionString, GetCollectionName())
Util.GetDatabaseFromUrl(new MongoUrl(connectionString))
.GetCollection(collectionName)

from where it does this:
var client = new MongoClient(url);
var server = client.GetServer();
return server.GetDatabase(url.DatabaseName); // WriteConcern defaulted to Acknowledged

@RobThree
Copy link
Owner

I understand; but why not keep the repository alive (e.g. keep a reference around instead of instantiating a new one every time)?

@saille
Copy link
Author

saille commented Jun 29, 2015

Thanks Rob. I did structure things that way for a while. I would prefer to see the dependency on a long lived MongoServer/MongoDatabase object, or a long lived MongoRepository instance made very explicit. The examples need to show that pattern of use - bear in mind many people don't use DI containers, and even if they do, they may inject a MongoRepository instance per request without thinking about it.

I managed to figure this out myself, but I know there's a lot of Mongo / .NET noobs who will fall into the trap of simply new'ing up a MongoRepository every time they need it. For your typical business application they may never notice the latency they have incurred.

@RobThree
Copy link
Owner

I am having trouble reproducing your findings; I have created a very simple test-project that

  • Creates a new repository
  • Adds a document/entity

And we do that in 10 "runs" of 1000 loops:

using MongoRepository;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int runs = 0; runs < 10; runs++)
            {

                var results = new List<TimeSpan>();

                for (int i = 0; i < 1000; i++)
                {
                    var s = Stopwatch.StartNew();
                    var repo = new MongoRepository<Customer>();
                    repo.Add(new Customer { Name = Guid.NewGuid().ToString("N") });
                    results.Add(s.Elapsed);
                }

                Trace.WriteLine(string.Format("Average: {0}", TimeSpan.FromTicks((long)results.Average(r => r.Ticks))));
                Trace.WriteLine(string.Format("Total  : {0}", TimeSpan.FromTicks((long)results.Sum(r => r.Ticks))));
            }
        }
    }

    class Customer : Entity
    {
        public string Name { get; set; }
    }
}

My output is:

Average: 00:00:00.0003750
Total  : 00:00:00.3750495
Average: 00:00:00.0003583
Total  : 00:00:00.3583765
Average: 00:00:00.0001761
Total  : 00:00:00.1761708
Average: 00:00:00.0001808
Total  : 00:00:00.1808039
Average: 00:00:00.0001764
Total  : 00:00:00.1764045
Average: 00:00:00.0001751
Total  : 00:00:00.1751791
Average: 00:00:00.0001778
Total  : 00:00:00.1778107
Average: 00:00:00.0001794
Total  : 00:00:00.1794380
Average: 00:00:00.0001788
Total  : 00:00:00.1788072
Average: 00:00:00.0001770
Total  : 00:00:00.1770513

The above results are for localhost; The results below are for another host on the LAN:

Average: 00:00:00.0006785
Total  : 00:00:00.6785227
Average: 00:00:00.0005014
Total  : 00:00:00.5014012
Average: 00:00:00.0004766
Total  : 00:00:00.4766673
Average: 00:00:00.0004823
Total  : 00:00:00.4823463
Average: 00:00:00.0004819
Total  : 00:00:00.4819672
Average: 00:00:00.0004909
Total  : 00:00:00.4909650
Average: 00:00:00.0004643
Total  : 00:00:00.4643221
Average: 00:00:00.0005465
Total  : 00:00:00.5465504
Average: 00:00:00.0004765
Total  : 00:00:00.4765501
Average: 00:00:00.0004820
Total  : 00:00:00.4820312

As you can see it's slower but that is to be expected since we actually have to go through/over the network. The results are pretty similar when in new up the repo outside of the inner for-loop:

var repo = new MongoRepository<Customer>();
for (int i = 0; i < 1000; i++)
{
    var s = Stopwatch.StartNew();
    repo.Add(new Customer { Name = Guid.NewGuid().ToString("N") });
    results.Add(s.Elapsed);
}

Results:

Average: 00:00:00.0006715
Total  : 00:00:00.6715329
Average: 00:00:00.0005192
Total  : 00:00:00.5192991
Average: 00:00:00.0003974
Total  : 00:00:00.3974282
Average: 00:00:00.0005721
Total  : 00:00:00.5721572
Average: 00:00:00.0005333
Total  : 00:00:00.5333527
Average: 00:00:00.0004177
Total  : 00:00:00.4177798
Average: 00:00:00.0003809
Total  : 00:00:00.3809415
Average: 00:00:00.0004902
Total  : 00:00:00.4902817
Average: 00:00:00.0004598
Total  : 00:00:00.4598998
Average: 00:00:00.0004196
Total  : 00:00:00.4196392

There's much to say about this test-case (as it doesn't take "warmup" into account, has a abysmal way of measuring etc. etc.) but as you can see my results are very different than from what you are seeing.

Are you sure you're not doing very expensive/slow DNS lookups? Or connecting to a high-latency host? Maybe something other network related?

@RobThree RobThree self-assigned this Oct 9, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants